From f1ca2ae26c2d230799d2c090f30be9692a3b3808 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 08:44:51 +0800 Subject: [PATCH 01/30] Unify terminal appearance preference controls --- Frameworks/Resources/Localizable.xcstrings | 36 +++++- .../Terminal/TerminalAppearance.swift | 20 +++- .../Terminal/TerminalFontFields.swift | 51 ++++++++ .../Terminal/TerminalThemePicker.swift | 44 +++++++ .../Settings/MobileTerminalSettingsView.swift | 22 +--- TFITests/MobileTerminalFontTests.swift | 19 ++- TFM/Features/Settings/SettingsPageView.swift | 42 +------ TFM/Platform/TerminalFontCatalog.swift | 5 +- TFMTests/TerminalFontRenderingTests.swift | 29 ++++- TFMTests/TerminalThemeTests.swift | 113 ++++++++++++++++++ 10 files changed, 310 insertions(+), 71 deletions(-) create mode 100644 Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift create mode 100644 Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift diff --git a/Frameworks/Resources/Localizable.xcstrings b/Frameworks/Resources/Localizable.xcstrings index 7417213a..000965e5 100644 --- a/Frameworks/Resources/Localizable.xcstrings +++ b/Frameworks/Resources/Localizable.xcstrings @@ -17143,6 +17143,23 @@ } } }, + "No Theme Selected" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未选择主题" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "未選擇主題" + } + } + } + }, "No Transfers" : { "extractionState" : "manual", "localizations" : { @@ -28762,6 +28779,23 @@ } } }, + "This theme is unavailable for this appearance. Using “%@” instead." : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此主题不适用于当前外观,将改用“%@”。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "此主題不適用於目前外觀,將改用「%@」。" + } + } + } + }, "This Vault record requires sync format %lld." : { "extractionState" : "stale", "localizations" : { @@ -31898,4 +31932,4 @@ } }, "version" : "1.2" -} \ No newline at end of file +} diff --git a/Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift b/Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift index 053ea920..552e325f 100644 --- a/Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift +++ b/Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift @@ -59,6 +59,20 @@ public nonisolated enum TermindTerminalThemeCatalog { public static let defaultDark = TermindTerminalPalette.termindDark.ghosttyDefinition public static let defaultLight = TermindTerminalPalette.termindLight.ghosttyDefinition + public static let darkThemes = allThemes.filter(\.isDark).sorted { $0.name < $1.name } + public static let lightThemes = allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name } + + public static func themes(isDark: Bool) -> [GhosttyThemeDefinition] { + isDark ? darkThemes : lightThemes + } + + public static func fallbackTheme(isDark: Bool) -> GhosttyThemeDefinition { + isDark ? defaultDark : defaultLight + } + + public static func resolvedTheme(named name: String, isDark: Bool) -> GhosttyThemeDefinition { + theme(named: name).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallbackTheme(isDark: isDark) + } public static func terminalConfiguration( themeName: String, @@ -67,8 +81,7 @@ public nonisolated enum TermindTerminalThemeCatalog { isDark: Bool, font: TerminalFontPreferences = .init() ) -> GhosttyTerminal.TerminalConfiguration { - let fallback = isDark ? defaultDark : defaultLight - let selected = theme(named: themeName).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallback + let selected = resolvedTheme(named: themeName, isDark: isDark) let base = selected.toTerminalConfiguration() let themed = customEnabled ? TerminalCustomConfiguration(customText).applying(to: base) : base // Ghostty renders theme commands after session overrides. Typography must @@ -88,8 +101,7 @@ public nonisolated enum TermindTerminalThemeCatalog { customText: String, isDark: Bool ) -> TermindTerminalPalette.RGB { - let fallback = isDark ? defaultDark : defaultLight - let selected = theme(named: themeName).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallback + let selected = resolvedTheme(named: themeName, isDark: isDark) if customEnabled, let value = TerminalCustomConfiguration(customText).lastValue(for: "background"), let customBackground = TermindTerminalPalette.RGB(hex: value) diff --git a/Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift b/Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift new file mode 100644 index 00000000..8d72261d --- /dev/null +++ b/Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift @@ -0,0 +1,51 @@ +import SwiftUI +import TermindDomain + +public struct TerminalFontFields: View { + public static let sizeRange: ClosedRange = 8...36 + + @Binding private var preferences: TerminalFontPreferences + private let families: [String] + + public init(preferences: Binding, families: [String]) { + _preferences = preferences + self.families = families + } + + public var body: some View { + TerminalFontPicker(family: $preferences.family, families: families) + VStack(alignment: .leading, spacing: 8) { + Picker("Font Size", selection: $preferences.sizeFollowsTheme) { + Text("Follow Theme").tag(true) + Text("Custom").tag(false) + } + .pickerStyle(.menu) + if !preferences.sizeFollowsTheme { + sizeEditor + } + } + } + + @ViewBuilder private var sizeEditor: some View { + #if os(macOS) + HStack { + Slider(value: $preferences.size, in: Self.sizeRange, step: 1) + .labelsHidden() + .accessibilityLabel("Font Size") + .accessibilityValue(preferences.size.formatted()) + sizeLabel.frame(width: 24, alignment: .trailing) + } + #else + Stepper(value: $preferences.size, in: Self.sizeRange, step: 1) { + sizeLabel + } + .accessibilityLabel("Font Size") + .accessibilityValue(preferences.size.formatted()) + #endif + } + + private var sizeLabel: some View { + Text(preferences.size, format: .number.precision(.fractionLength(0))) + .monospacedDigit() + } +} diff --git a/Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift b/Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift new file mode 100644 index 00000000..85567091 --- /dev/null +++ b/Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift @@ -0,0 +1,44 @@ +import GhosttyTheme +import SwiftUI + +public struct TerminalThemePicker: View { + @Binding private var selection: String + private let isDark: Bool + + public init(selection: Binding, isDark: Bool) { + _selection = selection + self.isDark = isDark + } + + private var themes: [GhosttyThemeDefinition] { + TermindTerminalThemeCatalog.themes(isDark: isDark) + } + + private var selectedThemeIsAvailable: Bool { + TermindTerminalThemeCatalog.theme(named: selection)?.isDark == isDark + } + + private var fallbackName: String { + TermindTerminalThemeCatalog.fallbackTheme(isDark: isDark).name + } + + public var body: some View { + Picker("Theme", selection: $selection) { + if !selectedThemeIsAvailable { + if selection.isEmpty { + Text("No Theme Selected").tag(selection) + } else { + Text(verbatim: selection).tag(selection) + } + } + ForEach(themes, id: \.name) { theme in + Text(verbatim: theme.name).tag(theme.name) + } + } + if !selectedThemeIsAvailable { + Text("This theme is unavailable for this appearance. Using “\(Text(verbatim: fallbackName))” instead.") + .font(.caption) + .foregroundStyle(.secondary) + } + } +} diff --git a/TFI/Features/Settings/MobileTerminalSettingsView.swift b/TFI/Features/Settings/MobileTerminalSettingsView.swift index b5dc8527..34b368c4 100644 --- a/TFI/Features/Settings/MobileTerminalSettingsView.swift +++ b/TFI/Features/Settings/MobileTerminalSettingsView.swift @@ -15,21 +15,7 @@ struct MobileTerminalSettingsView: View { themeSection(dark: false) themeSection(dark: true) Section("Font") { - TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families) - VStack(alignment: .leading, spacing: 8) { - Picker("Font Size", selection: $font.sizeFollowsTheme) { - Text("Follow Theme").tag(true) - Text("Custom").tag(false) - } - .pickerStyle(.menu) - if !font.sizeFollowsTheme { - Stepper(value: $font.size, in: 8...36, step: 1) { - Text(font.size, format: .number.precision(.fractionLength(0))).monospacedDigit() - } - .accessibilityLabel("Font Size") - .accessibilityValue(font.size.formatted()) - } - } + TerminalFontFields(preferences: $font, families: TerminalFontCatalog.shared.families) } Section("Integration") { Toggle("Shell Integration", isOn: binding(\.shellIntegration)) } if let error = model.storageError { Text(error).foregroundStyle(.red) } @@ -64,11 +50,7 @@ struct MobileTerminalSettingsView: View { private func themeSection(dark: Bool) -> some View { Section { - Picker("Theme", selection: binding(dark ? \.darkTheme : \.lightTheme)) { - ForEach(TermindTerminalThemeCatalog.allThemes.filter { $0.isDark == dark }, id: \.name) { - Text(verbatim: $0.name).tag($0.name) - } - } + TerminalThemePicker(selection: binding(dark ? \.darkTheme : \.lightTheme), isDark: dark) Toggle("Custom Configuration", isOn: binding(dark ? \.darkCustomEnabled : \.lightCustomEnabled)) Button("Edit Configuration", systemImage: "pencil") { customText = diff --git a/TFITests/MobileTerminalFontTests.swift b/TFITests/MobileTerminalFontTests.swift index bc5641f0..c5faf9f1 100644 --- a/TFITests/MobileTerminalFontTests.swift +++ b/TFITests/MobileTerminalFontTests.swift @@ -11,14 +11,27 @@ import UIKit struct MobileTerminalFontTests { @Test func fontCatalogIncludesOnlyAvailableMonospacedFamilies() { TerminalFontCatalog.shared.refresh() - #expect(!TerminalFontCatalog.shared.families.isEmpty) - #expect(!TerminalFontCatalog.shared.families.contains("Helvetica")) - for family in TerminalFontCatalog.shared.families { + let catalogFamilies = TerminalFontCatalog.shared.families + #expect(!catalogFamilies.isEmpty) + #expect( + catalogFamilies + == catalogFamilies.sorted { $0.localizedStandardCompare($1) == .orderedAscending }) + for family in catalogFamilies { #expect( UIFont.fontNames(forFamilyName: family).contains { UIFont(name: $0, size: 14)?.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) == true }) } + + let proportionalFamily = UIFont.familyNames.first { family in + let fonts = UIFont.fontNames(forFamilyName: family).compactMap { UIFont(name: $0, size: 14) } + return !fonts.isEmpty + && fonts.allSatisfy { !$0.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) } + } + #expect(proportionalFamily != nil) + if let proportionalFamily { + #expect(!catalogFamilies.contains(proportionalFamily)) + } } @Test(arguments: ["en", "zh-Hans", "zh-Hant"]) diff --git a/TFM/Features/Settings/SettingsPageView.swift b/TFM/Features/Settings/SettingsPageView.swift index dbfac41a..96120a9c 100644 --- a/TFM/Features/Settings/SettingsPageView.swift +++ b/TFM/Features/Settings/SettingsPageView.swift @@ -682,10 +682,6 @@ private struct TerminalSettings: View { @Environment(AppModel.self) private var app @TerminalFontSettings private var font @Environment(\.scenePhase) private var scenePhase - private var darkTheme: String { app.terminalSettings.preferences.darkTheme } - private var lightTheme: String { app.terminalSettings.preferences.lightTheme } - private var darkCustomEnabled: Bool { app.terminalSettings.preferences.darkCustomEnabled } - private var lightCustomEnabled: Bool { app.terminalSettings.preferences.lightCustomEnabled } private func binding(_ keyPath: WritableKeyPath) -> Binding { Binding( @@ -693,31 +689,20 @@ private struct TerminalSettings: View { set: { app.terminalSettings.set($0, for: keyPath) }) } - private var darkThemes: [GhosttyThemeDefinition] { - TermindTerminalThemeCatalog.allThemes.filter(\.isDark).sorted { $0.name < $1.name } - } - private var lightThemes: [GhosttyThemeDefinition] { - TermindTerminalThemeCatalog.allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name } - } - var body: some View { Group { if let error = app.terminalSettings.storageError { Text(error).foregroundStyle(.orange) } Section("Light") { - Picker("Theme", selection: binding(\.lightTheme)) { - ForEach(lightThemes) { Text($0.name).tag($0.name) } - } - .disabled(lightCustomEnabled) + TerminalThemePicker(selection: binding(\.lightTheme), isDark: false) Toggle("Custom Configuration", isOn: binding(\.lightCustomEnabled)) FormNavigationAction(title: "Edit Custom Configuration") { editConfiguration(.light) } } Section { - Picker("Theme", selection: binding(\.darkTheme)) { ForEach(darkThemes) { Text($0.name).tag($0.name) } } - .disabled(darkCustomEnabled) + TerminalThemePicker(selection: binding(\.darkTheme), isDark: true) Toggle("Custom Configuration", isOn: binding(\.darkCustomEnabled)) FormNavigationAction(title: "Edit Custom Configuration") { editConfiguration(.dark) @@ -730,22 +715,7 @@ private struct TerminalSettings: View { ) } Section("Font") { - TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families) - VStack(alignment: .leading, spacing: Metrics.Space.m) { - Picker("Font Size", selection: $font.sizeFollowsTheme) { - Text("Follow Theme").tag(true) - Text("Custom").tag(false) - } - .pickerStyle(.menu) - if !font.sizeFollowsTheme { - HStack { - Slider(value: $font.size, in: 9...24, step: 1) - .labelsHidden().accessibilityLabel("Font Size") - Text(font.size, format: .number.precision(.fractionLength(0))) - .monospacedDigit().frame(width: 24, alignment: .trailing) - } - } - } + TerminalFontFields(preferences: $font, families: TerminalFontCatalog.shared.families) } Section("Mouse") { Toggle("Copy on Selection", isOn: binding(\.copyOnSelect)) @@ -777,12 +747,6 @@ private struct TerminalSettings: View { } .onAppear { TerminalFontCatalog.shared.refresh() - if TermindTerminalThemeCatalog.darkTheme(named: darkTheme) == nil { - app.terminalSettings.set(SettingsKey.defaultTerminalThemeDark, for: \.darkTheme) - } - if TermindTerminalThemeCatalog.lightTheme(named: lightTheme) == nil { - app.terminalSettings.set(SettingsKey.defaultTerminalThemeLight, for: \.lightTheme) - } } .onChange(of: scenePhase) { _, phase in if phase == .active { TerminalFontCatalog.shared.refresh() } diff --git a/TFM/Platform/TerminalFontCatalog.swift b/TFM/Platform/TerminalFontCatalog.swift index 46e02a21..c7726182 100644 --- a/TFM/Platform/TerminalFontCatalog.swift +++ b/TFM/Platform/TerminalFontCatalog.swift @@ -10,7 +10,10 @@ final class TerminalFontCatalog { func refresh() { families = NSFontManager.shared.availableFontFamilies.filter { family in - NSFontManager.shared.font(withFamily: family, traits: [], weight: 5, size: 12)?.isFixedPitch == true + NSFontManager.shared.availableMembers(ofFontFamily: family)?.contains { member in + guard let fontName = member.first as? String else { return false } + return NSFont(name: fontName, size: 12)?.isFixedPitch == true + } == true }.sorted { $0.localizedStandardCompare($1) == .orderedAscending } } } diff --git a/TFMTests/TerminalFontRenderingTests.swift b/TFMTests/TerminalFontRenderingTests.swift index 12876975..5b9faa71 100644 --- a/TFMTests/TerminalFontRenderingTests.swift +++ b/TFMTests/TerminalFontRenderingTests.swift @@ -1,3 +1,4 @@ +import AppKit import GhosttyTerminal import TermindDomain import TermindPresentation @@ -44,9 +45,31 @@ struct TerminalFontRenderingTests { #expect(state.theme.dark.rendered.hasSuffix("font-family = Courier")) } - @Test func installedMonospacedFamiliesIncludeMenlo() { + @Test func fontCatalogIncludesEveryLoadableMonospacedFamilyInLocalizedOrder() { TerminalFontCatalog.shared.refresh() - #expect(TerminalFontCatalog.shared.families.contains("Menlo")) - #expect(!TerminalFontCatalog.shared.families.contains("Helvetica")) + let catalogFamilies = TerminalFontCatalog.shared.families + let manager = NSFontManager.shared + + let fixedPitchFamilies = manager.availableFontFamilies.filter { family in + NSFontDescriptor(fontAttributes: [.family: family]) + .matchingFontDescriptors(withMandatoryKeys: [.family]) + .contains { NSFont(descriptor: $0, size: 14)?.isFixedPitch == true } + } + .sorted { $0.localizedStandardCompare($1) == .orderedAscending } + + #expect(!catalogFamilies.isEmpty) + #expect(catalogFamilies == fixedPitchFamilies) + + let proportionalFamily = manager.availableFontFamilies.first { family in + let fonts = NSFontDescriptor(fontAttributes: [.family: family]) + .matchingFontDescriptors(withMandatoryKeys: [.family]) + .compactMap { NSFont(descriptor: $0, size: 14) } + return !fonts.isEmpty && fonts.allSatisfy { !$0.isFixedPitch } + } + #expect(proportionalFamily != nil) + if let proportionalFamily { + #expect(!catalogFamilies.contains(proportionalFamily)) + } + } } diff --git a/TFMTests/TerminalThemeTests.swift b/TFMTests/TerminalThemeTests.swift index 27261603..16b8ef27 100644 --- a/TFMTests/TerminalThemeTests.swift +++ b/TFMTests/TerminalThemeTests.swift @@ -1,3 +1,5 @@ +import AppKit +import SwiftUI import TermindApplication import TermindDomain import TermindInfrastructure @@ -9,6 +11,31 @@ import Testing @testable import Termind struct TerminalThemeTests { + @Test + func appearanceOptionsAreFilteredAndSortedByName() { + let lightNames = TermindTerminalThemeCatalog.lightThemes.map(\.name) + let darkNames = TermindTerminalThemeCatalog.darkThemes.map(\.name) + + #expect(lightNames == lightNames.sorted()) + #expect(darkNames == darkNames.sorted()) + #expect(TermindTerminalThemeCatalog.lightThemes.allSatisfy { !$0.isDark }) + #expect(TermindTerminalThemeCatalog.darkThemes.allSatisfy { $0.isDark }) + } + + @Test + func resolverFallsBackForMissingOrWrongAppearancePreferences() { + let preferences = TerminalPreferences( + darkTheme: SettingsKey.defaultTerminalThemeLight, + lightTheme: "Missing Light Theme" + ) + + let light = TermindTerminalThemeCatalog.resolvedTheme(named: preferences.lightTheme, isDark: false) + let dark = TermindTerminalThemeCatalog.resolvedTheme(named: preferences.darkTheme, isDark: true) + + #expect(light.name == SettingsKey.defaultTerminalThemeLight) + #expect(dark.name == SettingsKey.defaultTerminalThemeDark) + } + @Test func unavailableThemesFallBackForBothAppearances() { let light = TermindTerminalThemeCatalog.background( @@ -89,4 +116,90 @@ struct TerminalThemeTests { #expect(background == TermindTerminalPalette.termindDark.background) } + + @Test + func changingBaseThemeWithCustomOverlayPreservesOverrideAndChangesUncoveredColors() throws { + let first = TermindTerminalThemeCatalog.defaultDark + let second = try #require( + TermindTerminalThemeCatalog.darkThemes.first { + $0.name != first.name && $0.foreground != first.foreground + }) + + let firstConfiguration = TermindTerminalThemeCatalog.terminalConfiguration( + themeName: first.name, + customEnabled: true, + customText: "background = 010203", + isDark: true + ) + let secondConfiguration = TermindTerminalThemeCatalog.terminalConfiguration( + themeName: second.name, + customEnabled: true, + customText: "background = 010203", + isDark: true + ) + + #expect(firstConfiguration.rendered.hasSuffix("background = 010203")) + #expect(secondConfiguration.rendered.hasSuffix("background = 010203")) + #expect(firstConfiguration.rendered != secondConfiguration.rendered) + } + + @Test @MainActor + func presentingTerminalSettingsDoesNotRewriteUnavailableThemes() async throws { + let initial = TerminalPreferences( + darkTheme: SettingsKey.defaultTerminalThemeLight, + lightTheme: "Missing Light Theme" + ) + let persistence = RecordingTerminalPreferencesPersistence(initial: initial) + let terminalSettings = TerminalSettingsModel(persistence: persistence) + let app = AppModel(hosts: HostLibraryModel(), terminalSettings: terminalSettings) + let windowModel = WindowModel(workspaces: []) + windowModel.openSettings(route: .category(.terminal)) + let appearance = AppearanceProbe() + defer { app.shutdownForTermination() } + + let view = SettingsPageView() + .environment(app) + .environment(windowModel) + .frame(width: 640, height: 1100) + .onAppear { appearance.count += 1 } + let controller = NSHostingController(rootView: view) + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 640, height: 1100), + styleMask: [.borderless], backing: .buffered, defer: false + ) + window.isReleasedWhenClosed = false + window.contentViewController = controller + defer { + window.contentViewController = nil + window.close() + } + + controller.view.layoutSubtreeIfNeeded() + for _ in 0..<10 where appearance.count == 0 { + try await Task.sleep(for: .milliseconds(10)) + controller.view.layoutSubtreeIfNeeded() + } + + #expect(appearance.count > 0) + #expect(windowModel.settingsRoute == .category(.terminal)) + #expect(terminalSettings.preferences == initial) + #expect(persistence.saveCount == 0) + } +} + +@MainActor private final class RecordingTerminalPreferencesPersistence: TerminalPreferencesPersisting { + let initial: TerminalPreferences + var saveCount = 0 + + init(initial: TerminalPreferences) { + self.initial = initial + } + + func loadTerminal() throws -> TerminalPreferences { initial } + + func saveTerminal(_ value: TerminalPreferences) throws { saveCount += 1 } +} + +@MainActor private final class AppearanceProbe { + var count = 0 } From 771118ecb0a71aede21a318b18d9dde0a61206cf Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 09:01:04 +0800 Subject: [PATCH 02/30] Document terminal appearance preference boundaries --- openwiki/.claims/architecture/desktop-ui.json | 58 +++---- .../architecture/engineering-invariants.json | 146 ++++++++++++++---- openwiki/.claims/architecture/mobile-ui.json | 32 ++-- openwiki/.claims/architecture/module-map.json | 14 +- .../architecture/native-app-directories.json | 32 ++-- .../architecture/platform-runtime.json | 60 ++++--- .../shared-module-directories.json | 30 ++-- .../architecture/system-boundaries.json | 32 ++-- .../concepts/persistence-and-secrets.json | 66 +++++--- .../concepts/ui-design-principles.json | 102 +++++++++++- .../integrations/mcp-server-and-bridge.json | 6 +- .../cloudkit-release-readiness.json | 38 +++-- openwiki/.claims/quickstart.json | 10 +- .../behavioral-boundaries-and-fixtures.json | 38 ++--- .../workflows/settings-and-cloud-sync.json | 84 +++++----- .../workflows/ssh-session-lifecycle.json | 32 ++-- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 134 ++++++++-------- openwiki/architecture/desktop-ui.md | 2 +- .../architecture/engineering-invariants.md | 63 ++++++-- openwiki/architecture/mobile-ui.md | 8 +- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 8 +- openwiki/architecture/platform-runtime.md | 14 +- .../architecture/shared-module-directories.md | 8 +- openwiki/architecture/system-boundaries.md | 10 +- openwiki/concepts/persistence-and-secrets.md | 28 ++-- openwiki/concepts/ui-design-principles.md | 44 +++++- .../integrations/mcp-server-and-bridge.md | 2 +- .../operations/cloudkit-release-readiness.md | 14 +- openwiki/quickstart.md | 2 +- .../behavioral-boundaries-and-fixtures.md | 10 +- openwiki/workflows/settings-and-cloud-sync.md | 16 +- openwiki/workflows/ssh-session-lifecycle.md | 14 +- 34 files changed, 759 insertions(+), 404 deletions(-) diff --git a/openwiki/.claims/architecture/desktop-ui.json b/openwiki/.claims/architecture/desktop-ui.json index e861012e..5d97d851 100644 --- a/openwiki/.claims/architecture/desktop-ui.json +++ b/openwiki/.claims/architecture/desktop-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:e54867652d0a9b222655bb9e8dd43735a96cf9bf358c9a9f589e5c05b3b2a3b0", + "pageVersion": "sha256:fad06b683f4e91f808959ed85ce962b1c86d64a67078b39c7ba20f8aa12ad35b", "claims": [ { "id": "claim_5a53c5a47b7445f7a7b6f8865297a805", @@ -85,8 +85,8 @@ "statement": "Vault list/editor children retain identity across the split breakpoint; a covered list keeps its cached size and editor transitions animate position and reserved space rather than resizing the form.", "evidence": [ { - "resource": "repo://TFM/Features/Vault/VaultWorkspaceView.swift#L15-L99", - "version": "repo-lines-v1:sha256:e7b1d3162b4921f3534c97d5e853e4156f5572dca3293ade07c64f0c79e23dac:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiYTQxMjgyYjkyN2Y1MzU3ODhjMzBmOWVhNzYyYWNkZjQ0YzQwYmY5OGM5YzU5OWViOWI2NzJmODgzNjY1MWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZjZTQ1ODEzN2ZkNzk3MGI1MzZkYmI3NDc1MDY3NjUyNTc3ODA3ZmFhNzIwMjY3NDU2N2M3NWU5ZWY5M2IyN2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + "resource": "repo://TFM/Features/Vault/VaultWorkspaceView.swift#L88-L190", + "version": "repo-lines-v1:sha256:a665d654beeccc4e628ca99f55b42a632582346d665de97099bef8a725330cb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkYmJhOTI1YTIzZDJiZDAwZjU2ZjIyMjM1MWM3NGUwMGQxNWRkMDI1N2E1NjMyZmRlYWNmYjA5NjI1YzE0ZDNlIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0" } ] }, @@ -95,12 +95,8 @@ "statement": "ManagedCardPage supplies typed sections, stable item IDs, externally owned selection, capabilities and business views to a reusable renderer; drop geometry yields stable-ID mutation intent while resource-specific actions and presentation remain with the page.", "evidence": [ { - "resource": "repo://TFM/UI/Managed/ManagedCardPage.swift#L166-L270", - "version": "repo-lines-v1:sha256:163099e2bf8642a65bf5f109ed97a94391eccff6d279c4a2878cf40e88d94917:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJiMjA4N2Y3ODgwZGRiMjI0NzdkZWIyNTA3NjgyNTUyMDgyNWU3ZmZkZWQyZmY4MGYxNWYwNjU3Y2VmZWE4OWZjIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2NWE2ZjEwNTViZWI5YmVkMDAwZjFiYzc3N2ZhOWYzOGEwNTZlNmQxYjFkZTg5YWFlYTdjOTgxZDUyZWRkZjdhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkYTQ5YmM5ZDM0MDM0M2IxNTBhNGVjZmEwZTEyYjg0NDE1ODZkMjhiMmQ1OTczZDRhNmU5OTdmZTg1ZDI5YTZmIn0" - }, - { - "resource": "repo://TFM/UI/Managed/ManagedCardPage.swift#L6-L163", - "version": "repo-lines-v1:sha256:d1192ab4a16076d4b949c9aaa5fe7803f9f1f59f5ecc227acc66788726330b69:eyJzZWxlY3RlZExpbmVDb3VudCI6MTU4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2M2Q5YjEwYjUzMDUwMjgxM2M1MGRhMmUwMDNjNWFjM2FmMjdkZjk0MDRkZmE0N2ZlOGM1MDFlMzJjYjRhMmUxIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIxOTUwMmY5ZWEzMWZhNmI4OGNlZTI3OWJkNmVhMDc5YWNhOWZiMzZkYWViZGY2ZmI5NTUxYTdjYzdjNTNiNDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0ODgxZmMxMzdiMjIxOWE3Yjc2ZTc3YmNlODRjMzYyMzAwNDkzY2IzOTIwZmZmMzFjYzBjY2Q0NDQ2NjI2ZTFjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIn0" + "resource": "repo://TFM/UI/Managed/ManagedCardPage.swift#L76-L223", + "version": "repo-lines-v1:sha256:8d6e900fcbca332884d1b6ff34680001cac1f4cba34796e5fcd17076575572fa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhYjFhZWZiMjdjMWRjMTdjMWE4ZjlmYWZhMjYxZGMyNjEyZTIyNGJhMjlhNmNmYjMxMTkyYWQ3NzY4NDQ1YTQ3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlYTRiYWYyYTFlZTkwZWE1MTg1ZmFhOWJlMTZhNmJjNGViYjkzOWI1Y2FlMWUwNDdkNTg5YzljOWQ2Mzc0ZjljIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI1NzJhMzc1MGQ1NmY2N2I3MGNiZDIxZTZiMDRjNzA4MDU1YzU0MDRkZjU0NGU2MWVhNTI5NTU0ZDg3MThmOGNhIn0" } ] }, @@ -151,16 +147,24 @@ "statement": "TFM Welcome projects only this installation's recent connection events and uses their local timestamps, while the History sidebar searches the synchronized all-device timeline and caps displayed matches at 100.", "evidence": [ { - "resource": "repo://TFM/App/AppModel.swift#L874-L891", + "resource": "repo://TFM/App/AppModel.swift#L878-L895", "version": "repo-lines-v1:sha256:21be749246950d4a070db63a1246e321b0b9cbde28bf1bd0c8201b0140ce26b4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjViNDcwYmMzYjEwN2M5MjUwMTcyZGNjMTU3MjczNTUwZTE4YjcwMDYxNGRlYzkwNDFkYzIyZmZmNzE3OWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBkZjRkYjc1MzQ1NWNlYmI5MjcxNjUzYzY2MGMxZjNiYmJlNDFjMzE0YWY5YWVhMDE3NzY4ODY4YWYyODQ3NDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMwYjIxMmYzZGRiODMwZmUzMmM4YmJlNDE1NDRmN2Y4ODUyYTkzNGEwMzI0YmMzNDUzMGMwZDAyZGE5ZDg5NDcifQ" }, { - "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L1194-L1222", - "version": "repo-lines-v1:sha256:298e850411c61f527715f0d5a72d05ea4b2c3c9979e0892826c3d5ba7e4e3869:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRiZDViNWVhNTM2MmI3NTMwMmVlNDlkNDU0ZDk4NGQ3OTJjNWY2MzFkZjlkZDM2NDA4Yzg0OWQ2ZTM0Zjc4NmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2OTE2ODZlNWJmYzdiNWQ4NTZiNzcwMTNmNjkzNWUwNGEyYTdhMmI1NDRmNDQ3MTFiYjQwNGExMDZlY2I4MzIifQ" + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L1224-L1244", + "version": "repo-lines-v1:sha256:92f6ca52d5862b6ac1daf4550d6a55c31cac569e353b5709a09a5a15bb0e08de:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1MjFiZjdjNDZkMTJkOWVhZDU4YzY5ZjMzMWQ1Y2M4NmE4ZDdiOTBjMWM1NTllZThiNDBkNDk5MWRkMzUwMjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2YjQ3YTY5ZTg0Mzk2ZTg1Y2JhOGRhNTM2MjBlMjE2YzNjY2JiODA2ZDdmNjc2MDkzMGFhMjc4MjBiNzhhYjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2OTE2ODZlNWJmYzdiNWQ4NTZiNzcwMTNmNjkzNWUwNGEyYTdhMmI1NDRmNDQ3MTFiYjQwNGExMDZlY2I4MzIifQ" }, { - "resource": "repo://TFM/Features/Workspaces/WorkspaceContentView.swift#L1255-L1317", - "version": "repo-lines-v1:sha256:4cb5889da5eebb5cb29d9c34bf15afc0f1b0054b50dc2181301c80329ed715e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE0YmYwNzJlZGMwNDE1NTAzYTRlNTc1ODg4NGUyODE5ODQ5NzdhM2RjZGM1YTljZDk4ZWQyMzUzNGNkNDA1ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmM2IxYmI3MjYyOTFlNjQ0MTZhM2JlZWU4NDQ0YTA5YTlhY2Y5MzM1YmYwNjIyOTg3NjhkNDNjNTAzNDFiNWUifQ" + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L786-L792", + "version": "repo-lines-v1:sha256:a89d5d90ca621038753469ce401c92c24921c2515141ca78fc72195b90062176:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDM0ZmQzMmQzZjgwNTA3MTc4NmY5MjIwMGZhMDhjOWZjMWRkZmRkZTk4ZDk0MjkyMzZhYTkwNDcwYjA1MTkwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzkzMmI2ZmJjYTcwYzM4M2RmM2Q5OWNhMjEyODM0ZTIzMmE2NzFhYWVjOGFmOTM1YjZiMWUwNjFhZTRhY2U1OCJ9" + }, + { + "resource": "repo://TFM/Features/Workspaces/WorkspaceContentView.swift#L1101-L1108", + "version": "repo-lines-v1:sha256:b06b264b0f53578b0e4d329e0fc601d3227ac60da98df096f5d43c9c7aa9018f:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjk4NmU4YTBlNmIyYzViYWRkZDI5MTZmZDFlN2U2Y2Y3Yjk2ODcxNGI1ZTUzNWNmY2VjMGMzN2JkMGVlYjU2YSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2RlZDU3Y2RmNjI4ZTdiY2ZiN2Q3NjYzMmE4ZDcxNDg3MzRjMDQxYmM2NTAyMGRkODk2YmVmMTg4YTdhMjIxNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTgyYTRjY2RhODM4NTlhOWVmMTc5N2IxNWU4MjRjYTgxOTBhYTA4MDU0ZTliYjM1NTRiNjE3MzVmZDBjZjMzOCJ9" + }, + { + "resource": "repo://TFM/Features/Workspaces/WorkspaceContentView.swift#L1396-L1408", + "version": "repo-lines-v1:sha256:d4bb3faa4a906b5c4dbc0e5efa8939528f74556414d4af6632d39e58b9fecbf2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzYmUyNjAxMjMwZWY0MWRmYmIwMDI2MzNkODA0MjRhMTliMTY5ZGE2YWU4YjgxOTFlYjRlNzM2ZGJmYjBmYWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiMDgwY2M5Y2U3NjNlOGNkYjRhNjdkNmZiMWNkMDRlYTc2OTAwMDIxMzkwZmY4NDE3NTZiYWQ3NmQxM2JlMGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2NjE4NmUwZDBiZDI4NzIwOWQ3ZTU5NzM1ZDUyMjVmYWVmMDAxNjNkM2I1ODAzMDdlNzI0NmZhNzc0MzUxNGUifQ" } ] }, @@ -169,16 +173,16 @@ "statement": "Saved-host SSH, Telnet, and SFTP panes own connection-history events; closing ends the event, moving preserves it, and Serial or unsaved targets are excluded.", "evidence": [ { - "resource": "repo://TFM/App/AppModel.swift#L945-L953", - "version": "repo-lines-v1:sha256:fffac2073372d7b2eeb063bd1cc2e6559c6596ae31ceccc39b07d1f591d0ac73:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDM5MmMwY2IzYWM3ZjdjNzI0ODQ1ZjI5ZjU3YzVhNzYxZDdmZjJlMDcwZWJmNDRiMTRlYThlYjE4ZDg4YzQ0YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2QyZGJkZDIzYWZkZGM4ZTRmNTQ3MzVhZDlmYTMzOGMyODNiMzkyMGVlNzc1ZTJlMjc2YTFiMDRjOWMxYmFmOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWFiNmI1M2RiNzY3ZWI0YzcwODgxZTNkZWIwMTNiNDgyZmEzMzY3Y2EzY2I2ZWIwYjgwMjUxZWFjODIwMzY2ZSJ9" + "resource": "repo://TFM/App/AppModel.swift#L953-L965", + "version": "repo-lines-v1:sha256:48542809fc3ddc57f60b639ebc3b61af929831987e0f0e79e2f0b71e017fb397:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzOTJjMGNiM2FjN2Y3YzcyNDg0NWYyOWY1N2M1YTc2MWQ3ZmYyZTA3MGViZjQ0YjE0ZWE4ZWIxOGQ4OGM0NGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNkMmRiZGQyM2FmZGRjOGU0ZjU0NzM1YWQ5ZmEzMzhjMjgzYjM5MjBlZTc3NWUyZTI3NmExYjA0YzljMWJhZjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVhYjZiNTNkYjc2N2ViNGM3MDg4MWUzZGViMDEzYjQ4MmZhMzM2N2NhM2NiNmViMGI4MDI1MWVhYzgyMDM2NmUifQ" }, { - "resource": "repo://TFM/Features/Workspaces/Pane.swift#L96-L217", - "version": "repo-lines-v1:sha256:9d7af678ba78037ce05e5a7bc18dd9cd229bd01662d9022bd07666edd171a70f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJiNGM1ZjVkYjkyYWQzNjYxNTE4YzlmNDAwZGYzNjk2NmM5OWUzN2MyYmUzMWE1NWIwY2M1Nzk2Yjk0OTg0MGUyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMjQxODI1ZjE1N2M1NWU3ODFjYzlmZTlmMDQ5ZGNiYzRlZWNiMTVhMTM2ZjA3YzFiZWNjZWI0MDJhYmE5ZWFmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3ZGRiYmE2ODgzZGU1MzUxNDViMGMxZDExODgwMGRjZDRiMmMwYzc5MzFjZjQ0MmE0NDAzNWI4OWQ5ZDczNDc4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI2NDBlOWRjODY0MjI4YmY3NzIyOTA0NDljYTZiNzg4YzRlMDZmZTYwNDhiYzM4NDQ3ZTBiOWFlZWFhMTAxNDdmIn0" + "resource": "repo://TFM/Features/Workspaces/Pane.swift#L95-L217", + "version": "repo-lines-v1:sha256:4837c9694150a71de6e1f344875a1ffcad2f5bfe73549bfa684e9c5d9d8c055e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2OTZhYmUwMDhjNjk0NGIwYTMyYmU3MmQzNmNmZjIzZDA3NDJhNDAxMDAyM2NiODZhYTY2MDYwMTBhZjFjNzZkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIyYTNhOTExNGExNjkyMTVlYmYwMWZiYzhjN2E2ZTMzYmU0YzFiMTRiM2ZlOWI1MmI0OWJkODM0ZDQ3YzY3MzRiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2MDE4YTUwOWM5YWMyNTJmZmU2YTJjYTIzZTA3ZGU3MjYzNTVkYjBjMWNmZjE1ZWZhZWUwM2MxOWRiMjE2ZjgwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwNzQ0OWE0ZDJhNTZiNTM2YmI4YjU2YzVkYjJjZDY1YWRlYmQ0OWIyMDdiOTE4YWNlZjlhNTVjYzJkMTI3YzY1In0" }, { - "resource": "repo://TFM/Features/Workspaces/WindowModel.swift#L1019-L1029", - "version": "repo-lines-v1:sha256:a7a3129aa3ad4026cb332d95d6014c9767d2d915c96baa95a47bf2ccd9cc0866:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4MTk0NTE5ZWZhYzIzYzA2NWIzNTcxNGYzMTdmMTc4YmU5MTM4N2U2MTYwNjYwYTI3MmI0NzhmNWE3NTc1YzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhNGQ3Y2MwMDM0NDgyMDI3MzY3OTY4NDA5N2Q0OGVjMGZlNDIzOWRlZWRiZjVlNzg0NDYyOTVkMWI0ZTEyZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1ZmExNDU5MmY0NDQyODUwMzE1MjFjNWE2OGI2YThkNTE5N2VmYWIyMTUzOTY4NTcyY2Q5MzYwMmQzNmM1YTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZjYzk1YzhiYTUxZjFiZDFkMDRkNzRkNmJiNGU4Yjg4NTNjMzZhYzkxYjYyYTdiNjY3OTZiOWRhYWQ0NmZlN2YifQ" + "resource": "repo://TFM/Features/Workspaces/WindowModel.swift#L1008-L1020", + "version": "repo-lines-v1:sha256:1a68a70e3a66bddbc1c3afe159a067f37c88b9246b010bb6bfb09f7002f793d1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4MTk0NTE5ZWZhYzIzYzA2NWIzNTcxNGYzMTdmMTc4YmU5MTM4N2U2MTYwNjYwYTI3MmI0NzhmNWE3NTc1YzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1ZmExNDU5MmY0NDQyODUwMzE1MjFjNWE2OGI2YThkNTE5N2VmYWIyMTUzOTY4NTcyY2Q5MzYwMmQzNmM1YTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY4ODk3YjVkZGFlMzBkNjFkNGRiNTJlMDk3OWY3ZDc1NjFiMjMyMTIyMmQ4OWFmYWUxZWQ2NzJiYjM2MWM3YWEifQ" } ] }, @@ -187,22 +191,18 @@ "statement": "TFM History rows resolve each event's sourceDeviceID through the reusable app-wide device directory, so the displayed device name follows directory updates rather than remaining a hostname captured inside the event.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift#L26-L58", - "version": "repo-lines-v1:sha256:e339de41e16ec84556ec21c593095e36a5d87b9b95f124a5e7de3eda7472100d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4ZmQ0ZmYyODEyNjE3MTNkMjgzZWY3NTE2NGQ5NjU1NmRjMWVjNGMwMzU0NTk3MjM4NGU2N2I0YmJiMzg1YTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwY2MyNjc2ZjViM2QxZWY1NjIzNzQ3MThjMzEwYzYwY2Y5MTU2OTRhMmYzYzU2NDU4ZWQzY2FhYTc5NjRiOGUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2MjRkNDljNzU1ZWZmNTgzOTNmMmM4YmU3Nzg2MjI4ZGRiYTRkM2ViMmUzOTcxNDExYTlkMzE5NjExYmRiNmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxZTU0YWFlOWRkYjRmMmI0Yjg0OGVlZDRlNjhmY2RhZDY0Zjg5MTgxZDZiNGZlYTZhYjUxNTc1NzNkMTJiYTcifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift#L26-L64", + "version": "repo-lines-v1:sha256:b7b6eda5ba7fe38abe088a34de4c4bc3e5875ba98b9ab227daf6c9dcc451ccc4:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4ZmQ0ZmYyODEyNjE3MTNkMjgzZWY3NTE2NGQ5NjU1NmRjMWVjNGMwMzU0NTk3MjM4NGU2N2I0YmJiMzg1YTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1OTFmZGI3Mjk4MmFhMmRkMjUyMjY3ZWZlZDY2NGYxMTg1MWE5NDE3ODA0NjkwYzg0ZTE2ZTllOGQ1ZGQyNGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2MjRkNDljNzU1ZWZmNTgzOTNmMmM4YmU3Nzg2MjI4ZGRiYTRkM2ViMmUzOTcxNDExYTlkMzE5NjExYmRiNmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA2NWQ1NDZhOWQ0NTk5MmIxYzRjNzVjNGFiOTA5MTQzYjYxZDE3ZTQ0NDUyMWM5N2VjYWQwZTBmYzJhNTZhOTIifQ" }, { - "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L1202-L1221", - "version": "repo-lines-v1:sha256:2d4ed81e1357045077c3628f9cbdef7d67d1abaaa7f098b92577b6fb45552571:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1MjFiZjdjNDZkMTJkOWVhZDU4YzY5ZjMzMWQ1Y2M4NmE4ZDdiOTBjMWM1NTllZThiNDBkNDk5MWRkMzUwMjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjODFhZTcyODBiYzIzZGNjMjM5M2YyZjQzNGNlNzA2MWJjNzMzZWY1OWE2NDhhODliM2I0YTdlZTc0NWRiMzgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2YjQ3YTY5ZTg0Mzk2ZTg1Y2JhOGRhNTM2MjBlMjE2YzNjY2JiODA2ZDdmNjc2MDkzMGFhMjc4MjBiNzhhYjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiOWRiYzFmMDQ5YTIwNDhlYWE2NmU3OTVjNDkzODkzYjg2ZDNjZDI5NTEzM2E5ODQwMzRmNjM5MzI2ODMwNjUifQ" - }, - { - "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L1300-L1316", - "version": "repo-lines-v1:sha256:d636624a511397bcc5ac3ec25d058259b4e3c1d8243a878b96340684fd6ab0d4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZiZjJmOTUzMTg2NDUzNDdiNjBmZWY5NDg5Y2E5MmEwNWJkNTcwYWNjYjc1ZWI4YTNkMTY2YzRhNDM3ODhmNzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlZTlmY2QxZjc0MGM5MzBmZDRmYzlkNzgyMjIyNWNiNDI0YTZhZTI3NzRhMDVmYTEyNDI2ZTcxNmMyOGIxMDIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5NTg0Njc2ZWZjMGU1YjI4MWYyNzViYTgwNjYwMzY0ZTRlMTI1MjkzOGZjMGRiMjZiMzVhNjE4MDgzMjM4ZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwZGM1ODY1MTVjMTY1YWViYmJiZjRlZjgzZjA4NDM1ZTU1OTFmYjY5NjQzYmU2NTgzZTdjZWZlM2JhZjk5ZjgifQ" + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L1324-L1344", + "version": "repo-lines-v1:sha256:79ce25ce82f72b1ae985609690b6b009f1cf6b22de7e0e4586239fcd44763b14:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZiZjJmOTUzMTg2NDUzNDdiNjBmZWY5NDg5Y2E5MmEwNWJkNTcwYWNjYjc1ZWI4YTNkMTY2YzRhNDM3ODhmNzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5NTg0Njc2ZWZjMGU1YjI4MWYyNzViYTgwNjYwMzY0ZTRlMTI1MjkzOGZjMGRiMjZiMzVhNjE4MDgzMjM4ZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ3Njk2MzE4ODhkZGU0OWZlYWY5MmZhNzQ2MTQwMzhkMmMzOGU4MmU2NTEzMWM1Y2Y5ZjNiMjdjNmI1YTU2ZWMifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T09:17:11.491Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index 2d0c8ff2..e3d1bf4a 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:39f747ee778af3ed681b7fbcc46facf80f7fe3dc6233760e56c85515641f7ae8", + "pageVersion": "sha256:6f98da9e8f070ebc2a0b91545058eb3aa5a411129c7a18c9b33ee9eb6da8e799", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -12,7 +12,7 @@ }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift", - "version": "repo-file-v1:sha256:93f91b564371034f72c76c52581281fea35eebe77efa124b0f9668b4ed5b4034" + "version": "repo-file-v1:sha256:ca356c025f911101bbaf63a9a34b72736291043ed0fc0daa8dbedec972eac6b1" } ] }, @@ -22,7 +22,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "version": "repo-file-v1:sha256:ad0b99e06c0a0817f6980a6a682f016a27d45d86fd8c378ab1036ae910712cd4" }, { "resource": "repo://TFI/App/TermindIOSApp.swift#L19-L33", @@ -34,7 +34,7 @@ }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift", - "version": "repo-file-v1:sha256:93f91b564371034f72c76c52581281fea35eebe77efa124b0f9668b4ed5b4034" + "version": "repo-file-v1:sha256:ca356c025f911101bbaf63a9a34b72736291043ed0fc0daa8dbedec972eac6b1" } ] }, @@ -44,7 +44,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -52,11 +52,11 @@ }, { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:32faf4e7aced6a7dcb64ac04369a8c95838ed6cdcb896da42bec8ca7ad562dea" + "version": "repo-file-v1:sha256:1ac6f933da6e8e14162cab3e53a64ad3f1b4bd1bb3c55f6f3ead3286054c0cc1" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -128,11 +128,11 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/MetricsSyncCoordinator.swift", @@ -140,7 +140,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift", - "version": "repo-file-v1:sha256:5fe2842bd5872604eb9fc6954c9bd77ab04638cf13b8ad79d77f0452d8030d10" + "version": "repo-file-v1:sha256:824204c18d8d27fabfa814e302beb651599920ba5d6c4b1fa46ea0a8a0daea60" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/VaultSyncCoordinator.swift", @@ -154,7 +154,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift", @@ -162,29 +162,37 @@ }, { "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift", - "version": "repo-file-v1:sha256:da11b540bc3069f5ec53bf886c288e3e1af1271b0812cdd05e35efc408d9d9d8" + "version": "repo-file-v1:sha256:ea46bc5d91b8f0cbbdf562a56e1383dc1d7732ba249b109dd676a54369acf9c6" }, { "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:f4a0aba7ad07392adbc7b5e56e5c6ff67e41cfd3f4ace4cdd9754a5a487ef0ce" + "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" }, { "resource": "repo://TFITests/MobileWorkspaceStoreTests.swift", - "version": "repo-file-v1:sha256:8e3b0cd7aa4223d0ce68f9bf72d378ea2704a4560e1b708fa9633e6230a24e02" + "version": "repo-file-v1:sha256:9953e49389c0c2b390fe6bd51e467de56c31ec1556a13633ae239ac842c9241d" } ] }, { "id": "claim_af851820a38b45619b056c87c69e7131", - "statement": "Local schema V31 additively introduced scoped quarantine and recovery models, while V32 replaces only the live Host and connection-history models, adds the generic device-directory model, removes obsolete Host usage storage, and backfills existing history with the stable installation ID plus a separate initial DeviceRecord without changing the V1 CloudKit envelope format.", + "statement": "V31 adds scoped quarantine and recovery models; released V31 stores migrate directly to V34, which replaces Host, connection-history and Identity models, adds device-directory and Workspace-preference models, and backfills history with the stable installation ID and an initial DeviceRecord without changing independently versioned CloudKit envelopes.", "evidence": [ { - "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift", - "version": "repo-file-v1:sha256:fe75c0ab4a89d01ab803fa20fae886ad659de7d6f093a0107bde1016ed28a87e" + "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift#L160-L192", + "version": "repo-lines-v1:sha256:f51bbf899ad15618d3a8b0aca55e214798373991a6428b2772d528306e84daca:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmODcyNTcyZjMyOGRmM2I3OGFjMDM3NTM1ODg4NGJhM2M5NDlmMWVmMTFjYWEwZGJmMzk2YmU4ZjcwMTkxYjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVmYmQyYzBjMDY1MTgyYjMwMmY5NjUxMjAyYTNhNzcyMTRmZmZmMjE4MWY4ZjQzYzk0M2ZkYmNlMmNkNTlkNjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkODM3YjNkNGQ3MDYyMGNlMzZiMDQ4ODk3NjA5MDQ4NzBjZTQzZDFhNDUyMTNjYmE0NTY4ZWZkODllODNiZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" }, { - "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L770-L895", - "version": "repo-lines-v1:sha256:13d97d80bdac697f0a344b2f92f76de714dd6824005f1c5b2a19eaed7ae1c885:eyJzZWxlY3RlZExpbmVDb3VudCI6MTM3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJmN2QzZTNlY2JlYzc4Y2U2NmVjMjQ5YWRmYzU1NWY4NzVlNGNlODMwYmIyMTg0MWJhZWQwYzA0NWY4ZGE1ODMyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkMWIwNTQ4OTc3Njg1MTE4N2EyMTEwODI0ODZkOGUyYjc1NWQxMTJkMjYxOGNjNjQ0NjE4NDk4ZjdhOTFkNzdjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwY2YzMTVkYmQ1MWNiMGU3NGUzZWYwNTQxMzk5NzZlNjAxNjJlZTVhZmMzYTlkZGE0MzI5NTdlMjgyMWRhMDQ5In0" + "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L769-L806", + "version": "repo-lines-v1:sha256:3642e24e473cceed587af72269b3e7916ab02eeda15108249a54a0b6b3320aa3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdhY2JkOGQ4OWI1YmUyZGFmMmQ0NGVmMDQ4ZGNlMWJmZjY5ZmMxZWRhMmYxNzZiM2NkY2M0MzIyMDAyZjNkODEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMyNzEzMGYzYzFhZWM1ZDRiODIxZjcwODBmYzFkZDUyNzRmNWQ4M2VjYzdmMWRlZjJlNzU2Y2Y5MTIwZDIxN2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ5NTg0Njc2ZWZjMGU1YjI4MWYyNzViYTgwNjYwMzY0ZTRlMTI1MjkzOGZjMGRiMjZiMzVhNjE4MDgzMjM4ZDcifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L876-L898", + "version": "repo-lines-v1:sha256:28eb9309ffd1456c48b43bedfb27a33bd603389a2757cb3ffc05b9a3bace63dd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlmNjhiZTU3Njg0MmM1NWI2ZmNlZGZlMDg0ZTZjY2VlMGE2YTRmYzIwZWMyZDllZDdjOGJjYjRlOTQ2NjYwYmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkMzRhOWRmNzc5Yjk1NjZiMDRhZDJiOWU2Y2RkZTNiMWQ1ZTZjMWE4YzhiOTdhYjdjMjhhN2IxM2EzM2JkNmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxOTQzZTUyMTk4NjA3YTJlNWIzYjQ2ZTg1ZmYxNWZkMmY1ZDJkNGVmODY5MzFjMTk1MzJkZWQzYWI0OWQwNWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNTJkYTk1NjY5ODdhZThlZmYyMGY2NzgzOTZlMzA4Yjk3NmM1M2M4YmQ5OGZiMTFiNWQxYmMxZjlhYmNjMTkifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift#L7-L10", + "version": "repo-lines-v1:sha256:96eb4ef3db249e5fce9723726a499028b6382260c3013b18d1166c0ff5eddb55:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTdlNTVjMWVhYWVlZjJjYzE4ZDdkZjg5ODBjMTJlN2I4MmFlYzhlOThhZWU0MTFkMDdjNDg3YmY2ZmI4MzQxYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGM1MjFkMzdhNWY2N2ViNDg3ZTYyZDYzMTMzYTljM2JlNzljYWM3OGM5MjU0ZDZlOWE2YTE4ZjA2NjNmMWEzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGYwYjQyMGUzMzNlYjRlMzc3ZDUyZWJlZWFhOGQwOGQ2ODdmMDJiMmJkZWJkMGFlZWY2NTc0MDhlODA5NmI1MiJ9" } ] }, @@ -194,15 +202,15 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncPersistenceSafety.swift", @@ -220,33 +228,103 @@ }, { "id": "claim_e0132529187b45d393b5590f72043afe", - "statement": "Connection history synchronizes complete saved-host SSH/Telnet/SFTP events with stable source-device ID, start time and optional end time, while a separate reusable synchronized device directory maps installation IDs to mutable display names; stores deterministically retain 300 events and desktop/mobile all-device History surfaces present at most 100.", + "statement": "Connection history synchronizes complete saved-host SSH/Telnet/SFTP events with stable source-device ID, start time and optional end time, while a separate reusable synchronized device directory maps installation IDs to mutable display names; stores deterministically retain 250 events and all-device History surfaces present at most 100 events on desktop and 50 on mobile.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift", - "version": "repo-file-v1:sha256:d9b5104bf66f06c45f5b038b7e24a14ce0f2bc09bcc5fa0bdc668d12dc7dc7a8" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift#L8-L93", + "version": "repo-lines-v1:sha256:4e3e25850e98df14d26155194259562c056d9719cf7fe5814236b4f8442582b7:eyJzZWxlY3RlZExpbmVDb3VudCI6ODYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5NmFiZTAwOGM2OTQ0YjBhMzJiZTcyZDM2Y2ZmMjNkMDc0MmE0MDEwMDIzY2I4NmFhNjYwNjAxMGFmMWM3NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjOWQyZWNkNWIwZmRhYTQyYmIzNmIwYThiYmY1M2IxYmE4Njg2OGFlOTM5YTNjMDJlYjExNjU3OGIyOTc0ZjUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlZDQwYzIxNWVhY2JiN2E2OTFhNmE3ODZiNDY0N2VjMDI3YzVjMDljOGY0NDA1ZWJmOWI2OTQ3NjMxY2FjYjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg3NjE3NGI1OTA2ODY5YTkwM2NkYjI4NmM3MjM2Njg2NWVjYjk3MTc0NjllMGNhNjA2ZWM0MTYwM2I1NmFhNGUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift", - "version": "repo-file-v1:sha256:368841be82a49dc8d6660dca31216bd0f319fdc76276a9bd1e22b6a8ea4a2cf8" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift#L26-L63", + "version": "repo-lines-v1:sha256:54f874152f8aeb3e95c2589dd19214e5c1a547e96a25929a54fcfb3206101b7c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4ZmQ0ZmYyODEyNjE3MTNkMjgzZWY3NTE2NGQ5NjU1NmRjMWVjNGMwMzU0NTk3MjM4NGU2N2I0YmJiMzg1YTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2MjRkNDljNzU1ZWZmNTgzOTNmMmM4YmU3Nzg2MjI4ZGRiYTRkM2ViMmUzOTcxNDExYTlkMzE5NjExYmRiNmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRkYWZmMDA5NmRhYjdmMzM1M2IxZWQ3N2FkYTgwMDVhZmU1Y2E2Y2E0NTQyN2M1Y2YzOGU2YmI5NDczZjg3YWMifQ" }, { - "resource": "repo://Frameworks/TermindDomain/Connections/ConnectionHistoryEntry.swift", - "version": "repo-file-v1:sha256:edcf5020f1965dcc1bad5d1b9dc1523cced21a62d1a93f3a1fd079dde25fb37f" + "resource": "repo://Frameworks/TermindApplication/HistoryLimits.swift#L1-L3", + "version": "repo-lines-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmMxNDAxZmI0YzIwNTE3YTEyYWUxOWZkMDNlYjE4Y2UwMDg3ODZlMGFmOGQ0OTJjZTNiNjIwZGI3YWJhMmFlNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" }, { - "resource": "repo://Frameworks/TermindDomain/DeviceRecord.swift", - "version": "repo-file-v1:sha256:7bd4537277fda9c7e6e70318290ee3f17ce0271433ebff5c3ac0a28394f1d1d0" + "resource": "repo://TFI/Features/Terminal/MobileTerminalListPresentation.swift", + "version": "repo-file-v1:sha256:18dcb57132115ad0cdabbf30e6a7a4e186c9281038af66c84dbd7cb440bbacff" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "resource": "repo://TFI/Features/Vault/MobileVaultView.swift#L45-L69", + "version": "repo-lines-v1:sha256:7eec61f38c065a2a673d862b815bc84f8f23f7a136eeebe59c0c3e01213ce732:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiYTQxMjgyYjkyN2Y1MzU3ODhjMzBmOWVhNzYyYWNkZjQ0YzQwYmY5OGM5YzU5OWViOWI2NzJmODgzNjY1MWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI0YTM2NWE5MGVjMTFlOTA0MzI3NTJjZjkzNzZlYzVlZDM2YjgwYmE1ZThlNjg1NGZjZjk4OTcwZDMwNDQ0NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiM2U3YjdjMDYyNDhmMTRiYmViMDY5YjUwMTkxOTU1NmNkZTkxMWE2Y2ExMDk3Y2Y2OWVkNmJiZDMyNDM0ODUifQ" + }, + { + "resource": "repo://TFI/Features/Vault/MobileVaultView.swift#L8-L14", + "version": "repo-lines-v1:sha256:fe53b5cd0ec52d914aaf603de261a76a6b27cd2f50ac2a9fc85547f5920d4106:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzkzMjVjMDk1ZDNlNjQyMjk0MDYwYTlkOTNlYzJjY2EzODc4MWRkMzEyYjgwNDA5MjY3MGNkZDUxYjM1OTUzZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2RhMzEyZmUxNTU3MDQzOTMzNGJjOTAwZGI0NDI5NTM2MmYzODlmNWM2ZWQ4ZTQxNWI4MjJjMGY3ZjA0MmM0OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2QyYTQ2MjllMmFlM2EzZTQ1MjZiMWFjMzAxZDJjNzcwMGM5NGZmNzliYTA3NGFjMjFmYzI5MDQ1MmQ2NzdmZCJ9" + }, + { + "resource": "repo://TFITests/MobileConnectionPickerTests.swift#L142-L168", + "version": "repo-lines-v1:sha256:34ffbbbe2fef5d46ef7874361f9eac02fc08dd4fc8e0fd8ebcb72467cb717449:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NjQ5MzRlNTAwMzZlMDJhNjAzMTAwODk3MDdkZDE5YmNmZWFkOGYwYWI2Y2QxMzM5NTk0NzU4ZDhhZDc5OWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjOGQ0ZjUxZGVmNWU1ZmI1M2MyODMzMTE3ODI5ZDA4YjhkN2ZmODQ0MmY1ZTc4MDM3NDAyYTk3YjA5ZDk1OWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzMyNDlmNDI2MmUyOGE5NzRiZTQ0ODI2MjhmZmIyMGE4ZDRkYjZkNWExMWVmOGViYzg2MjRmZWJiZGNiMjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhNzU2NzMzYTg1NmYyMTA1MGQxMTFkNGJlN2QxYmI3ZTg4ZDVlY2RjZDljYmFmYTgzMWYyMjZmODg5OTk1M2YifQ" + }, + { + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L786-L792", + "version": "repo-lines-v1:sha256:a89d5d90ca621038753469ce401c92c24921c2515141ca78fc72195b90062176:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDM0ZmQzMmQzZjgwNTA3MTc4NmY5MjIwMGZhMDhjOWZjMWRkZmRkZTk4ZDk0MjkyMzZhYTkwNDcwYjA1MTkwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzkzMmI2ZmJjYTcwYzM4M2RmM2Q5OWNhMjEyODM0ZTIzMmE2NzFhYWVjOGFmOTM1YjZiMWUwNjFhZTRhY2U1OCJ9" + } + ] + }, + { + "id": "claim_ed2f9d0208b84e0782a6e04379426208", + "statement": "Terminal font family, size and follow-theme-size stay device-local; shared binding-driven controls use an 8–36 editing range with a macOS slider and iOS stepper, while app-owned catalogs include families with any loadable monospaced member and sort using the current locale.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift#L4-L48", + "version": "repo-lines-v1:sha256:d946b0d140f4be2ae38b11c355aefd32af955306e8f2bebbaa199b678bc32420:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2M2Mjg1YTU0ZWFmNWZiY2ZlYmViODY4ZDcwMmFkYzlkOTg1NWRlYjE1ZGE5ZjRmMzUxOGMzMDFlYTE5YjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0Mzc4ODZmYjRiM2M2YmI3YjlhMjk0ZWM0MDhlZGRmYjVlNDA4OTc5MTA1YjQxZjIzMmQxM2I5ZTc5Y2VlMGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQwOTI0MjQ5ODI3MzE0ZDkzMGUzMWYxMjlkZjg1NWJmYTg0MDFiMWI0NGZkNzc3MmEyZjA0Zjg3ZDE4MWIxMzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUwY2ZjNWYyNWEwNDY4MDhhNTBiZmRjYzUyNDBiZjY1NWI5NmY3MTEzMTNkYjhlMTM4NmVjMTllZTQwM2EyMDkifQ" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalFontSettings.swift#L5-L26", + "version": "repo-lines-v1:sha256:fc9a9007589065d414dbba46a453c433655d9de41d7ef11bed251844ff58a524:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmMGJkZmVhMzIwMDQ4NzRkZjU0MjY4MDg4YjAwODhlNzRiODFiYzliN2JlYzZiMWI3Njk5MzgyZmVlZjgxZGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkZGI4NTc4MDBhNWQ3MjQ3ZmYyM2M1Y2Q1ZTUyYzU0N2JiZjRiYzM2NWI5YTc0MjY5OWVlMzNkYmIyODkyYzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://TFI/Platform/TerminalFontCatalog.swift#L10-L17", + "version": "repo-lines-v1:sha256:b59ed0ca803c29f9017000aa1149e030c31c07dc8f43215635f8eb36e0ef0114:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2I2NjIyZWE1MTRkNWI2NWNmMjNkYWM3OTkyMGUzZTNhYTliYzNlMzg2YzIwOGU0ZDBjOWM0MWQ5OTVlZjc4OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSJ9" + }, + { + "resource": "repo://TFM/Platform/TerminalFontCatalog.swift#L10-L18", + "version": "repo-lines-v1:sha256:17fc005c6ceea378586b85f22ef87ce5db92fab9049f42734e5bdd914a825d76:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2I2NjIyZWE1MTRkNWI2NWNmMjNkYWM3OTkyMGUzZTNhYTliYzNlMzg2YzIwOGU0ZDBjOWM0MWQ5OTVlZjc4OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSJ9" + } + ] + }, + { + "id": "claim_2dafe46c7a9a4c43bfe87e0b565ae762", + "statement": "Unavailable and wrong-appearance saved theme names remain selectable without appearance-time persistence; both apps use shared filtered and sorted options and display a fallback while rendering applies the appearance default, custom commands, and finally local typography.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift#L59-L96", + "version": "repo-lines-v1:sha256:027f0ed8e913904d8d367d68727682c10d4a5667e15c63e5b18e1c6a8eb05f9a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2Zjk4ZDgxZTQxMDU0OTYxY2E4MmVlNDc4ZDY4ZWUyNTUwN2I3NGNmNzBjMzliMjMyOGZkZjQyZmJkOWFjZjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRmYzUzMjljZTc1ZGEyMmZhOWIzODhiMDVmZmQ5NjhhZTg5YmQ1MzRiOTg1ODFlOTFlOTg1OWUyYTE1OTAyZTEifQ" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift#L13-L44", + "version": "repo-lines-v1:sha256:e7f239057b5dd80a08bb8aeb24a13f8e36078fba15d196240b4286eec2aa7de8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhY2YwMDY0OTRlMDNmMjU5MjFhZjEyZDg0ZmQ4YTdhZWVjZTA2OWRhOWM4NDFjMTAzODIzNmIzMTVmZDllOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3YjdmYTg3ZjIyZTQ5Mjg5NzU3ZGJhOTQ0MWE5Y2MwMDcwODdjYzEzMDQ0OWZhNjQyNDhlMzIzYTNiMjVmNWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://TFI/Features/Settings/MobileTerminalSettingsView.swift#L50-L68", + "version": "repo-lines-v1:sha256:87ce0945f50c6927408568200b7041f7967d904453c8f769964f1220a4c35e46:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmM2UzN2Y2NzljZDY2NzAyYjcxZjNmOThkN2IyYjYyZTAwMGEzM2FkYjQ4OTE1NDdjNjZhOTJkNDEyNGIxNzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhNzI3YmI3Zjk2M2NkYjUxODYzYzkyNmEyNGFmN2QxM2Y2NzllNjZjNTYyYTA1OGY1ZDJlMGVlZjA1MmI0NTIifQ" + }, + { + "resource": "repo://TFMTests/TerminalThemeTests.swift#L145-L187", + "version": "repo-lines-v1:sha256:511024300ee294432e3aac81e2781a321cd890a6abf22746371ab22aca03e96e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhZDAxNjAyODg0NTdmMGYyZmVlMzM3MGUwNDBjOWEyYTU1ZjI4ZWI1ZGUyODkwZTcyMmRkMWJkMGEyYzllYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM1NDExNGUzNjA0MDA4NjlmMTk5ZjFmMWY0ZTc5M2FjMTNhNWY5MzE0YjljM2M0NmI0ZjE4MTRjMDY2MGExZDcifQ" + } + ] + }, + { + "id": "claim_84b57fb4a0114cb19748b09b00dba859", + "statement": "Command history retains at most the shared 250 entries per Host.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift#L10-L17", + "version": "repo-lines-v1:sha256:bda4934e36d6c75bc28eec6899615eea9d97749ea5af0204b2b7da84ddde6aa8:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmQ3NjkxNDdhNDEyYTMzY2YxYWQxYTdmMmM4MTY3M2NmZjhmMDJmYTRlNDgyZDE3M2IzMjE4ZWQzNWU0NzVmYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOTQ3ZjQxNTQ2M2I3ZDg3MjA4ZWY5NWY0NGRlMmU5ZTZmMDg3OGUxZjI1YjM0YzU4NzE2ODhjOGYwMGNiNWU5OSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODdkYTQzYmEwMGY4ZmQzZDAxMmU3YTE4OTJhYWRlNzliZjhlY2FiYzMyZjhiZDA3OTg5NjNiZjcyNDMxYTllMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDE2YjYxZjhjOGQ0NTU2OGM0YTUwYTQwYTgxYzM4NjRlMzBhZDI3NzUwMDdlYmY1MWExNThkMjI2ZDI5YjJiYSJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/HistoryLimits.swift#L1-L3", + "version": "repo-lines-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmMxNDAxZmI0YzIwNTE3YTEyYWUxOWZkMDNlYjE4Y2UwMDg3ODZlMGFmOGQ0OTJjZTNiNjIwZGI3YWJhMmFlNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:59:31.979Z" } } diff --git a/openwiki/.claims/architecture/mobile-ui.json b/openwiki/.claims/architecture/mobile-ui.json index 1425c5c8..b2a4b991 100644 --- a/openwiki/.claims/architecture/mobile-ui.json +++ b/openwiki/.claims/architecture/mobile-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:e71b16d155c8189cf4bc29a8e15efcfc015a8b5ea0a52154a4842e5584633c5d", + "pageVersion": "sha256:769919ef95916ccc91f27ef2666730600005e79c052893c2ae098644ac389a07", "claims": [ { "id": "claim_fbb919f62a2d4ab88aafcc4c1186731b", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -48,7 +48,7 @@ }, { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceListView.swift", - "version": "repo-file-v1:sha256:88d9fcebce0e42f5177fc9e7052e38651020d19534d9ac6d5668a27114ca59c6" + "version": "repo-file-v1:sha256:f3209667a7bc50005bac10a013a881940663e225a9844359d831eb71873a33cd" } ] }, @@ -108,7 +108,7 @@ }, { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceView.swift#L136-L251", - "version": "repo-lines-v1:sha256:2a363f15c40691f5935bc1fe91ef1f52f62b6f8acb05876e99064f6871e6dab3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhNjJmNmU2M2U0MjE3OTlkODZiZTU1OWVmNzFhNDAzMzJjYzM2M2JhNmM2NzlhYTk1Mjg5Njc5YjZjMTVmOGFlIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI1YjNmYzQwMDRkNTU5YWM3ZWE5MjVlZTljOGYzMTA4MmUwZTFkODI2Nzc4OGM0MTBjMjQ1MzEyY2MxMjZjZGZhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI4NjE1ZjdkYWRmOGM3ODMxMmJkMTE4MmIxODgyNzQ5NmQ4ZmM2YTY3MWFiM2Y4MWM1YzcwOWY4NTBkNTE3YWRkIn0" + "version": "repo-lines-v1:sha256:0c59640ed6d27d6c81930878a17245c46e01668167c58d2d07ebae792ed531c5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhNjJmNmU2M2U0MjE3OTlkODZiZTU1OWVmNzFhNDAzMzJjYzM2M2JhNmM2NzlhYTk1Mjg5Njc5YjZjMTVmOGFlIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI1YjNmYzQwMDRkNTU5YWM3ZWE5MjVlZTljOGYzMTA4MmUwZTFkODI2Nzc4OGM0MTBjMjQ1MzEyY2MxMjZjZGZhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI4NjE1ZjdkYWRmOGM3ODMxMmJkMTE4MmIxODgyNzQ5NmQ4ZmM2YTY3MWFiM2Y4MWM1YzcwOWY4NTBkNTE3YWRkIn0" } ] }, @@ -194,19 +194,23 @@ }, { "id": "claim_691b837e633448ff9d3dd27ecf34c11e", - "statement": "TFI Workspaces Recents uses only local-device events and aggregates to five Hosts, Home Screen quick actions publish at most three host-only items, and the Vault History page presents at most 100 all-device events.", + "statement": "TFI Workspaces Recents uses only local-device events and aggregates to five Hosts, Home Screen quick actions publish at most three host-only items, and the Vault History page presents at most 50 all-device events.", "evidence": [ { "resource": "repo://TFI/Features/Vault/MobileVaultView.swift", - "version": "repo-file-v1:sha256:cd7a3b0fcbc217401dd3273c3a3463c4feb7f55a26f3985a0a15daefa1d178f4" + "version": "repo-file-v1:sha256:ccb0a1c9c7db91c0e853149a638c43c633f8a0fe41fca90a0e39e3566df7bce8" }, { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceListView.swift", - "version": "repo-file-v1:sha256:88d9fcebce0e42f5177fc9e7052e38651020d19534d9ac6d5668a27114ca59c6" + "version": "repo-file-v1:sha256:f3209667a7bc50005bac10a013a881940663e225a9844359d831eb71873a33cd" }, { "resource": "repo://TFI/Platform/MobileHomeScreenQuickActions.swift", "version": "repo-file-v1:sha256:876e36822c6894079fa98043df42e98d7044a2b7658eacb3a4e049075d41cf88" + }, + { + "resource": "repo://TFITests/MobileConnectionPickerTests.swift#L142-L168", + "version": "repo-lines-v1:sha256:34ffbbbe2fef5d46ef7874361f9eac02fc08dd4fc8e0fd8ebcb72467cb717449:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NjQ5MzRlNTAwMzZlMDJhNjAzMTAwODk3MDdkZDE5YmNmZWFkOGYwYWI2Y2QxMzM5NTk0NzU4ZDhhZDc5OWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjOGQ0ZjUxZGVmNWU1ZmI1M2MyODMzMTE3ODI5ZDA4YjhkN2ZmODQ0MmY1ZTc4MDM3NDAyYTk3YjA5ZDk1OWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzMyNDlmNDI2MmUyOGE5NzRiZTQ0ODI2MjhmZmIyMGE4ZDRkYjZkNWExMWVmOGViYzg2MjRmZWJiZGNiMjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhNzU2NzMzYTg1NmYyMTA1MGQxMTFkNGJlN2QxYmI3ZTg4ZDVlY2RjZDljYmFmYTgzMWYyMjZmODg5OTk1M2YifQ" } ] }, @@ -216,7 +220,7 @@ "evidence": [ { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceListView.swift", - "version": "repo-file-v1:sha256:88d9fcebce0e42f5177fc9e7052e38651020d19534d9ac6d5668a27114ca59c6" + "version": "repo-file-v1:sha256:f3209667a7bc50005bac10a013a881940663e225a9844359d831eb71873a33cd" } ] }, @@ -226,15 +230,15 @@ "evidence": [ { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceListView.swift", - "version": "repo-file-v1:sha256:88d9fcebce0e42f5177fc9e7052e38651020d19534d9ac6d5668a27114ca59c6" + "version": "repo-file-v1:sha256:f3209667a7bc50005bac10a013a881940663e225a9844359d831eb71873a33cd" }, { "resource": "repo://TFI/Features/Workspaces/MobileWorkspaceView.swift", - "version": "repo-file-v1:sha256:4ded58e76f48b96d9fb4c8de99a641cd5966b35460360605359f3210c2f4bfb3" + "version": "repo-file-v1:sha256:5bbad71afa88faa5c9d4351c6f05d4efee6ae1baa952172f64a55293091ae9e7" }, { "resource": "repo://TFITests/MobileWorkspaceMenuTests.swift", - "version": "repo-file-v1:sha256:664f7213a7c9950ddc18baee1ca457b7ef08aead40deab681ccfc1e7c34e8e8b" + "version": "repo-file-v1:sha256:498e60b8b8587bd1257b77ce6a1b7bdbf4bb346da467f9b39eb8d12cc0314c7a" } ] }, @@ -244,7 +248,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFITests/MobileAdaptiveLayoutTests.swift", @@ -252,13 +256,13 @@ }, { "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:f4a0aba7ad07392adbc7b5e56e5c6ff67e41cfd3f4ace4cdd9754a5a487ef0ce" + "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:59:31.979Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 51d8c4ca..49074100 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:0d41246b831e3a59c548887a3cec7e8cb0546108c0300d36813d3f2264fb8e02", + "pageVersion": "sha256:8c5b708036c7d089f4821e24e31a00e1684e44d78bf3a8182b9df0800ffe7f00", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -32,7 +32,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -40,11 +40,11 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -140,17 +140,17 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index 191aa022..7813df32 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:43a03b1d5306dd21b5102c03ec20ee848a7787150f88ab226cb8587c37d7322f", + "pageVersion": "sha256:411c153b0239360e9fc0a413c9ff012607f8aa71dfd42cf349bcfc83eb2fb690", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -16,11 +16,11 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -82,7 +82,7 @@ "evidence": [ { "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift", - "version": "repo-file-v1:sha256:da11b540bc3069f5ec53bf886c288e3e1af1271b0812cdd05e35efc408d9d9d8" + "version": "repo-file-v1:sha256:ea46bc5d91b8f0cbbdf562a56e1383dc1d7732ba249b109dd676a54369acf9c6" }, { "resource": "repo://TFM/Infrastructure/Database/SwiftDataToolAuditRepository.swift#L8-L39", @@ -112,7 +112,7 @@ }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift", - "version": "repo-file-v1:sha256:f6683637b8bfb3975c568b706682d4f07f99fb65cdb432a8fad1e2b53e227ef8" + "version": "repo-file-v1:sha256:f452636d57e54e873af2d27b0a25492e6ae8f2cb2eff8543ae001d09c5a6b265" }, { "resource": "repo://TFM/Features/Shell/WindowRoute.swift", @@ -156,11 +156,11 @@ }, { "resource": "repo://TFI/Features/Settings/MobileSettingsView.swift", - "version": "repo-file-v1:sha256:657a98d0add321476c81dfed4ac37ece39a7a4131be136aff838df209ec83535" + "version": "repo-file-v1:sha256:b513140ba94ba3c1ad07470e05ae601bd5f0f220bee89fe9e186ac98c6cc4a1f" }, { "resource": "repo://TFI/Features/Vault/MobileVaultView.swift", - "version": "repo-file-v1:sha256:cd7a3b0fcbc217401dd3273c3a3463c4feb7f55a26f3985a0a15daefa1d178f4" + "version": "repo-file-v1:sha256:ccb0a1c9c7db91c0e853149a638c43c633f8a0fe41fca90a0e39e3566df7bce8" }, { "resource": "repo://TFM/Features/SFTP/SFTPChannelModel.swift", @@ -168,7 +168,7 @@ }, { "resource": "repo://TFM/Features/Settings/SettingsPageView.swift", - "version": "repo-file-v1:sha256:eae627b1a3f2bbc042c018d93d8e936e898ed6bef0729083bd6b713458744ab4" + "version": "repo-file-v1:sha256:0c4e6f0881eead2da90bc782396a7c7c069a85a6ede7f2f4fdd23bc6233ecb1a" }, { "resource": "repo://TFM/Features/Vault/VaultView.swift", @@ -212,21 +212,25 @@ }, { "id": "claim_7f2d91654f904de190065beceb9ff84c", - "statement": "Native app composition adapts platform device naming into the shared device directory: both TFM and TFI read DeviceKit's Device.current.safeDescription; TFM refreshes it on app activation and TFI on active scene transitions.", + "statement": "Native app composition adapts platform device naming into the shared device directory: TFM uses the trimmed kern.hostname with DeviceKit fallback and refreshes on application activation; TFI uses DeviceKit's Device.current.safeDescription and refreshes on active scene transitions.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "resource": "repo://TFI/App/MobileAppModel.swift#L580-L589", + "version": "repo-lines-v1:sha256:71bcd9d234f4fe43fb8074f23095acbfa238266403359ce6f85621ad7d170c6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1Nzg1MjI2MjQyOTAxY2NkOTlmMTgzNjZjYTdhOGNhOGEwNTc1YTA0OGNkOGU3NTZlZmI2ZmRmMjc5MTFiNTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQzZGFiZTIzNDEzOTNjYzU1NWUzZjI0MzRlOTFlZTZjYjVjZTNhYTgwNWEwOWVhNjA2YTY1MDI3YzNlZWUzY2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzNDVkYzM2OGMxODNiMzEwOTZlMDYwYWFiODA1NzE2MTY3YTYyNDQ5MTgzMTM1NjJlMTgzNmQ1NDk0ZjIwMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4ZWY4Mjc0MmI5NzUwODBiMTdhNDZjZjVjMDYzOGM0MmY1ZGYxNjE3NTBjM2U0OTY1MGRhYTc1OTQzNjJmMWIifQ" }, { - "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "resource": "repo://TFM/App/TermindApp.swift#L285-L294", + "version": "repo-lines-v1:sha256:4f92aff6ab1ede611953c68748bb9269d0f87191ba08801a24ee1e1968e1fe94:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNjQ3OWNjMjMwNDQwZmQ0MWU4MjI0MjE4Njc5Njk4NTU5ZDI4OTNiZDY3YjQzODBhZmFiODExYjM4YTU1YWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2Y2JmMWEzM2ZlOWQ1MWIwZWY0MDc2MzM5Y2MxMDBhZTIwZThhYmM5Njk2ZTM5ODYwYWNiZjZlNWFkNmU0OGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYwYTMzOTQyMzQ4NzRjMmRkNjYwMzEwZTMxZjZiNDAyYmY4NjM1ZDAwZmI4OWVmN2E1M2VmOTAwZmEwNDlhYjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVhZmNhZTk2NzVmZWU3ZDkxZTJlODNmYmY5MjI4MGQ1NmFjZGQ5MWFjMzFlODg1MzA3YTNhMTcyM2MwYzFjOTkifQ" + }, + { + "resource": "repo://TFM/Platform/MacDeviceName.swift#L5-L29", + "version": "repo-lines-v1:sha256:2e3a802d84fd2b017f0074f76c3ef896d3127453e0142f3a054521e823ba686f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZWIyNmFmMDY4MGI1ZTY3MGVmYjZkZGRhNjRmNGEyMzVmNDhmNTE2N2NlMWY4OTYwMDQ0MDU0MGE1OWRkMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlNGQzYWNkOTllYjdjOGNhNzFkNzZjODJjMTIxOTcxNzU5YjBlOTFmODVlNzhhNWQ1NDJlOWUzNWQ4ODFhODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index f756e327..6cb8c320 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:f73e9e1fc7067538111ec94107bf093f7e34deef9f30a2e1f5ee2f071a2fe06e", + "pageVersion": "sha256:1928bdff8b5655653f6d6eaa66489a0a3c967dfebdf9c9c86245df87bf2011c1", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -16,7 +16,7 @@ }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift", - "version": "repo-file-v1:sha256:93f91b564371034f72c76c52581281fea35eebe77efa124b0f9668b4ed5b4034" + "version": "repo-file-v1:sha256:ca356c025f911101bbaf63a9a34b72736291043ed0fc0daa8dbedec972eac6b1" }, { "resource": "repo://Termind.xcodeproj/project.pbxproj", @@ -30,7 +30,7 @@ "evidence": [ { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] }, @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:32faf4e7aced6a7dcb64ac04369a8c95838ed6cdcb896da42bec8ca7ad562dea" + "version": "repo-file-v1:sha256:1ac6f933da6e8e14162cab3e53a64ad3f1b4bd1bb3c55f6f3ead3286054c0cc1" }, { "resource": "repo://TFM/App/RootView.swift", @@ -48,7 +48,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -116,7 +116,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" } ] }, @@ -144,7 +144,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -152,25 +152,25 @@ }, { "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:f4a0aba7ad07392adbc7b5e56e5c6ff67e41cfd3f4ace4cdd9754a5a487ef0ce" + "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" } ] }, { "id": "claim_f5346484cdd743e09ae538b8754cddd5", - "statement": "Mobile cold launch restores only saved workspace descriptors, not live sessions: it loads valid descriptors, starts with no workspaces, clears live activities, and opens/connects a descriptor only on demand; descriptors exclude runtime command/path state.", + "statement": "Mobile cold launch restores only saved workspace descriptors, not live sessions: it loads valid descriptors, starts with no workspaces, clears live activities, and opens/connects a descriptor only on demand; descriptors contain only workspace ID, Host ID, title, and connection kind, excluding runtime command/path state.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift#L345-L353", - "version": "repo-lines-v1:sha256:1d57ee858a60500f68e9bdbf3fba3cfb6613a303d9c65a290a7bad433bf7fcef:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzUxNjg0ZWYwZTk2OGFjMjk5MjA2MGY0MmEwZWM0MjI1ZDZhYjc5MWI4Mjc0OTNkNTliNTkwNjI2OWJhODNjMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjFhMjgzODcyMjk0ZTkxOGM4MTYwNzVlODNmNGUzY2JiZjBlNDkwMTcyMTU2ZjA2YWNlOTNjYTI2Zjg1NjczNSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZThkYTY5NWUzYTVlMDMxODgzMjMwZWM3NmMxOGY4MzY1ODI2MmZkZDYzZmI3NTlhNGE2YjVjYzAzZWNhNWQyZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWYzYzJiZTljODg4NmMyYzVjMjgyNjM5OTgxMGZlNjIwNjRhZmFlYTI3ZGYxM2Y3NDk4YjgzZDIyZWU4MGM4OSJ9" + "resource": "repo://TFI/App/MobileAppModel.swift#L659-L687", + "version": "repo-lines-v1:sha256:79cc24d90367f9ef59be0ea4dd25af4951788cf734444c57cefc55aa8a746b6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4YjM0MjE2MjYzZjM3NTkzNDUyZGMyNDNlYTQyNTQ3MDJkZTEzYjI0OTliYTQ4ODYxODk2NTI4NGNmNDQzNmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1NWE2YmQ5MDhjMTBjOGM0MzYyZDBkNDAyZDI0Y2JkMDY2MDgyYjNjMjQ0N2YwZTA0MjZhYjEzY2M4NDc2M2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MDlmNTg5MzgzODA2NjM1OTVhOTFhNDhlYjQzNWFhNjI1OGNiZWVlZDc1MjAzMDUxODZiYjBiMGI4MTZjMDIifQ" }, { - "resource": "repo://TFI/App/MobileAppModel.swift#L602-L620", - "version": "repo-lines-v1:sha256:b8f6eda04174d103d8335304283bde8c4fccdd41c3e4862ab8a30d6692d2255f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE4MWU5OGE2NTQ4ZjM3ODk4ZWRhZTdlNTI2MTVhNTVlN2U1MmMzMWRhYzNmODE4N2NhNjBhNGYxZGM0NWU0YTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY4NDg1NDZhYzY0MDljOTA2MmY1NmFhOTZjMjljMWMyZmJhMGQ0YmQ4NTZlOTkyOGNhZjgxNTcyNzk3NmRiNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY1OTRkNzNjNzQ0ZDE4YWVhMDM4MjgzOGE5YWFhYzVmMjJmNjhhNTljNTc4ZjJlMWM1OWIzYTQ0NzA5M2FhY2EifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L737-L755", + "version": "repo-lines-v1:sha256:3449b8dcc3ba4d272c3cfbdc74c82d98c9356a9d9d2ae8a6400f2e62ca150874:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUxODI4YWZmNmM2MDJhZDBjNDkzM2FhYjA1ZmQzZTgzOTVmYmI4ZjY3NTI1OGU4NzBiMmE4MGY5MjhjYzhmZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE5YmZhMTRhYWRlOGE1OGNjNTMwMzA1N2IzMmEyNDk1NjdmNzI3NDFhMTJjZDJjNjRjYzAwMzZjNzlkZGRmOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVhNzhhMmQzMGFkYTcwNjU3OTM0MDdhOGM1ODY5NmMxMmM3NjAyZTc0OTVlM2IwZmE5MDRmMDE3NmI0YzY5NmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYzMmY5MDQ0OGE2NzUwMTI4YzczM2Y4NjgwNGQ0ZTg1N2FlOWMxZGIwYTJhNzFmZWRiM2QzMmRkZTM1MGEzMzYifQ" }, { - "resource": "repo://TFITests/MobileAppTests.swift#L1421-L1439", - "version": "repo-lines-v1:sha256:75e1a0a8bcca7df32e065b7c55332a2b6da3fa6213495d8b124dd90df28b08ac:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkNjFlYTM5MmIwYWViMDc5OTViYjhjZjA0ZjQwMjA2YzcwODVkNjU2YzE1N2NjMDIxZTY2OWMxYTBlMWM5YWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAyZmRmZmY4YjRkNzFkNzdkODhmZDc2NWQ2MjhkMzI5NmRlYjY3NjljMWM3NjFmNDRiMzgzN2Q5OWVkYWE1MWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyZGZmM2RjZTkwMTYzOGNlMjRkNDc0ODNkYmUyYzE5ODQzMmY2ZWQzYzI5NzU2ODI4YWEzYmExYzgxZjk1YTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc4NTMxZTQ5Yjg5YzNjN2IzMWMzZTAzMmE5Yzk4NWIwMzkzYWEzOWM2Y2UxZWE2NjU1Njk2YmNjMmFkYTNjZTAifQ" + "resource": "repo://TFI/Application/Workspaces/MobileWorkspaceDescriptor.swift#L4-L11", + "version": "repo-lines-v1:sha256:77f7c0d7a427983ddaf016443366b4285cf1c2f05a98140967648265613b928f:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGZhYWVjMjQ3MjRjODcwZWYzMDdmZjA5NzBlYzdiMTc5YzZmY2U1MjQzMWQ2ZDY2MzU2YjZmMDVlODVhOTI5ZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTU4ZDRhOTEwNzQwNTlmMGM4ZDIxMzZmOWJmODA4MzQ2MTQ4ZmM4Y2ZiMzU0M2RkY2JjNzUwZTc1NzZlNzI2MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2NmOTkwN2FlMjY4ZTFmOWEwNGU2MDRmMDlmMTA5MjhkMWNlZjgzNTk5MGUyOTllYTk2YWYwMmRkZjRiYWQ4NCJ9" } ] }, @@ -216,35 +216,47 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/Features/Workspaces/Pane.swift", - "version": "repo-file-v1:sha256:b385002c836e3a670278bf487552d97daa0edc44fcfa6ec0d5e77bf8510c272b" + "version": "repo-file-v1:sha256:35b8edb059701d5548d750f48c2591c3d5b820e35e1c4dd6e53ae316debce779" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, { "id": "claim_b19f509c5c4640898fee787a3fa8efaa", - "statement": "Both app composition roots create an app-wide reusable DeviceDirectoryModel with the Vault installation ID and a display name from DeviceKit's Device.current.safeDescription; foreground activation refreshes the directory record without rewriting connection events.", + "statement": "Both app composition roots create an app-wide DeviceDirectoryModel with the Vault installation ID. macOS names it from trimmed kern.hostname with a DeviceKit fallback; iOS uses Device.current.safeDescription directly. Foreground activation refreshes the directory record without rewriting connection events.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "resource": "repo://TFI/App/MobileAppModel.swift#L204-L207", + "version": "repo-lines-v1:sha256:e4c05ddc982049c744719ce0e23ec5e9583f75ad8cdf645293c1d455c1430051:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2M3ZjhiYzVmMjYwMjJmY2QyMGMzM2QzN2EyMTA3MTc5ZWNiYTMzNTAzMzgyNzAxZDg3YTM0MmVjMDQzZjNjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYmU1NDFmMThiZTNlOTczZTBjY2Y3YzQ1ZjljOWRhMmE3ODU2ODYxMDk2NDA3M2EyNGFhOTg0Zjk2MDVmYjJiZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNmE5OTJjNGI5YTNmYzZiMzgwNDU3ZDAyZGU5N2I3ZjJjYTg2NmE4MmEwZjdlNzg2OTc5NzhmZmRkOTExOTgxMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTYyMmI4OWNhYjU0MGMxM2UwNjFiNTdjNjQ5ZDY3ZWMyMTZhNjY0YmJmZGZjNDg3YTAyM2JkZGIxMTYxMGU0MSJ9" }, { - "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "resource": "repo://TFI/App/MobileAppModel.swift#L580-L589", + "version": "repo-lines-v1:sha256:71bcd9d234f4fe43fb8074f23095acbfa238266403359ce6f85621ad7d170c6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1Nzg1MjI2MjQyOTAxY2NkOTlmMTgzNjZjYTdhOGNhOGEwNTc1YTA0OGNkOGU3NTZlZmI2ZmRmMjc5MTFiNTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQzZGFiZTIzNDEzOTNjYzU1NWUzZjI0MzRlOTFlZTZjYjVjZTNhYTgwNWEwOWVhNjA2YTY1MDI3YzNlZWUzY2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzNDVkYzM2OGMxODNiMzEwOTZlMDYwYWFiODA1NzE2MTY3YTYyNDQ5MTgzMTM1NjJlMTgzNmQ1NDk0ZjIwMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4ZWY4Mjc0MmI5NzUwODBiMTdhNDZjZjVjMDYzOGM0MmY1ZGYxNjE3NTBjM2U0OTY1MGRhYTc1OTQzNjJmMWIifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L285-L294", + "version": "repo-lines-v1:sha256:4f92aff6ab1ede611953c68748bb9269d0f87191ba08801a24ee1e1968e1fe94:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNjQ3OWNjMjMwNDQwZmQ0MWU4MjI0MjE4Njc5Njk4NTU5ZDI4OTNiZDY3YjQzODBhZmFiODExYjM4YTU1YWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2Y2JmMWEzM2ZlOWQ1MWIwZWY0MDc2MzM5Y2MxMDBhZTIwZThhYmM5Njk2ZTM5ODYwYWNiZjZlNWFkNmU0OGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYwYTMzOTQyMzQ4NzRjMmRkNjYwMzEwZTMxZjZiNDAyYmY4NjM1ZDAwZmI4OWVmN2E1M2VmOTAwZmEwNDlhYjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVhZmNhZTk2NzVmZWU3ZDkxZTJlODNmYmY5MjI4MGQ1NmFjZGQ5MWFjMzFlODg1MzA3YTNhMTcyM2MwYzFjOTkifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L90-L100", + "version": "repo-lines-v1:sha256:99c03b236bf8dffd11d7dc7a6c3ebbae74a50a0d5ef1bc523799c17d53c040fd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUyM2I4NzQyMzJhYmM3NDhjNTI1NDkzNjk2MmQ0MjliMjM3ZmEwYWFiN2VkZDUzZDY5MzY1NWJhZGZmNzFjY2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhYWU3ZDJkZDk1OTRkNmMzYmViZjE3Y2FlZTgzOTllYWIyMTNlMjM3NGQ2MWZhYjY3MDc5YmVjMTYwMjg2MTIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI2NGY1MjY1NmMwMzEyZGY4Y2IzZDcyN2JiYWFiMTVlZWM4NmE1ZDcwYjJiNGMxNjNkNWM3Zjc1YTNiNTdlZjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1MDFlN2VkMjlmY2YzZTZlMmE3MDI0NDZhNzQzZWRmMWE3ZGQ3MGVlOWY4ODg4ZWYwOGI4NTFjMTc2Yzc4YzQifQ" + }, + { + "resource": "repo://TFM/Platform/MacDeviceName.swift#L5-L29", + "version": "repo-lines-v1:sha256:2e3a802d84fd2b017f0074f76c3ef896d3127453e0142f3a054521e823ba686f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZWIyNmFmMDY4MGI1ZTY3MGVmYjZkZGRhNjRmNGEyMzVmNDhmNTE2N2NlMWY4OTYwMDQ0MDU0MGE1OWRkMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlNGQzYWNkOTllYjdjOGNhNzFkNzZjODJjMTIxOTcxNzU5YjBlOTFmODVlNzhhNWQ1NDJlOWUzNWQ4ODFhODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/architecture/shared-module-directories.json b/openwiki/.claims/architecture/shared-module-directories.json index 932cec2e..ca03951f 100644 --- a/openwiki/.claims/architecture/shared-module-directories.json +++ b/openwiki/.claims/architecture/shared-module-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:8477e980e76c27f6b9eeb9c8b9a93f6fc37ee858ae5819a9da3bd7b97f383ee9", + "pageVersion": "sha256:6ba56baf56fd5439756c528b55b7765f506ed716677142f0298cbad9f4cf2f93", "claims": [ { "id": "claim_6e30bc8cde134c23815d274543d0786c", @@ -74,12 +74,8 @@ }, { "id": "claim_4ed733207e8b495f9fb9f0db86f1ad04", - "statement": "Persistence owns the versioned SwiftData schema/container and persistence adapters, including scoped sync restrictions, recovery checkpoints, V32 connection-history storage, and the independent generic device-directory store; GRDBMemoryStore separately owns a lazily opened SQLite memory database and full-text message index.", + "statement": "Persistence owns the versioned SwiftData schema/container and adapters, including scoped sync restrictions, recovery checkpoints, synchronized connection history and the generic device directory. Current V34 migrates released V31 directly, replaces Host/history and Identity shapes and adds workspace preferences; V32 and V33 were never distributed endpoints, and local schema versions do not change CloudKit envelopes. GRDBMemoryStore separately owns a lazily opened SQLite memory database and full-text message index.", "evidence": [ - { - "resource": "repo://Frameworks/TermindPersistence/AISecretStore.swift", - "version": "repo-file-v1:sha256:39f0b10f3adf7bf4bf751f9e370e221c8613b8924c40063e8865bc75b6bbdafd" - }, { "resource": "repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift", "version": "repo-file-v1:sha256:c028e57ece449c591c32f8ac849fda2f6a82cda00974acc87f832295bb3acc3d" @@ -89,16 +85,20 @@ "version": "repo-file-v1:sha256:5d148d9b678c6512cdf58a6803d31d360869a54ecf3c7cd1176abd1126ed2bb6" }, { - "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", - "version": "repo-file-v1:sha256:91868100b77cc0aca7a6c119f4dfeb7c17fda8e21018788dff6b1b4bb709066c" + "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L769-L793", + "version": "repo-lines-v1:sha256:b7ac9e99d444715cb0911e2477b946c4911b7233b3d39ab58f99844ae268d408:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdhY2JkOGQ4OWI1YmUyZGFmMmQ0NGVmMDQ4ZGNlMWJmZjY5ZmMxZWRhMmYxNzZiM2NkY2M0MzIyMDAyZjNkODEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjYzllZDBkMTRmYjhhM2EzOGI3MDhmZmYzYmFmMWIxM2Q3YzA3YmIxMmNmZDZkMDJlMWJiMjRhZGY3ZTUzZWIifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L876-L898", + "version": "repo-lines-v1:sha256:28eb9309ffd1456c48b43bedfb27a33bd603389a2757cb3ffc05b9a3bace63dd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlmNjhiZTU3Njg0MmM1NWI2ZmNlZGZlMDg0ZTZjY2VlMGE2YTRmYzIwZWMyZDllZDdjOGJjYjRlOTQ2NjYwYmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkMzRhOWRmNzc5Yjk1NjZiMDRhZDJiOWU2Y2RkZTNiMWQ1ZTZjMWE4YzhiOTdhYjdjMjhhN2IxM2EzM2JkNmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxOTQzZTUyMTk4NjA3YTJlNWIzYjQ2ZTg1ZmYxNWZkMmY1ZDJkNGVmODY5MzFjMTk1MzJkZWQzYWI0OWQwNWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNTJkYTk1NjY5ODdhZThlZmYyMGY2NzgzOTZlMzA4Yjk3NmM1M2M4YmQ5OGZiMTFiNWQxYmMxZjlhYmNjMTkifQ" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataDeviceDirectoryRepository.swift", "version": "repo-file-v1:sha256:9a3e7296fd09c4b397c90c7b15c20775a0950a145ec1c506ac431c04fc5ea020" }, { - "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift#L4-L21", + "version": "repo-lines-v1:sha256:7e65b61e0ecd8586bc1e80f16931fdbf2d817b19ded10a5a2402897ced66b541:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMzA4NTlkNjgzODAyNWFiM2Y2NjIxNTU5NWM4N2JiZWI1ZDVlMDlmNTIxMjk3YmY2MzI3NTUwYzgxYzBjYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ1Y2FlOGYwMjAzOGQzMThlOTFlNzA2NTYxZGIyZTE4NzcxMmEwMzYwZDgwNzZjZDg2NGQwMzU0YzFmMTg0OGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY4OTUxYTVlMDA0MTE2ZjBmM2Y1MzVjODgxOTBiNmUwNmU3NGFmZGZlYjU2MTc0MTA0YTIzYzQyZjJiYTNlZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" } ] }, @@ -120,7 +120,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift", - "version": "repo-file-v1:sha256:5fe2842bd5872604eb9fc6954c9bd77ab04638cf13b8ad79d77f0452d8030d10" + "version": "repo-file-v1:sha256:824204c18d8d27fabfa814e302beb651599920ba5d6c4b1fa46ea0a8a0daea60" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/VaultSyncCoordinator.swift", @@ -128,7 +128,7 @@ }, { "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "version": "repo-file-v1:sha256:ad0b99e06c0a0817f6980a6a682f016a27d45d86fd8c378ab1036ae910712cd4" } ] }, @@ -228,11 +228,11 @@ }, { "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift", - "version": "repo-file-v1:sha256:a8ac25cdd81d3d6205d9a1645a307d05b17e4f7690cdb2d8d25cd536d9ca449b" + "version": "repo-file-v1:sha256:c2028ccf99f366328201299e762fa706e35c04c72aa8434b6bb44608e0761ca9" }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift", - "version": "repo-file-v1:sha256:93f91b564371034f72c76c52581281fea35eebe77efa124b0f9668b4ed5b4034" + "version": "repo-file-v1:sha256:ca356c025f911101bbaf63a9a34b72736291043ed0fc0daa8dbedec972eac6b1" } ] }, @@ -291,6 +291,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index 739684f9..9401f70b 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:0b0ea65c8f99eafc07573ee48a92854ed136e4499e1a50382c1e7a9f6a57087b", + "pageVersion": "sha256:db9f09d0c4799f6c8ecc04c8b993af8c45c2c71fbb32827587c3c99c945c3c2b", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -36,7 +36,7 @@ }, { "id": "claim_04df7977ba1a43bfba0b2585bc5f185a", - "statement": "Persistence exclusively owns SwiftData models, versioned schemas, migrations, and ModelContainer construction; its current container uses TermindSchemaV33 and architecture tests reject @Model declarations outside TermindPersistence.", + "statement": "Persistence exclusively owns SwiftData models, versioned schemas, migrations, and ModelContainer construction; its current container uses TermindSchemaV34 and architecture tests reject @Model declarations outside TermindPersistence.", "evidence": [ { "resource": "repo://AGENTS.md", @@ -44,15 +44,15 @@ }, { "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", - "version": "repo-file-v1:sha256:91868100b77cc0aca7a6c119f4dfeb7c17fda8e21018788dff6b1b4bb709066c" + "version": "repo-file-v1:sha256:2decf5319e2a1302f86a30e9dd9066658885034b8edd637368d1a3e2f8025682" }, { "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "version": "repo-file-v1:sha256:ad0b99e06c0a0817f6980a6a682f016a27d45d86fd8c378ab1036ae910712cd4" }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift", - "version": "repo-file-v1:sha256:93f91b564371034f72c76c52581281fea35eebe77efa124b0f9668b4ed5b4034" + "version": "repo-file-v1:sha256:ca356c025f911101bbaf63a9a34b72736291043ed0fc0daa8dbedec972eac6b1" } ] }, @@ -62,11 +62,11 @@ "evidence": [ { "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "version": "repo-file-v1:sha256:ad0b99e06c0a0817f6980a6a682f016a27d45d86fd8c378ab1036ae910712cd4" }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFI/App/TermindIOSApp.swift", @@ -74,7 +74,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] }, @@ -148,11 +148,11 @@ }, { "id": "claim_af53e904e34a4a029b78a9356cc9b56e", - "statement": "Connection-history ownership is layered and independent from device naming: Domain defines events that reference sourceDeviceID, Application defines the history model/repository port, and Persistence owns event storage/journal transactions; the reusable DeviceRecord-to-name directory has its own Domain value, Application model/port, and Persistence repository, while both TFM and TFI supply DeviceKit's Device.current.safeDescription as their app-owned naming adapter.", + "statement": "Connection-history ownership is layered and independent from device naming: Domain defines events that reference sourceDeviceID, Application defines the history model/repository port, and Persistence owns event storage/journal transactions; the reusable DeviceRecord-to-name directory has its own Domain value, Application model/port, and Persistence repository, while TFM supplies trimmed kern.hostname with a DeviceKit fallback and TFI supplies DeviceKit's Device.current.safeDescription directly as their app-owned naming adapters.", "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Repositories/ConnectionHistoryRepository.swift", - "version": "repo-file-v1:sha256:52a0371f6d25d3eaa7692a88d55cb6fe40487800882b96f592f1f1cc8f211059" + "version": "repo-file-v1:sha256:bc820384eb0b3010fde22d9c46b6847f41b982a0df9eb04fe5cbd52cb8ffd4d3" }, { "resource": "repo://Frameworks/TermindApplication/Repositories/DeviceDirectoryRepository.swift", @@ -168,7 +168,7 @@ }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataConnectionHistoryRepository.swift", - "version": "repo-file-v1:sha256:e0468dec12004d9d8552b2f0d7745008a17b0271f20b65c1b425d9b917e0319a" + "version": "repo-file-v1:sha256:4444aea2b60d28d150e05778ddf9fb71bbd535e6773a2043cb6405cf32406c2b" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataDeviceDirectoryRepository.swift", @@ -176,17 +176,21 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + }, + { + "resource": "repo://TFM/Platform/MacDeviceName.swift#L5-L29", + "version": "repo-lines-v1:sha256:2e3a802d84fd2b017f0074f76c3ef896d3127453e0142f3a054521e823ba686f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZWIyNmFmMDY4MGI1ZTY3MGVmYjZkZGRhNjRmNGEyMzVmNDhmNTE2N2NlMWY4OTYwMDQ0MDU0MGE1OWRkMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlNGQzYWNkOTllYjdjOGNhNzFkNzZjODJjMTIxOTcxNzU5YjBlOTFmODVlNzhhNWQ1NDJlOWUzNWQ4ODFhODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index d092f6ea..fa3371ad 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,22 +1,22 @@ { "schemaVersion": 1, - "pageVersion": "sha256:b40dd5b97eab1b2f15226b54a5f25925facd3f23cc4461865676fa87a99e7228", + "pageVersion": "sha256:1235c885f381c01020d2218636f500707c8fc6b3b1e8e4fbd712336ef4deb064", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", - "statement": "TermindPersistence.makeContainer is the single SwiftData container factory: it fixes the current V33 schema and migration plan, disables automatic SwiftData CloudKit storage, rejects an in-memory request with a file URL, and propagates store-open errors.", + "statement": "TermindPersistence.makeContainer is the single SwiftData container factory: it fixes the current V34 schema and migration plan, disables automatic SwiftData CloudKit storage, rejects an in-memory request with a file URL, and propagates store-open errors.", "evidence": [ { "resource": "repo://Frameworks/TermindPersistence/TermindPersistence.swift", - "version": "repo-file-v1:sha256:99027dc9f52c9df892fa1a52d5236e1b2940613ea5bc1bb23fc7b215c05f068e" + "version": "repo-file-v1:sha256:ad0b99e06c0a0817f6980a6a682f016a27d45d86fd8c378ab1036ae910712cd4" }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] }, @@ -26,11 +26,11 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] }, @@ -60,19 +60,19 @@ }, { "id": "claim_724958e5129a4a6a847deb92d7f33b5f", - "statement": "Termind retains adjacent V1-through-V33 migrations and frozen historical shapes: V31 adds scoped sync quarantine/recovery; V32 removes live Host usage, adds connection-history source/lifecycle fields and DeviceRecord backfill; and V33 removes live Identity usage metadata while preserving Identity configuration and Vault journal dates.", + "statement": "Termind freezes historical model shapes and retains released migration endpoints V1–V27, V30, V31 and V34, with direct V27→V30 and V31→V34 stages. V31 adds scoped sync quarantine/recovery; V34 removes live Host/Identity usage columns, adds connection-history origin/lifecycle fields and device backfill, and introduces workspace preferences, preserving Identity configuration and Vault journal dates.", "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift", - "version": "repo-file-v1:sha256:fe75c0ab4a89d01ab803fa20fae886ad659de7d6f093a0107bde1016ed28a87e" + "version": "repo-file-v1:sha256:f3d80ad41d0db18620795edc893bd0998245a3caea8f12d3158bf97b19c395a2" }, { "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", - "version": "repo-file-v1:sha256:91868100b77cc0aca7a6c119f4dfeb7c17fda8e21018788dff6b1b4bb709066c" + "version": "repo-file-v1:sha256:2decf5319e2a1302f86a30e9dd9066658885034b8edd637368d1a3e2f8025682" }, { "resource": "repo://Frameworks/TermindPersistence/TermindSchemaV33IdentityModels.swift", @@ -82,11 +82,11 @@ }, { "id": "claim_e5eace34b0c14e78bff0e37229bf9f6f", - "statement": "Persistence compatibility tests open legacy V1/V14 fixtures, verify reopen and full-schema in-memory behavior, and exercise migrations through V33 that preserve prior values and journal dates while applying new defaults or removing obsolete stored data.", + "statement": "Persistence compatibility tests open legacy V1/V14 fixtures, verify reopen and full-schema in-memory behavior, and exercise migrations through V34 that preserve prior values and journal dates while applying new defaults or removing obsolete stored data.", "evidence": [ { "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift", - "version": "repo-file-v1:sha256:fe75c0ab4a89d01ab803fa20fae886ad659de7d6f093a0107bde1016ed28a87e" + "version": "repo-file-v1:sha256:f3d80ad41d0db18620795edc893bd0998245a3caea8f12d3158bf97b19c395a2" } ] }, @@ -252,19 +252,19 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift", @@ -274,11 +274,23 @@ }, { "id": "claim_8df49fd2e5c34c6da847b1fb49c965d7", - "statement": "Connection-history mutations and their event envelopes or tombstones commit together and retain the newest 300 deterministically; the separate device-directory repository atomically journals reusable ID-to-name DeviceRecord upserts and is not subject to event retention or History clearing, while V32 removes Host lastUsedAt and V33 removes Identity lastUsedAt from the live persisted models.", + "statement": "Connection-history mutations and their envelopes or tombstones commit together and retain the newest 250 deterministically; History presentation caps are 100 desktop and 50 mobile. The independent device directory atomically journals ID-to-name upserts and is unaffected by History clearing or event retention. V34 selects live Host and Identity shapes without lastUsedAt.", "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift#L8-L12", + "version": "repo-lines-v1:sha256:0d6e20e2ad1e8cbb9f182d6d5b0625dcf2da968485f8b23a57cce9d628f0510a:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjk2YWJlMDA4YzY5NDRiMGEzMmJlNzJkMzZjZmYyM2QwNzQyYTQwMTAwMjNjYjg2YWE2NjA2MDEwYWYxYzc2ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzU1NGM0YjE5NTBmNzk1NzdmMjRhNDgyYjVjZTg2MTE3YTliNzIyMGUwNjE3YjczZDUwZmI1NTMzOWIwNWY0MCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2VkNDBjMjE1ZWFjYmI3YTY5MWE2YTc4NmI0NjQ3ZWMwMjdjNWMwOWM4ZjQ0MDVlYmY5YjY5NDc2MzFjYWNiMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGI1ODcyN2I1YmQxMTEzOGFmNjNmZGQ2YjRlZGU3NTc0YTc5ZmYzZDlmYjBiZDc3NWFiMjcxMDUzZTYyYmNkMCJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/HistoryLimits.swift", + "version": "repo-file-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e" + }, + { + "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift#L776-L793", + "version": "repo-lines-v1:sha256:353119c5e7283a9b21241b130634972fdad7d65ef1f02f2395e43fb91013ef96:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcxZjM2MjEyMzM2MGM4MDkzODU4YzIyN2I3ZTEyYmUyMDk1ZWQyODBkZmJkNzdlMDljYTMyMzJmMzlkYmRjZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjYzllZDBkMTRmYjhhM2EzOGI3MDhmZmYzYmFmMWIxM2Q3YzA3YmIxMmNmZDZkMDJlMWJiMjRhZGY3ZTUzZWIifQ" + }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataConnectionHistoryRepository.swift", - "version": "repo-file-v1:sha256:e0468dec12004d9d8552b2f0d7745008a17b0271f20b65c1b425d9b917e0319a" + "version": "repo-file-v1:sha256:4444aea2b60d28d150e05778ddf9fb71bbd535e6773a2043cb6405cf32406c2b" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataDeviceDirectoryRepository.swift", @@ -286,7 +298,11 @@ }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "version": "repo-file-v1:sha256:53e2195732d61b918a7ea9eb8bbe64bd000a402b894f31620d9c5747f08cacbe" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultStore.swift#L225-L235", + "version": "repo-lines-v1:sha256:95e9774f2b6d8d79a9634e030eea1c47d88c4e7c25a580d459192f542ed233f6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMxNzE3Mzk5MzhiMWMzZWMyNDZlY2IxN2JhNzg4ZTlkN2Y5MjRkZGJlYWRmYzFkNTYzNmRjYjA5YjJjMzkxMzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA0MjVjZTM4ZWU1NDFhZjRjYWVjNWM4MzY1NjM2Y2I4NThjMjUwOTFhODYwMzc1M2U4NGVhODUzM2VlZTk2MWQifQ" }, { "resource": "repo://Frameworks/TermindPersistence/TermindSchemaV29HostModels.swift", @@ -295,12 +311,20 @@ { "resource": "repo://Frameworks/TermindPersistence/TermindSchemaV33IdentityModels.swift", "version": "repo-file-v1:sha256:5003174b324bccdcd35d4aad831b07154f339117d344713e9cf850e86833f021" + }, + { + "resource": "repo://TFI/Features/Vault/MobileVaultView.swift#L8-L14", + "version": "repo-lines-v1:sha256:fe53b5cd0ec52d914aaf603de261a76a6b27cd2f50ac2a9fc85547f5920d4106:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzkzMjVjMDk1ZDNlNjQyMjk0MDYwYTlkOTNlYzJjY2EzODc4MWRkMzEyYjgwNDA5MjY3MGNkZDUxYjM1OTUzZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2RhMzEyZmUxNTU3MDQzOTMzNGJjOTAwZGI0NDI5NTM2MmYzODlmNWM2ZWQ4ZTQxNWI4MjJjMGY3ZjA0MmM0OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2QyYTQ2MjllMmFlM2EzZTQ1MjZiMWFjMzAxZDJjNzcwMGM5NGZmNzliYTA3NGFjMjFmYzI5MDQ1MmQ2NzdmZCJ9" + }, + { + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L786-L792", + "version": "repo-lines-v1:sha256:a89d5d90ca621038753469ce401c92c24921c2515141ca78fc72195b90062176:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDM0ZmQzMmQzZjgwNTA3MTc4NmY5MjIwMGZhMDhjOWZjMWRkZmRkZTk4ZDk0MjkyMzZhYTkwNDcwYjA1MTkwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzkzMmI2ZmJjYTcwYzM4M2RmM2Q5OWNhMjEyODM0ZTIzMmE2NzFhYWVjOGFmOTM1YjZiMWUwNjFhZTRhY2U1OCJ9" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/concepts/ui-design-principles.json b/openwiki/.claims/concepts/ui-design-principles.json index 4d0c6bb9..02a51b63 100644 --- a/openwiki/.claims/concepts/ui-design-principles.json +++ b/openwiki/.claims/concepts/ui-design-principles.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3c9dd812481bbfb7ac06fdb0073a97ba6a5d2d2966f90d7619e1c7d1d5fc6319", + "pageVersion": "sha256:2ea45885f28729e3e1c545ea046ac5d552d5ad17465af3efc9beccb6755bc299", "claims": [ { "id": "claim_3a403468456840acbfe0a524b90d5717", @@ -98,7 +98,7 @@ }, { "resource": "repo://TFM/Features/HostEditor/HostEditorView.swift#L8-L100", - "version": "repo-lines-v1:sha256:61f6f4836f9c752b4a98127700de4b5e4e05c6af251cf6e19c01b00df95fdef4:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3Y2Q5ODQ4ZjJkY2Y0M2VjZmQ0MGVlODYxZDNjZTI1OWU5NjQxMDAxZDdlNjIwNDk2N2YwMzE0MDY2NTY2NDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4ODc1MTVlMzgwNGJlM2RiZGU3YjIzNmNiMWE2N2ZhODg3MTNkYWMyNTM3OGU4NjcwN2YyNGVmMDg1MDQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNkYTMxMmZlMTU1NzA0MzkzMzRiYzkwMGRiNDQyOTUzNjJmMzg5ZjVjNmVkOGU0MTViODIyYzBmN2YwNDJjNDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjNTM0OGE3MjlmMjNmOGM1MTFlZjEzYWI4NTk4YjhjMWZmYmYzOTY4MDhlNjk4YWEzOTFkOThlYmUyMDQ3OGMifQ" + "version": "repo-lines-v1:sha256:49673a299a1dad2236e522503b138500311be62416728477bb1efaf1b282477d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5N2NkOTg0OGYyZGNmNDNlY2ZkNDBlZTg2MWQzY2UyNTllOTY0MTAwMWQ3ZTYyMDQ5NjdmMDMxNDA2NjU2NjQ3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwODg3NTE1ZTM4MDRiZTNkYmRlN2IyMzZjYjFhNjdmYTg4NzEzZGFjMjUzNzhlODY3MDdmMjRlZjA4NTA0NDc0IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJjZGEzMTJmZTE1NTcwNDM5MzM0YmM5MDBkYjQ0Mjk1MzYyZjM4OWY1YzZlZDhlNDE1YjgyMmMwZjdmMDQyYzQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyYzUzNDhhNzI5ZjIzZjhjNTExZWYxM2FiODU5OGI4YzFmZmJmMzk2ODA4ZTY5OGFhMzkxZDk4ZWJlMjA0NzhjIn0" } ] }, @@ -189,10 +189,106 @@ "version": "repo-lines-v1:sha256:36c22131a55ec5b65823348af839c0293b5a92ddefbe81767e35d7639cd91675:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUwYTE2OTVjOWVlODY1YjZlODA3Nzc2NWVmZDRhYjFkOGZmN2YzM2ZmY2EzNDExNGIxYTg5Zjk2ZTczOGFkYjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" } ] + }, + { + "id": "claim_f313061b243e4f7fa6f1db50dbba001a", + "statement": "Both apps consume binding-driven TerminalFontFields with the shared 8–36 editing range, using a macOS Slider and iOS Stepper. App-owned font catalogs include any family with a loadable monospaced member, sort in localized order and refresh on settings appearance or scene activation; unavailable family choices remain stored while rendering resolves a copy.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindDomain/Sessions/TerminalFontPreferences.swift", + "version": "repo-file-v1:sha256:c93baa0f2e250a82d0885a552a876132c642e95a26dd4b62753719eefe5f00dc" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift", + "version": "repo-file-v1:sha256:c119424e412062df06cd4a3ca4acfe9f5825e21f042ae615f7c62979f24ddb8e" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalFontPicker.swift", + "version": "repo-file-v1:sha256:50d5bcef66143d4630cd81b0fbfe5f6a2b3a83db89335b55651ef00174fc8230" + }, + { + "resource": "repo://TFI/Features/Settings/MobileTerminalSettingsView.swift#L6-L27", + "version": "repo-lines-v1:sha256:db0f6ac319ebe6e659670cb3fd3961daab53fc6bb6cca98bd301196b4a94cb2c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlZWQ0ODIzYWM5OWFkMjBjNDQ4YTY5NTNiZDAxMDI0ZjhmZTNiZGE5YWE4Nzk4NTI3Yzk3MmVmN2I2NzRiNGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmODMzMmZhOTk2MTExMDJiYjgyOTEzYTZjMDFlYWU5N2Q3OTMyN2JlNTViZTY1ODYxMDM3Y2Y0OGRmYWQ5MjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdjODk1ZjdlZDQ0MWRhOTBjNGEwMzliZjFkNzg2YWYzMjg2YmY2YmZjMjgxZjM3ODQ5MzIzNTczNjk4M2YzYjMifQ" + }, + { + "resource": "repo://TFI/Platform/TerminalFontCatalog.swift", + "version": "repo-file-v1:sha256:458044744f770958bc9ab87d9824ecffe182f5cabfeaecef8156906ae8a9f96b" + }, + { + "resource": "repo://TFM/Features/Settings/SettingsPageView.swift", + "version": "repo-file-v1:sha256:0c4e6f0881eead2da90bc782396a7c7c069a85a6ede7f2f4fdd23bc6233ecb1a" + }, + { + "resource": "repo://TFM/Platform/TerminalFontCatalog.swift", + "version": "repo-file-v1:sha256:c1792c30b77feda7f8f0f61b0c8c632b29e85f9d6debc5bb10a271d4924aee45" + } + ] + }, + { + "id": "claim_1e008bed54f24db1af88acb888b9c932", + "statement": "TerminalFontSettings stores typography locally through AppStorage, separately from synchronized TerminalPreferences; TerminalSettingsModel persists preference edits before publishing and retains existing preferences on a save failure while exposing storageError.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/TerminalSettingsModel.swift", + "version": "repo-file-v1:sha256:eb5cf53c8644858b49d6f711d4743dfe86248ecbb530d2b832506a373091ed08" + }, + { + "resource": "repo://Frameworks/TermindDomain/Sessions/TerminalFontPreferences.swift", + "version": "repo-file-v1:sha256:c93baa0f2e250a82d0885a552a876132c642e95a26dd4b62753719eefe5f00dc" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalFontSettings.swift", + "version": "repo-file-v1:sha256:e59c85ba12335af1a05e03b26a4ddd4d9ecfc6573952dad2b4961831e017437c" + } + ] + }, + { + "id": "claim_4556ccaeb3eb45819c67c7edb0d5f37b", + "statement": "TerminalThemePicker retains missing, empty or wrong-appearance selection values with a localized hint while the renderer chooses an appearance-specific fallback without writing preferences. Rendering applies base theme, custom overlay and local typography in order; the base picker remains editable with overlays. Desktop custom editing autosaves and imports files, while mobile explicitly saves a draft sheet.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift#L60-L96", + "version": "repo-lines-v1:sha256:4913d1b987cc2081c776971d909d256779a51d26723c42bad5ae9e09e5a30e02:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwNTEyZTQ4YzcxZWY3NmY3NDdmZGQxYThmYzZiMjlkMTQxZWM0NTA5ZTllNzMzMmQ4MzNjYjdmOGU5MzIzZjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkxMjg4YmU0Y2U3ZTdmMDAyNTdiODBlMmY2NGI1NGY3MjNiODBjZGYwNmFkZThmYmQ1ODhmYTg0NjkzYjc4ODEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRmYzUzMjljZTc1ZGEyMmZhOWIzODhiMDVmZmQ5NjhhZTg5YmQ1MzRiOTg1ODFlOTFlOTg1OWUyYTE1OTAyZTEifQ" + }, + { + "resource": "repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift", + "version": "repo-file-v1:sha256:1531a49920dd29ec27794a8c0113cd62601cb025e9263e1714942238c2dc106c" + }, + { + "resource": "repo://TFI/Features/Settings/MobileTerminalSettingsView.swift", + "version": "repo-file-v1:sha256:47527034489c9cedc0dc531e2524553e5e6f4bcfaae73233f3ca5833d5299180" + }, + { + "resource": "repo://TFM/Features/Settings/SettingsPageView.swift#L680-L726", + "version": "repo-lines-v1:sha256:49d4b62cae29977d89d76b80a4c2a92de212e127217474382144e30452d744bc:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyZWYyOGVjNTZhMzkyZGI1NDEzNGI2YzBmYzVlN2QyM2QzMDNlNjIxMzFmMDA0ODMzNWE2ZmE0M2JkZTUwYmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwZjY1YWFhMDIxZWI3NTc1YjZmNWIzYjc0OWM1ZTk2N2FlNTFkNDZlOGQ1YjYwN2FlNDJmZTdiZTkyYmMzMjgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjljZWYwZTgzNDcyNDgwMWQwN2I4N2YzZmY3YmYwNTAzZjBmZGRmODlmZTQwNGMxNTUwMGM4MWY0ZGRmOTI1NjIifQ" + }, + { + "resource": "repo://TFM/Features/Settings/TerminalCustomConfigurationEditorSheet.swift#L88-L149", + "version": "repo-lines-v1:sha256:a9895ec2bcad6c2f2ae96c2e28c6012a07a88f8c59aa87e94762f84f17277032:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5NjY2OGNjZjFjZDBlYzYwYjE1YjY2MDEwZTBjZDJiZjJiNjgzNjA4NmFiYzM3MDgyNzY2YTU4NzdkMWM5NDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc5NmRiNmJiZmE2NTc0MWZjMjUzNGZhNWE4NzAyM2JhZTY1NTY1M2NmZGQyYzdmM2Q1YzRkMmU2MTYxYjA2MjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + } + ] + }, + { + "id": "claim_adb6b4c166494ec084899f3ae35c379d", + "statement": "Focused terminal tests verify complete macOS monospaced-family membership, local-font precedence and controller identity, filtered theme catalogs and fallback, overlay/base interaction, and zero writes on presenting the actual desktop settings page; mobile font rendering tests cover English and both Chinese variants.", + "evidence": [ + { + "resource": "repo://TFITests/MobileTerminalFontTests.swift", + "version": "repo-file-v1:sha256:49fe7d9938c969c0d56269a3b89d4497496ce107f76db3e65f9c77d8dd85f050" + }, + { + "resource": "repo://TFMTests/TerminalFontRenderingTests.swift", + "version": "repo-file-v1:sha256:6055de0f0e82e9c220a70f0954052c644cfbebdb07ee51d2b79afea4bdff947f" + }, + { + "resource": "repo://TFMTests/TerminalThemeTests.swift", + "version": "repo-file-v1:sha256:486afc2bf3721fee357282fdd83470fcd029c99f029f17b66f3c4f634305e662" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T09:51:39.958Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/integrations/mcp-server-and-bridge.json b/openwiki/.claims/integrations/mcp-server-and-bridge.json index 4680928b..5d6185d3 100644 --- a/openwiki/.claims/integrations/mcp-server-and-bridge.json +++ b/openwiki/.claims/integrations/mcp-server-and-bridge.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:dd68622974b0364e9ac768703272c0529344c92ea6e1547f962fda0b8f878725", + "pageVersion": "sha256:d5354c90144215080870d9769a26cb62524794d9f08d9222bf13f1dfaab336a0", "claims": [ { "id": "claim_b47979774aa94e7dae517a6894c508d4", @@ -8,7 +8,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:1861792f3f00f64e974298d8d61e0d8b9538bb1e78d2c2f0cd8318359ee36290" + "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" }, { "resource": "repo://TFM/App/MCPServerController.swift", @@ -293,6 +293,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/operations/cloudkit-release-readiness.json b/openwiki/.claims/operations/cloudkit-release-readiness.json index 03ee6cc4..d9522750 100644 --- a/openwiki/.claims/operations/cloudkit-release-readiness.json +++ b/openwiki/.claims/operations/cloudkit-release-readiness.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:392a5cd29488846a61161bf56d2b7a9e5d87640c51cea9de02d4a7f679efe676", + "pageVersion": "sha256:cca8239457462455652c40b3bb1223ac001457c4955f7d3427e24358992cb80d", "claims": [ { "id": "claim_bca4a6f8574343d5b8310421bbdd595d", @@ -30,7 +30,7 @@ }, { "id": "claim_89ac65df647f447c8a7f43a8ef686191", - "statement": "Settings synchronization uses zone `TermindAIConfigurationV1`, subscription `termind-ai-configuration-v1-changes`, record type `AIConfigurationItem`, and a distinct `SFTPPreferencesItem` record type; Metrics uses zone `TermindMetricsV1`, record type `MetricsItem`, and subscription `termind-metrics-v1-changes`.", + "statement": "Settings synchronization uses zone TermindAIConfigurationV1, subscription termind-ai-configuration-v1-changes, and record types AIConfigurationItem, SSHPreferencesItem, SFTPPreferencesItem and WorkspacePreferencesItem; Metrics uses zone TermindMetricsV1, record type MetricsItem and subscription termind-metrics-v1-changes.", "evidence": [ { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/MetricsSyncCoordinator.swift#L13-L16", @@ -38,7 +38,11 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L12-L19", - "version": "repo-lines-v1:sha256:4ed5720bb25292fcd425cb02a2a8ea0ccc81de824e598fc089506ac6106afe74:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmVkNGFjNzk5MjNmZGM4ODg4ZmNlNmRkZTQ3NjNkYTIxMDg1OTliNDJiNzYxMzJlN2Y3ZGMwYzU1MGNlNGMwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGE0MmM2ZjQ3ZTA4YTgyNmIwZjgzNTM2YzViYmEyY2QyMjc2YTM1YzJmMWMzYWMzYTM2MDBkMDVmOTlmNzE2MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDczMGYzMjI3ZDNmYTUzZTU2YTc0YzVjYmQxZmQ3NTJmMmVjZGUyOTI2NTVlZWI1ZjNjYjQ1NTNjMWE1NWIxYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmJkYzg2M2IwOWEwN2RlOWE4ZGM3ZjBmNTdhMDFkY2Y3MGQ1MDNiMzBkZWEzODRiNzE3MzA1OTlmMGFiYmYyZiJ9" + "version": "repo-lines-v1:sha256:680c59cf2510a2133b35051167e7e804d76e49457b8c03cbb6f0510aae90512e:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmVkNGFjNzk5MjNmZGM4ODg4ZmNlNmRkZTQ3NjNkYTIxMDg1OTliNDJiNzYxMzJlN2Y3ZGMwYzU1MGNlNGMwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjRhYzA4ZjU0ZjE3ZTBlMzkzZjZlNzA0ODViOGEwNWRiZTZhZWQ3NGY0Yjk3NmFjY2FmZmEzNDY2MzQ4OGJjZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDczMGYzMjI3ZDNmYTUzZTU2YTc0YzVjYmQxZmQ3NTJmMmVjZGUyOTI2NTVlZWI1ZjNjYjQ1NTNjMWE1NWIxYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2U4YmQ0MGZkZjIxZWZhNjRiNzIxMjdhYTE4ZmVjNDlmM2NmYzVlZTMyYzIwYjg2Zjc1NTQ0NDcwMGNkNmVjMCJ9" + }, + { + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L12-L20", + "version": "repo-lines-v1:sha256:5e0f93b1072206c5f40b2745ae881cd78b3e209652bf916dfa748c68aabd41ce:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmVkNGFjNzk5MjNmZGM4ODg4ZmNlNmRkZTQ3NjNkYTIxMDg1OTliNDJiNzYxMzJlN2Y3ZGMwYzU1MGNlNGMwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGE0MmM2ZjQ3ZTA4YTgyNmIwZjgzNTM2YzViYmEyY2QyMjc2YTM1YzJmMWMzYWMzYTM2MDBkMDVmOTlmNzE2MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDczMGYzMjI3ZDNmYTUzZTU2YTc0YzVjYmQxZmQ3NTJmMmVjZGUyOTI2NTVlZWI1ZjNjYjQ1NTNjMWE1NWIxYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmJkYzg2M2IwOWEwN2RlOWE4ZGM3ZjBmNTdhMDFkY2Y3MGQ1MDNiMzBkZWEzODRiNzE3MzA1OTlmMGFiYmYyZiJ9" } ] }, @@ -108,7 +112,7 @@ }, { "id": "claim_e195235702f84b7188836027fefa15f1", - "statement": "The compatibility safety layer uses local V31 quarantine/recovery entities, and V32 connection-history plus generic device-directory payload additions continue using the existing category zones, VaultItem record type, and V1 CloudKit envelope format.", + "statement": "The compatibility safety layer retains local V31 quarantine/recovery entities in current V34, whose direct V31 migration adds connection-history and generic device-directory shapes. These Vault payloads continue using the existing category zone, VaultItem record type and V1 CloudKit envelope format.", "evidence": [ { "resource": "repo://Frameworks/TermindDomain/Vault/VaultSyncTypes.swift", @@ -120,7 +124,7 @@ }, { "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", - "version": "repo-file-v1:sha256:91868100b77cc0aca7a6c119f4dfeb7c17fda8e21018788dff6b1b4bb709066c" + "version": "repo-file-v1:sha256:2decf5319e2a1302f86a30e9dd9066658885034b8edd637368d1a3e2f8025682" } ] }, @@ -130,7 +134,7 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyFixture.swift", @@ -138,11 +142,11 @@ }, { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" } ] }, @@ -152,7 +156,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift", @@ -174,25 +178,33 @@ }, { "id": "claim_f279371635eb47f6af4ea58a4b6f3bb8", - "statement": "Connection events and reusable device-name records use separate connectionHistory and device payload kinds in the existing VaultItem schema; release validation must cover event end updates, tombstones and newest-300 convergence as well as device-rename propagation and independence from history retention or clearing on signed devices.", + "statement": "Connection events and reusable device-name records use separate connectionHistory and device payload kinds in the existing VaultItem schema; release validation must cover event end updates, tombstones and newest-250 convergence as well as device-rename propagation and independence from history retention or clearing on signed devices.", "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/HistoryLimits.swift", + "version": "repo-file-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e" + }, { "resource": "repo://Frameworks/TermindDomain/Vault/VaultSyncTypes.swift", "version": "repo-file-v1:sha256:5186b2e30bb07fa6c53dd4def6a5cfba285e103690004e7767bbd40f9f3fe18c" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "version": "repo-file-v1:sha256:53e2195732d61b918a7ea9eb8bbe64bd000a402b894f31620d9c5747f08cacbe" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultStore.swift#L225-L235", + "version": "repo-lines-v1:sha256:95e9774f2b6d8d79a9634e030eea1c47d88c4e7c25a580d459192f542ed233f6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMxNzE3Mzk5MzhiMWMzZWMyNDZlY2IxN2JhNzg4ZTlkN2Y5MjRkZGJlYWRmYzFkNTYzNmRjYjA5YjJjMzkxMzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA0MjVjZTM4ZWU1NDFhZjRjYWVjNWM4MzY1NjM2Y2I4NThjMjUwOTFhODYwMzc1M2U4NGVhODUzM2VlZTk2MWQifQ" }, { "resource": "repo://TFMTests/VaultSyncTests.swift", - "version": "repo-file-v1:sha256:43cfca7990751c8b39bc13e19f561fba99edaa0bbc8d96232b456d9418981e7e" + "version": "repo-file-v1:sha256:fb1177900e93b9a2d3f30d4b728e61381d7d1336226ac22610e85779b8d0dadc" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 74a99886..28641f57 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6ef69e5f7c1232777ba9d2db117e7e53939190d8fb556779d50aa2ad21ae5e23", + "pageVersion": "sha256:36dd6088760d21cfecb2ca2fffc6e6ea9aa667c3f14011c61dcbb354b0377150", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -34,11 +34,11 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:005db3b60e96b760cf9e6ebed4cfdd0477f4c15db69205924901e47103c9949a" + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -48,7 +48,7 @@ "evidence": [ { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:38cd92e23d3ede988a474a1bc7ce819702e822216227cc5cb253ce7c1790b3d8" + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" } ] }, @@ -129,6 +129,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json index 607b6479..d1a9589b 100644 --- a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json +++ b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:380061db2e83cfce09c0c64a2a8fbd37156aad0e810d165ecc9eb3c988af5c68", + "pageVersion": "sha256:57c765ca650c5a286d85e4b4740dbd3e17d8d481a2fbdae60324abe952962271", "claims": [ { "id": "claim_b7fbd64158554b8b8ca80a0a613b3bc3", @@ -22,7 +22,7 @@ "evidence": [ { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift#L137-L223", - "version": "repo-lines-v1:sha256:2eca7fb32f0263b431e4dcd591abebf96836b26801aa4b584a47e39f6aba0c41:eyJzZWxlY3RlZExpbmVDb3VudCI6ODcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjOWJmNjE2ZGNmNTM5YTBkNTRhMTM2YzY2NGM1M2EyNTllZmFkOWNmNWFmYTY5MDY5ZmEzOGI1MzE5OTQ0YzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE0N2NhYjY3NzUxMmRjZWQ0YTIyMGVkZGQ4NjNlZDFlNDM1OTM0NmRiYTk4ZDljYjkwZDBmZjk2MjhjM2IzNGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzOWVhMmE5NzQ2MjU5N2MwNmU3ODEyZDZmYWRkNzJkMTNmMTYxMDliYTBkZTgwOWQ3MjRlM2E3NTQ1ZjZhNzkifQ" + "version": "repo-lines-v1:sha256:46de22dba5c1a56c0f11f63a5dc40aca60e3e250d1e6327a2ae912dc2bd4330c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwYzliZjYxNmRjZjUzOWEwZDU0YTEzNmM2NjRjNTNhMjU5ZWZhZDljZjVhZmE2OTA2OWZhMzhiNTMxOTk0NGM1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhNDdjYWI2Nzc1MTJkY2VkNGEyMjBlZGRkODYzZWQxZTQzNTkzNDZkYmE5OGQ5Y2I5MGQwZmY5NjI4YzNiMzRhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwMzllYTJhOTc0NjI1OTdjMDZlNzgxMmQ2ZmFkZDcyZDEzZjE2MTA5YmEwZGU4MDlkNzI0ZTNhNzU0NWY2YTc5In0" }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift#L64-L106", @@ -32,19 +32,15 @@ }, { "id": "claim_b7909d224fe141669ceba8e0e7df3fda", - "statement": "MobileWorkspaceStore is a device-local workspace-intent store: it persists a versioned workspace-descriptor snapshot, migrates legacy arrays with deterministic duplicate-index allocation, and preserves unreadable bytes by disabling saves until a successful load.", + "statement": "MobileWorkspaceStore persists a versioned device-local descriptor snapshot containing only IDs, host references, titles and kinds; migration preserves legacy values while discarding duplicate indices. Invalid, unknown-version or duplicate-ID data prevents saves until a successful load and preserves original bytes.", "evidence": [ { - "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift#L31-L84", - "version": "repo-lines-v1:sha256:4f35fc7eed35df1e05ad4bf4ac39ad8de492fd4362a6d501d9026fc401484a23:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJjYzJhMGUxNmJmYTJmOTQ0NGRjYWExOTQ2NjgyMGVkZmViYjRlOGIxZjM2ZjA2NDI0OGUxNmMzM2ZmNGM5OTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1Njk5ZjYzN2FkMjM5YWIxNjY1ODdiZTI0ZTAyZGVmM2ZjOTA4YTMwNjFiNmZmMmM1ODE3NzNkY2FjOGZlOTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" - }, - { - "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift#L4-L12", - "version": "repo-lines-v1:sha256:84eaf6fe9f99a32f4dd09544034ab1280431f7726607f6c01ba8255bd4034c74:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDE0OTE4NmQ3MjRjYjkzMzkyMmE3ZTQxNDBlNWQzOGI0YjkwZDljNDJmNDFmZjQ0MjMzNmM2NWQ1Y2EwZGVkOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTU4ZDRhOTEwNzQwNTlmMGM4ZDIxMzZmOWJmODA4MzQ2MTQ4ZmM4Y2ZiMzU0M2RkY2JjNzUwZTc1NzZlNzI2MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjQyYTQyNjRhMTAwMzFhM2QyZGE2MDUzOWE4ODMyMzhlZjcyZDA5ZjE3NmMxYTMwOTQ3ZDdkNjdkZWJjYTBhMyJ9" + "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift#L4-L68", + "version": "repo-lines-v1:sha256:b43cb4870d4d5319ae6dd1b68255f083bdf1c62a876c6ea566d2ef6c266f5cc6:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxNDkxODZkNzI0Y2I5MzM5MjJhN2U0MTQwZTVkMzhiNGI5MGQ5YzQyZjQxZmY0NDIzMzZjNjVkNWNhMGRlZDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OGQ0YTkxMDc0MDU5ZjBjOGQyMTM2ZjliZjgwODM0NjE0OGZjOGNmYjM1NDNkZGNiYzc1MGU3NTc2ZTcyNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" }, { "resource": "repo://TFITests/MobileWorkspaceStoreTests.swift#L8-L106", - "version": "repo-lines-v1:sha256:f89327bd5e875c95ecbbc1ceea1775b637daaa5d1539444c0091541e0de689ab:eyJzZWxlY3RlZExpbmVDb3VudCI6OTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0NDQ4MDMyZmY0NWRhMDgxNzExZDlkZDhiNzVkMzc5N2UyZTkyYjhlODIxYTNkNWZmZjhlNzkyNmNjYjY5M2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiYzJiZWEwNjNiMmYzMTM0Yzc1NjY0ZDIwMzI5ODQxN2ZlMmRjZjI3MTE5NTE2MjIwMmRkNGI2MDY2MzFmNjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmMDhmOWE1NGExNjNiYTI3ZTAxYmI2ZGI3OTRmODQ1ZmM2Y2IyMTIyMjg2YWZhZTBlZWU2NTAzMTM5OTAyODUifQ" + "version": "repo-lines-v1:sha256:7480523358d0df14f9f1fa13df60463c7901f03a60ca17c03ca5a51b0eccc1cb:eyJzZWxlY3RlZExpbmVDb3VudCI6OTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0NDQ4MDMyZmY0NWRhMDgxNzExZDlkZDhiNzVkMzc5N2UyZTkyYjhlODIxYTNkNWZmZjhlNzkyNmNjYjY5M2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiYzJiZWEwNjNiMmYzMTM0Yzc1NjY0ZDIwMzI5ODQxN2ZlMmRjZjI3MTE5NTE2MjIwMmRkNGI2MDY2MzFmNjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5MTQwMzFjNGIwOTdmODBlZjJlOTVjZjRjOGE2ZGU3NmZhZjRlNjBjODg1ZjVmZjBhMWQ4YjUzZjA4NTkxNWIifQ" } ] }, @@ -150,11 +146,11 @@ }, { "id": "claim_8d0709b664d94ebb8abcf7d7a20a7418", - "statement": "Persistence compatibility tests open historical SwiftData fixtures through the shared container factory, assert retained records and migration outcomes through V33, and verify independent in-memory containers expose the full schema.", + "statement": "Persistence compatibility tests open historical SwiftData fixtures through the shared container factory, assert retained records and migration outcomes through V34, and verify independent in-memory containers expose the full schema. The direct V31-to-V34 fixture also verifies preserved Identity configuration/journal dates, event-origin backfill and default workspace preferences; V32 and V33 are not distributed endpoints.", "evidence": [ { "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift", - "version": "repo-file-v1:sha256:fe75c0ab4a89d01ab803fa20fae886ad659de7d6f093a0107bde1016ed28a87e" + "version": "repo-file-v1:sha256:f3d80ad41d0db18620795edc893bd0998245a3caea8f12d3158bf97b19c395a2" } ] }, @@ -196,7 +192,7 @@ }, { "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" } ] }, @@ -206,21 +202,21 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" } ] }, { "id": "claim_fd10d6f20c88477691425ceeb1241a6c", - "statement": "Focused connection-history tests cover V31-to-V32 migration, the independent generic device record and rename propagation, synchronized event lifecycle/tombstones, stable local-device filtering, newest-300 storage, 100-event presentation, preferred-protocol quick actions and multilingual mobile rendering while excluding Serial.", + "statement": "Focused connection-history tests cover V31-to-V34 migration, the independent generic device record and rename propagation, synchronized event lifecycle/tombstones, stable local-device filtering, newest-250 storage, 100-event desktop and 50-event mobile presentation, preferred-protocol quick actions and multilingual mobile rendering while excluding Serial.", "evidence": [ { "resource": "repo://FrameworkTests/PersistenceCompatibilityTests.swift", - "version": "repo-file-v1:sha256:fe75c0ab4a89d01ab803fa20fae886ad659de7d6f093a0107bde1016ed28a87e" + "version": "repo-file-v1:sha256:f3d80ad41d0db18620795edc893bd0998245a3caea8f12d3158bf97b19c395a2" }, { "resource": "repo://TFITests/MobileCardRenderingTests.swift", @@ -228,21 +224,21 @@ }, { "resource": "repo://TFITests/MobileConnectionPickerTests.swift", - "version": "repo-file-v1:sha256:130622c416a1e6180c210594ac540fd1213991276c062a4efcba65905daa537e" + "version": "repo-file-v1:sha256:4186a0f0cee0a53fe901f751fbfd84bd30eb7f8557683f5160160c1815757003" }, { "resource": "repo://TFMTests/ConnectionHistoryTests.swift", - "version": "repo-file-v1:sha256:ad26db9ba0c341ffb62052b57f42f82fd1296db1d81a17fedf795752e247e019" + "version": "repo-file-v1:sha256:a7e161c0b2e3a5b47a1b33440a679238d66ae75666621b4fa529b79d860de0d2" }, { "resource": "repo://TFMTests/VaultSyncTests.swift", - "version": "repo-file-v1:sha256:43cfca7990751c8b39bc13e19f561fba99edaa0bbc8d96232b456d9418981e7e" + "version": "repo-file-v1:sha256:fb1177900e93b9a2d3f30d4b728e61381d7d1336226ac22610e85779b8d0dadc" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/workflows/settings-and-cloud-sync.json b/openwiki/.claims/workflows/settings-and-cloud-sync.json index c20fc30c..702eb67f 100644 --- a/openwiki/.claims/workflows/settings-and-cloud-sync.json +++ b/openwiki/.claims/workflows/settings-and-cloud-sync.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:a1336fb6bb6fa0733b3bf551d203c4bfcbfff5097545cea1d5614309428ce74d", + "pageVersion": "sha256:b9df8c5b704668ff3f401eb5ba03c19783905eb26e3754598ec05f2c4840c025", "claims": [ { "id": "claim_dc6281a3ccb7415ea961d1a954248647", @@ -8,7 +8,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L94-L142", - "version": "repo-lines-v1:sha256:1b14a573b159ee87ec3ca9f03819b9c98b6a906cca148017fd259d9044fda525:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI0N2VkOWI3ZDY3ZWZlZjBhZGU4N2Q1OTNiZWU1YzFiYTM1NGU1NWY1YzM5YmIxYWM0ZWZmMDdjOTIyNTYyYjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5OThlYWJiZjM3Y2U2MWY2NWE4OGVjODI0N2NhYTUwMWM5OTBlN2JkMmE0NTAxYTliNmQ1Zjc0M2ZlODQ1YzQifQ" + "version": "repo-lines-v1:sha256:4675ad5fd8f107c0b249593f29e22d4afa4086066d857f8f34184267092f49f8:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI0N2VkOWI3ZDY3ZWZlZjBhZGU4N2Q1OTNiZWU1YzFiYTM1NGU1NWY1YzM5YmIxYWM0ZWZmMDdjOTIyNTYyYjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5OThlYWJiZjM3Y2U2MWY2NWE4OGVjODI0N2NhYTUwMWM5OTBlN2JkMmE0NTAxYTliNmQ1Zjc0M2ZlODQ1YzQifQ" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/MetricsSyncCoordinator.swift#L8-L27", @@ -16,7 +16,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L8-L31", - "version": "repo-lines-v1:sha256:4f6d3393e2e44d20a6cc076dbfbd8c3a6aa993ecb248055a24de0b8484debe79:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlNDhhMTRlZDczN2IwNzhiZDNkZWM5ODJlYzE2NmIxZWM2ODMyZmM2MGMyYmIzZjA1M2IzMWYxMjdlZDUxNzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJmYzcxZGM1ZTM3ZGFhOTNiYjAwMzg3MDNmNWI1MGY5NGM5ZWJmNDMxMzNlZmI2YWIxYzI0NDk5NzU2NjZhYmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhNDEzOTNjODU0MWVjYzQwZTdjMzZiM2Y2MmQ0YTFmMDY5MWRmOTI3NzZkM2E1YzRhYWU2OWQ4OWFhYTZhOWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYxMjE1OWY1MWUzNjExNmQ2OWY5M2MxOWJmMzdkMDEzODdiNjA5YWQxZDhiNjc3ZmMxNDI1MGZmZjI3MzZkNjUifQ" + "version": "repo-lines-v1:sha256:0670f90527ad1c773a838bbf7e938cc535926a5a58051f3fc958d45c69ce6969:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlNDhhMTRlZDczN2IwNzhiZDNkZWM5ODJlYzE2NmIxZWM2ODMyZmM2MGMyYmIzZjA1M2IzMWYxMjdlZDUxNzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJmYzcxZGM1ZTM3ZGFhOTNiYjAwMzg3MDNmNWI1MGY5NGM5ZWJmNDMxMzNlZmI2YWIxYzI0NDk5NzU2NjZhYmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhNDEzOTNjODU0MWVjYzQwZTdjMzZiM2Y2MmQ0YTFmMDY5MWRmOTI3NzZkM2E1YzRhYWU2OWQ4OWFhYTZhOWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYxMjE1OWY1MWUzNjExNmQ2OWY5M2MxOWJmMzdkMDEzODdiNjA5YWQxZDhiNjc3ZmMxNDI1MGZmZjI3MzZkNjUifQ" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/VaultSyncCoordinator.swift#L10-L25", @@ -62,7 +62,7 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L180-L193", @@ -80,19 +80,19 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L222-L237", - "version": "repo-lines-v1:sha256:7b73695cda74b4db008103cff9ac2114815c4c58a9b04a46d988055cae76c4b6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkM2ZkMzBhMTczNWU4NTQzZjQzZTE3OTJlODliMDc2NWJlNjhmM2JlZTNlMGVlNDE5MGMzM2EyNDhiZmY3ZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4OTUwYjYyZTc5ZjEwZWU4MDM4N2VjYmFiMWNlZGMwYWMxZWZmYWUxNDY3Y2NhOWQ1NjJmYWVhZTY3MzNjY2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2OTgwNTAwYWRlYTg1NDAwYzEzNjNiYTY1M2QxNTEyMTI5MjE5YjFlZDg2MzE5YzliOTNiNGRlMzFmYTRkMDcifQ" + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L226-L240", + "version": "repo-lines-v1:sha256:c69e1857f909fd122d2bbb72abe8e12e02489c982429a3de49c2e572c951d57e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkM2ZkMzBhMTczNWU4NTQzZjQzZTE3OTJlODliMDc2NWJlNjhmM2JlZTNlMGVlNDE5MGMzM2EyNDhiZmY3ZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4OTUwYjYyZTc5ZjEwZWU4MDM4N2VjYmFiMWNlZGMwYWMxZWZmYWUxNDY3Y2NhOWQ1NjJmYWVhZTY3MzNjY2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkNWY5ZDZlNjY2NzNjNWNiOWViMThhYWQxMWEzNGRiZjIwYmZiOTNiMTExNjVhZTNhYzM0YzhhYWQ2MDQ1NDIifQ" }, { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L378-L410", - "version": "repo-lines-v1:sha256:92b1614c04bac36e3cbe93778da330303d8c15d3a55f1198d7fe5e245b263e5c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAwYWM5Yjk3MDI3ZDY3ZjA2YWQzNGQwNTM3NTNjNDFmZjQwNDYyYjNiZjg0YzlmODFiZjliN2JhMjgxY2ZhODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1YTVhNDg2OTY2NTU2MzE3OGU5NDRmZTM4ZDkzNmUyNTRhZDNiYTMxYTdiYmI3MTU1YTIyZDI3OTc0OTUyNGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVlOTYzOGM4NmM1MWNmM2NhOWY5MzUzZTEzNWM2MDVkMGM5ZTk3M2M2Y2VjZjNlZDZmOWZlOGY3OTI3NGRlMDUifQ" + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L382-L417", + "version": "repo-lines-v1:sha256:43ebea323a6a9736718db500ca448a6a88196b3e75648f25bafd1b2474d420ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAwYWM5Yjk3MDI3ZDY3ZjA2YWQzNGQwNTM3NTNjNDFmZjQwNDYyYjNiZjg0YzlmODFiZjliN2JhMjgxY2ZhODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1YTVhNDg2OTY2NTU2MzE3OGU5NDRmZTM4ZDkzNmUyNTRhZDNiYTMxYTdiYmI3MTU1YTIyZDI3OTc0OTUyNGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVlOTYzOGM4NmM1MWNmM2NhOWY5MzUzZTEzNWM2MDVkMGM5ZTk3M2M2Y2VjZjNlZDZmOWZlOGY3OTI3NGRlMDUifQ" }, { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L458-L482", - "version": "repo-lines-v1:sha256:0c6c173c282933d930c6947571705d98c723b6edd025f5c3f23a4c85ff105a46:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0NjczYWQwYTc2YWY5ODY3MWJjMjBiZmNiYWU1OTM0NjE2ZjU5NWNjZjhlMTcxMzE5ZGM4NDlkYjYxMjk1OWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5N2FjZTViZGVmZWVhZWVkYjJiMzQ1MzI2NWZjODgwMzRkNmE5OTRiMjZiYjBkYjIxZTA2YTRlZWVmODQxZmMifQ" + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L466-L489", + "version": "repo-lines-v1:sha256:3ea6acba43fa7b647c911bc4f3bf0f423edd99ccb967a7297051919151965273:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBiMjcxZTVkOTIwMWM1YjhiMjJjNTgyMzQxMjMyNzA0OGU1ODcxMmQ4MGUxOTkxZDE5MDE5ZTgyMDJiN2Y0NjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzYzIzNzgwZmYyOWQ5MGJlYWM2NGEwYjcyNDZjN2E5NTFiOGI1MTk4YjNiODg1YTJjZTBlODJiNzgyOGFjYTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5N2FjZTViZGVmZWVhZWVkYjJiMzQ1MzI2NWZjODgwMzRkNmE5OTRiMjZiYjBkYjIxZTA2YTRlZWVmODQxZmMifQ" } ] }, @@ -102,11 +102,11 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift", @@ -114,7 +114,7 @@ }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "version": "repo-file-v1:sha256:53e2195732d61b918a7ea9eb8bbe64bd000a402b894f31620d9c5747f08cacbe" } ] }, @@ -146,7 +146,7 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/MetricsSyncCoordinator.swift#L191-L264", @@ -176,7 +176,7 @@ }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "version": "repo-file-v1:sha256:53e2195732d61b918a7ea9eb8bbe64bd000a402b894f31620d9c5747f08cacbe" }, { "resource": "repo://Frameworks/TermindPersistence/TermindSchemaV33IdentityModels.swift", @@ -226,15 +226,15 @@ }, { "id": "claim_b5a9a81603ab425aa1d9436175848295", - "statement": "Settings uses compatibility record types for SSH and SFTP preferences in its existing zone, allowing older clients that filter record types to ignore newer preferences before decoding.", + "statement": "Settings uses compatibility record types for SSH, SFTP and workspace preferences in its existing zone, allowing older clients that filter record types to ignore newer preferences before decoding.", "evidence": [ { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L11-L20", - "version": "repo-lines-v1:sha256:ba64e9fd7d03ea70c7260531f00825cc1976772f0275d81176a20f25e63564fb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwNjg0Y2I2MGIyOWVkNTViY2FkMWU5MmNlMmExNjhiYjU3NmY5NTZiNjQyZGYwMDUyYjFiNmVjYmE2ZmIwZjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYzZmYyMmRiNDEwNzIxNDkwYzZmOTBiMWE1YmYwMzhlZGE2NmM4NTkzYmVmMTE1NjIyNjVjMzhhMTc4ZjQ5MDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhjMDAzZmE4NDc1OGM2MmQ5OWQzODFmYzE2YjdlZWZkODcwMjg3ZWI4YjU1MTExNGM2NzYwNmU2MDlkNzgyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQzZGMzNDIxOWUwMzNmNGNjOWFhYjJlYzExODAyODFmNDEyNzE2N2Y1ZDAwMmU0NGE4MGExNDFiZTRiODcwMjgifQ" + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L12-L20", + "version": "repo-lines-v1:sha256:5e0f93b1072206c5f40b2745ae881cd78b3e209652bf916dfa748c68aabd41ce:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmVkNGFjNzk5MjNmZGM4ODg4ZmNlNmRkZTQ3NjNkYTIxMDg1OTliNDJiNzYxMzJlN2Y3ZGMwYzU1MGNlNGMwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGE0MmM2ZjQ3ZTA4YTgyNmIwZjgzNTM2YzViYmEyY2QyMjc2YTM1YzJmMWMzYWMzYTM2MDBkMDVmOTlmNzE2MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDczMGYzMjI3ZDNmYTUzZTU2YTc0YzVjYmQxZmQ3NTJmMmVjZGUyOTI2NTVlZWI1ZjNjYjQ1NTNjMWE1NWIxYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmJkYzg2M2IwOWEwN2RlOWE4ZGM3ZjBmNTdhMDFkY2Y3MGQ1MDNiMzBkZWEzODRiNzE3MzA1OTlmMGFiYmYyZiJ9" }, { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L336-L365", - "version": "repo-lines-v1:sha256:e314c414b55b9bc4bb5e4896cfbf869ee20e4644f31b5d436e3193e1245f9fac:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlYTRjZDRlZGI0MDJmNDVmNThmMGJkY2UyZWI0MzVkNTgxOTZiZmYyNjU4MjdlNmE3OWE3YzZjOTZkYmI2Y2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY3ZDA4ZWY2ODk0ODdmOWQxZWYzOWEyNDhjNWViNWY4NmEzMDUwYTZkY2Q2NzgyMDYwZDZmZDJkNTA4MWY1OWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBlMTk0NzllZWYyMjk1MzI3ZjQ1ODc4OGY4OTUwYjg1MGE0NTM2ZjE1NWE1MmUxYmZmYjk3MTIxZWU0MTUzNWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE3ZDdhNTk3NDA1NDc4NDg3NzUzOWZlYjQ3N2FiZDZhMTM0YjVmMmQ2YzYxYTQzMGE0MTMwMTZiMjE5YWRkNGQifQ" + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L327-L367", + "version": "repo-lines-v1:sha256:cb3cf1efea892279977da6e3c3546f8b4ea65c5d15c84334d49d85a9bee5b463:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlYTRjZDRlZGI0MDJmNDVmNThmMGJkY2UyZWI0MzVkNTgxOTZiZmYyNjU4MjdlNmE3OWE3YzZjOTZkYmI2Y2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUwMjFhOGU0MzJkNDI5NTI0NjdkOWZlZDM4ODVhMTVmN2MzNzhkODUwNmJmMjU1NjdhYTE0NDMxYzIwYzM4NjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0NzY1YWExMjBjYzIxZGVhZGVjZDI3NDgzMTEyMWQ2ODM4YzBhNmQzMmU2NDQ3OWNmYzgzMmY1ODE0YTQ3ZjMifQ" } ] }, @@ -244,11 +244,11 @@ "evidence": [ { "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift", - "version": "repo-file-v1:sha256:c319f68b716338d47084e88bb37751052cff7d5d0f04989cb7841c15b65bdb48" + "version": "repo-file-v1:sha256:f1cf26cdf75bc42748c7a938467689d7fb2e55161868d6d7c267d1e7b2537fbe" }, { "resource": "repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift", - "version": "repo-file-v1:sha256:41fe85b84350f855a0bb51655cea0423cf1a3b61003ca3ab0ffac859991aa94b" + "version": "repo-file-v1:sha256:49b60cd6fbd6947443a729053f11b81278f3124c032b68439cf18a2ee2645f32" }, { "resource": "repo://Frameworks/TermindDomain/Vault/CloudSyncSafety.swift", @@ -256,7 +256,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift", @@ -266,15 +266,19 @@ }, { "id": "claim_c9162b2f86af4ec58eae974ae8187925", - "statement": "Outgoing safety checks filter journal eligibility, prune restored queue entries, materialize eligible saves synchronously and recheck the current engine and scoped restriction in the final provider; unreadable state fails closed, and the shared status distinguishes partial restrictions and newer-format update hints from corruption.", + "statement": "Outgoing safety checks filter journal eligibility, prune restored queue entries, materialize eligible saves synchronously and recheck the current engine and scoped restriction in the final provider; unreadable state fails closed, and the shared status distinguishes partial restrictions and newer-format update hints from corruption. Each batch admits at most 250 materialized saves, so invalid or blocked pending entries do not consume that record limit.", "evidence": [ { - "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", - "version": "repo-file-v1:sha256:aee7f41dbc34ca19214dde4c5a8408464fdd96b3efc490df52e23419ced39b30" + "resource": "repo://FrameworkTests/CloudSyncLifecycleTests.swift#L12-L68", + "version": "repo-lines-v1:sha256:2f0748ef490e3e75ff099e0a9251ed22c806e1046f0e3c22a234c330a2803950:eyJzZWxlY3RlZExpbmVDb3VudCI6NTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1OTZlM2M2NGRmMjNiNjQ3NGY1NWFjZmUwYTRhNjNhN2YzN2Y5YWQyYzQ2OWI3ZDYxNWNlYjFhZmUwMmNiYjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViZWUzMmEyZGRiZWI3ODY0NDc4MGQ5ZmEzYzQ5ODJmYzhiMjRjOWE0ODhmNTZhMDZiYTA4NjllNzdmZjEzYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVhMDNmZmI1NmJhYzU5YjEyMDc4ZTBhMTJjNTkxMGYzOTY0YTMyOGZmNWM0MTYyYWE3NGFmZDdjZGU5ODU2MTYifQ" }, { - "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L365-L433", - "version": "repo-lines-v1:sha256:16bd6745f4cbbff8540167833015ae8775bdb3e78a145bafabd20a6800bf8bd7:eyJzZWxlY3RlZExpbmVDb3VudCI6NjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlMGZiNDE4N2YzZjQ4M2M0YWE5N2RiNDdiNjRkMGRjOGJiNDE5MzgwNTc2NmUxY2Y3Y2JmYTkxMTk3N2M1YTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjMTdlMGQ1MjM3Yjk2NmZiNGY4N2Y4Y2ZjMDk1MDExNDZjN2RjOWVlYjY5ZTQ2ZjVhODcxODNlMWYzYmVjMDAifQ" + "resource": "repo://FrameworkTests/CloudSyncSafetyTests.swift", + "version": "repo-file-v1:sha256:603f6e77ab33461d78203254b0746011e57d85070aa095fe60cf525ae9ad9dd7" + }, + { + "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift#L370-L439", + "version": "repo-lines-v1:sha256:8433db060627cfc7183575b225117e728fd2fbb2bd46d6903388c7b3cf57acf8:eyJzZWxlY3RlZExpbmVDb3VudCI6NzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3MTJjMmExNjI3ZmVlMWEwMzViYTVmMjY4ZTk3M2IxYTMwNzM5NjNhODZjY2EwNTFmNTcxYzM1ZGQzNTQyYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc5Y2IwOTk0OGZiMjBhN2Y5ODk3MzQ4N2E5NjA2YmY3MmYyNGEzODY4ZjM0NGRkZWY5MWQxYTFkYWU4NTg3ZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE2YWVjZmZiZWYwMjQ3NTUyMDViYWQxY2VlYjUxOTEwYjRmMWM0NmU1MjAxNTVmYWFkM2YwNThjOTExNDhlMjYifQ" }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncPersistenceSafety.swift", @@ -282,7 +286,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/SettingsSyncCoordinator.swift#L167-L200", - "version": "repo-lines-v1:sha256:ec8e38d535b93a9d23aaa85353501ebfcf104a488b9c11f53cb96df813663a54:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzYTg4YTY4YTRiMDZhMTgwYjJlZjhhMDUwMDZhOTM4YmNkNjBiYWJmNzMxYjg2MzA3Mjc3MmNjY2JlNWI0MDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0ZDljZjU3NDgzNjQ3Y2UzMjdjNTNmMWJmNzg0MWVmYzA0NWQ4NTlhMWM4MzFkOTRlMzE2M2Y5MDBlMGMyOTQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0YjA3Mzk4Njc2OWY1ZGMzYTZmZmZmNWM1ZTQxNDI2ODBmZjQ5YmY3NjMyNTk3MGFlZmQ4NDYyOWMzOThiOGMifQ" + "version": "repo-lines-v1:sha256:1e40224844b50f1b91dcd8b3b79b20e68049568bec8f75a9653eb53bcb1e9476:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyNTcxMGQ2YTE4OWExYjNkY2Y0ZTYzZWE2NmNkZDk0ODQ5NTI1YjZmYzllYTUxMTgyM2I2MWRjNGM0YTBmYWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiMGUyMWRkMWUyZDk4ZDgzZGM4ZWJlZDUzYzE2MWViNWIyZGZjZjdlZTZiZDlkMmQ2ZGY1OTMwZDBhZWIxNWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0Mjg2YjhjMTA3NDYyMmI1YzBhMjEzOGI5YWRlZGYxZWQxYWI0NzNkZjRiNTg3MjY0OGMxNDk3MzViMTYyNWIifQ" }, { "resource": "repo://Frameworks/TermindPresentation/VaultSyncStatusLabel.swift", @@ -300,7 +304,7 @@ }, { "resource": "repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift", - "version": "repo-file-v1:sha256:2c8baddd408651cfaaec231be95b0af95c4b93b8e264d1733cbffbba7bad48f7" + "version": "repo-file-v1:sha256:fb027da6eb8f6d18a40f242b03d9e49c2a8a96540082884a13956b4bded9fc99" }, { "resource": "repo://Frameworks/TermindPersistence/MetricsSyncJournal.swift", @@ -318,15 +322,23 @@ }, { "id": "claim_12e52cd084bd42fb9fc004e10abddf17", - "statement": "Each connection event is a VaultItem connectionHistory key whose local row and journal change commit together; remote events can precede Hosts and deterministic overflow tombstones converge devices to the newest 300. Separately, each reusable DeviceRecord is a VaultItem device key whose name upsert commits with its own journal envelope and is not deleted by event retention or History clearing; neither payload kind requires a new CloudKit schema.", + "statement": "Each connection event is a VaultItem connectionHistory key whose local row and journal change commit together; remote events can precede Hosts and deterministic overflow tombstones converge devices to the newest 250. Separately, each reusable DeviceRecord is a VaultItem device key whose name upsert commits with its own journal envelope and is not deleted by event retention or History clearing; neither payload kind requires a new CloudKit schema. During launch pruning, never-uploaded overflow journal rows are discarded, whereas uploaded overflow becomes tombstones.", "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift#L8-L12", + "version": "repo-lines-v1:sha256:0d6e20e2ad1e8cbb9f182d6d5b0625dcf2da968485f8b23a57cce9d628f0510a:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjk2YWJlMDA4YzY5NDRiMGEzMmJlNzJkMzZjZmYyM2QwNzQyYTQwMTAwMjNjYjg2YWE2NjA2MDEwYWYxYzc2ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzU1NGM0YjE5NTBmNzk1NzdmMjRhNDgyYjVjZTg2MTE3YTliNzIyMGUwNjE3YjczZDUwZmI1NTMzOWIwNWY0MCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2VkNDBjMjE1ZWFjYmI3YTY5MWE2YTc4NmI0NjQ3ZWMwMjdjNWMwOWM4ZjQ0MDVlYmY5YjY5NDc2MzFjYWNiMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGI1ODcyN2I1YmQxMTEzOGFmNjNmZGQ2YjRlZGU3NTc0YTc5ZmYzZDlmYjBiZDc3NWFiMjcxMDUzZTYyYmNkMCJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/HistoryLimits.swift", + "version": "repo-file-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e" + }, { "resource": "repo://Frameworks/TermindDomain/Vault/VaultSyncTypes.swift", "version": "repo-file-v1:sha256:5186b2e30bb07fa6c53dd4def6a5cfba285e103690004e7767bbd40f9f3fe18c" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataConnectionHistoryRepository.swift", - "version": "repo-file-v1:sha256:e0468dec12004d9d8552b2f0d7745008a17b0271f20b65c1b425d9b917e0319a" + "version": "repo-file-v1:sha256:4444aea2b60d28d150e05778ddf9fb71bbd535e6773a2043cb6405cf32406c2b" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataDeviceDirectoryRepository.swift", @@ -334,17 +346,17 @@ }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift", - "version": "repo-file-v1:sha256:bce6af5c5b9287e3c05817bd18bc59bdfb8836b85f92c35005eac37273ed736e" + "version": "repo-file-v1:sha256:53e2195732d61b918a7ea9eb8bbe64bd000a402b894f31620d9c5747f08cacbe" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataVaultStore.swift", - "version": "repo-file-v1:sha256:4208b9910c5530f219ff7148de966e3d47b72671b58d2a9c7a1d38c8f980ac92" + "version": "repo-file-v1:sha256:f7f74f7c300cfaaf1eacdb9f9fb162aa6e9fade3efbb8d6142476d53e4e28b59" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.claims/workflows/ssh-session-lifecycle.json b/openwiki/.claims/workflows/ssh-session-lifecycle.json index d23357a7..50cc14dd 100644 --- a/openwiki/.claims/workflows/ssh-session-lifecycle.json +++ b/openwiki/.claims/workflows/ssh-session-lifecycle.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1c3a728083dd0fd403bf9e9e1cedb2079dfe280c3f037e0de4b6c1a072818a75", + "pageVersion": "sha256:5cad513345c5cd73a1bb915584cf0566e8f1a8f543c5e5dd4da91692e6407352", "claims": [ { "id": "claim_69a9de4ce918417c8a44c6c87c9f6564", @@ -118,27 +118,35 @@ }, { "id": "claim_42c9e8f115d54eed818fdd19063fb088", - "statement": "Desktop restoration does not reconnect live Workspaces, whereas mobile persists only connection-free workspace descriptors and cold launch performs no session or network restoration; mobile background expiration closes transports and foreground can resume existing workspace intent.", + "statement": "Desktop restoration does not reconnect live Workspaces, whereas mobile persists only connection-free descriptors containing workspace ID, Host ID, kind and title; cold launch performs no session or network restoration. Mobile background expiration closes transports and foreground can resume existing workspace intent.", "evidence": [ { - "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift#L1051-L1093", - "version": "repo-lines-v1:sha256:fbbd6aa6c600fb47badb453ae29fadf02d4eee77b793e62a39a4e5284ed2d3f1:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZiMjBmNmMyMmZmMjQ5M2I1OGJmMmNhYjNkMzU5Yzk3OWU5ZTJhMjE1NDIxMjUzNGU4NGFiODM2MWJlZDgyNzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1NDZkMzEzNDZkMjg3Y2Y4YTM3NDhhMmNlZTU4ODE1OTFmNTJlNTVjOTM2MDZhM2IzYmM4MDU2OGQxZWQ0MzIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGMzODIwMjNmZDQ2NDhkYjQzZmZhNjFjNWJmYjQ3ZjliOTZjZTIyZjFmNGI2OGYxZTczZGY3ODkxN2VlMDkifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L659-L687", + "version": "repo-lines-v1:sha256:79cc24d90367f9ef59be0ea4dd25af4951788cf734444c57cefc55aa8a746b6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4YjM0MjE2MjYzZjM3NTkzNDUyZGMyNDNlYTQyNTQ3MDJkZTEzYjI0OTliYTQ4ODYxODk2NTI4NGNmNDQzNmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1NWE2YmQ5MDhjMTBjOGM0MzYyZDBkNDAyZDI0Y2JkMDY2MDgyYjNjMjQ0N2YwZTA0MjZhYjEzY2M4NDc2M2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MDlmNTg5MzgzODA2NjM1OTVhOTFhNDhlYjQzNWFhNjI1OGNiZWVlZDc1MjAzMDUxODZiYjBiMGI4MTZjMDIifQ" }, { - "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift#L4-L84", - "version": "repo-lines-v1:sha256:003d10c288ef925e851ed18c5b8c0f9efd88a3a9d64141a8b1b58ec8b3bce666:eyJzZWxlY3RlZExpbmVDb3VudCI6ODEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxNDkxODZkNzI0Y2I5MzM5MjJhN2U0MTQwZTVkMzhiNGI5MGQ5YzQyZjQxZmY0NDIzMzZjNjVkNWNhMGRlZDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OGQ0YTkxMDc0MDU5ZjBjOGQyMTM2ZjliZjgwODM0NjE0OGZjOGNmYjM1NDNkZGNiYzc1MGU3NTc2ZTcyNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift#L1056-L1101", + "version": "repo-lines-v1:sha256:0a7ae642dd083c84f164b264fee0281ba51d48288dcddb66d8c2e9912a631140:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljN2Y4NWI1NDQ5YmI0MTM0YjhmMTAzN2M1ZDkwNjU4ZDNiMjU4YWIxMDZlNWZmNjEzNTBiZjg2MzlmYzY4ODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGMzODIwMjNmZDQ2NDhkYjQzZmZhNjFjNWJmYjQ3ZjliOTZjZTIyZjFmNGI2OGYxZTczZGY3ODkxN2VlMDkifQ" }, { - "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:f4a0aba7ad07392adbc7b5e56e5c6ff67e41cfd3f4ace4cdd9754a5a487ef0ce" + "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift#L4-L68", + "version": "repo-lines-v1:sha256:b43cb4870d4d5319ae6dd1b68255f083bdf1c62a876c6ea566d2ef6c266f5cc6:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxNDkxODZkNzI0Y2I5MzM5MjJhN2U0MTQwZTVkMzhiNGI5MGQ5YzQyZjQxZmY0NDIzMzZjNjVkNWNhMGRlZDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OGQ0YTkxMDc0MDU5ZjBjOGQyMTM2ZjliZjgwODM0NjE0OGZjOGNmYjM1NDNkZGNiYzc1MGU3NTc2ZTcyNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://TFITests/MobileAppTests.swift#L1516-L1546", + "version": "repo-lines-v1:sha256:da0450e18748d4e9ec4ac784144804c174d495f485f47167f501f7493cc28fbe:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyNjg1Mjg5MWQ1ZjVjNTVlNjY5YzQyNjRiNzc3NzAyOTVkMDcwYTY0YTVlZDAyZTc4ZmJiYTJhMTk5NGFkNDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjllOWQzMmRjYWFlMWI1NmZiYzYzMTNjZTM2YzAyMGE5MDhkOGFiMTUxNDk5NTgyM2MyODFhOGI3YWE0ZjQwODEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjczNzhlZjkzMGE2OGVlNWNmZTg4Y2NlMTZlOWYzYjkwMzBhNjExMzYwNzg2OTI0ZDM4ZGU5MzJlZGM3NjI0NDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBjNTgzMzcyZWM2ZDg1Zjg4NDI1Mzk5ZWE4OWJlYmJlNGY5ODY4ZmExMmU1NWI3YTkzYTdiOTAzMDExNDZmYWUifQ" }, { "resource": "repo://TFITests/MobileWorkspaceStoreTests.swift", - "version": "repo-file-v1:sha256:8e3b0cd7aa4223d0ce68f9bf72d378ea2704a4560e1b708fa9633e6230a24e02" + "version": "repo-file-v1:sha256:9953e49389c0c2b390fe6bd51e467de56c31ec1556a13633ae239ac842c9241d" }, { - "resource": "repo://TFMTests/WindowStateTests.swift", - "version": "repo-file-v1:sha256:95b16fe6f03f826333e99762f9751e8f931f931f437a4dc1d052b239eb3c3039" + "resource": "repo://TFM/App/TermindApp.swift", + "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + }, + { + "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", + "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" } ] }, @@ -167,6 +175,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T00:14:42.075Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 25c910e4..ac5160f5 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-22T11:47:27.017Z", + "updatedAt": "2026-09-23T01:00:12.175Z", "command": "update", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index 809e293b..e6b56146 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,53 +2,53 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { - "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", - "sourceFingerprint": "sha256:bb9e6ab2370a6da5d36671f148615ef16040004c9923a517f7ce5b86c27a028a", - "pageVersion": "sha256:e54867652d0a9b222655bb9e8dd43735a96cf9bf358c9a9f589e5c05b3b2a3b0", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:fad06b683f4e91f808959ed85ce962b1c86d64a67078b39c7ba20f8aa12ad35b", "completedBy": "codex", - "completedRunId": "10c79875-8fc3-4267-9696-e221166a064d" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:39f747ee778af3ed681b7fbcc46facf80f7fe3dc6233760e56c85515641f7ae8", + "pageVersion": "sha256:6f98da9e8f070ebc2a0b91545058eb3aa5a411129c7a18c9b33ee9eb6da8e799", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "9d54b362-29b2-42d6-9960-30b93a494d0b", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db" }, "/openwiki/architecture/mobile-ui.md": { - "pageVersion": "sha256:e71b16d155c8189cf4bc29a8e15efcfc015a8b5ea0a52154a4842e5584633c5d", + "pageVersion": "sha256:769919ef95916ccc91f27ef2666730600005e79c052893c2ae098644ac389a07", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "9d54b362-29b2-42d6-9960-30b93a494d0b", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db" }, "/openwiki/architecture/module-map.md": { - "pageVersion": "sha256:0d41246b831e3a59c548887a3cec7e8cb0546108c0300d36813d3f2264fb8e02", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:8c5b708036c7d089f4821e24e31a00e1684e44d78bf3a8182b9df0800ffe7f00", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/architecture/native-app-directories.md": { - "pageVersion": "sha256:43a03b1d5306dd21b5102c03ec20ee848a7787150f88ab226cb8587c37d7322f", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:411c153b0239360e9fc0a413c9ff012607f8aa71dfd42cf349bcfc83eb2fb690", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/architecture/platform-runtime.md": { - "pageVersion": "sha256:f73e9e1fc7067538111ec94107bf093f7e34deef9f30a2e1f5ee2f071a2fe06e", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:1928bdff8b5655653f6d6eaa66489a0a3c967dfebdf9c9c86245df87bf2011c1", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/architecture/shared-module-directories.md": { - "pageVersion": "sha256:8477e980e76c27f6b9eeb9c8b9a93f6fc37ee858ae5819a9da3bd7b97f383ee9", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:6ba56baf56fd5439756c528b55b7765f506ed716677142f0298cbad9f4cf2f93", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/architecture/supporting-system-directories.md": { "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", @@ -58,11 +58,11 @@ "completedRunId": "8bdb277a-cf85-4174-a91f-b6a4f89a046d" }, "/openwiki/architecture/system-boundaries.md": { - "pageVersion": "sha256:0b0ea65c8f99eafc07573ee48a92854ed136e4499e1a50382c1e7a9f6a57087b", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:db9f09d0c4799f6c8ecc04c8b993af8c45c2c71fbb32827587c3c99c945c3c2b", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,18 +79,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { - "pageVersion": "sha256:b40dd5b97eab1b2f15226b54a5f25925facd3f23cc4461865676fa87a99e7228", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:1235c885f381c01020d2218636f500707c8fc6b3b1e8e4fbd712336ef4deb064", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/concepts/remote-services-and-forwarding.md": { + "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", + "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c", "pageVersion": "sha256:df4482efaa9bf8c8456a93e90ca849b5c53f58754ec6b5f653cc9acffd52fda9", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6" }, "/openwiki/concepts/shell-integration.md": { "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", @@ -107,18 +107,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/ui-design-principles.md": { - "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", - "sourceFingerprint": "sha256:9fdb52819a900696d202afba4b9b9a96e2501b945f17df7f029db20b595431ac", - "pageVersion": "sha256:3c9dd812481bbfb7ac06fdb0073a97ba6a5d2d2966f90d7619e1c7d1d5fc6319", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:2ea45885f28729e3e1c545ea046ac5d552d5ad17465af3efc9beccb6755bc299", "completedBy": "codex", - "completedRunId": "dc9c6d50-8538-42e9-8a0f-bba67cb704f7" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/integrations/mcp-server-and-bridge.md": { - "pageVersion": "sha256:dd68622974b0364e9ac768703272c0529344c92ea6e1547f962fda0b8f878725", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:d5354c90144215080870d9769a26cb62524794d9f08d9222bf13f1dfaab336a0", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/operations/build-test-and-release.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -128,11 +128,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/operations/cloudkit-release-readiness.md": { - "pageVersion": "sha256:392a5cd29488846a61161bf56d2b7a9e5d87640c51cea9de02d4a7f679efe676", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:cca8239457462455652c40b3bb1223ac001457c4955f7d3427e24358992cb80d", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/operations/marketing-assets-and-app-icons.md": { "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", @@ -156,18 +156,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "pageVersion": "sha256:6ef69e5f7c1232777ba9d2db117e7e53939190d8fb556779d50aa2ad21ae5e23", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:36dd6088760d21cfecb2ca2fffc6e6ea9aa667c3f14011c61dcbb354b0377150", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { - "pageVersion": "sha256:380061db2e83cfce09c0c64a2a8fbd37156aad0e810d165ecc9eb3c988af5c68", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:57c765ca650c5a286d85e4b4740dbd3e17d8d481a2fbdae60324abe952962271", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -177,11 +177,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/settings-and-cloud-sync.md": { - "pageVersion": "sha256:a1336fb6bb6fa0733b3bf551d203c4bfcbfff5097545cea1d5614309428ce74d", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:b9df8c5b704668ff3f401eb5ba03c19783905eb26e3754598ec05f2c4840c025", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/workflows/sftp-transfer-and-external-edit.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { - "pageVersion": "sha256:1c3a728083dd0fd403bf9e9e1cedb2079dfe280c3f037e0de4b6c1a072818a75", + "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", + "pageVersion": "sha256:5cad513345c5cd73a1bb915584cf0566e8f1a8f543c5e5dd4da91692e6407352", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6", - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c" + "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" } } } diff --git a/openwiki/architecture/desktop-ui.md b/openwiki/architecture/desktop-ui.md index 595e0975..85280a2f 100644 --- a/openwiki/architecture/desktop-ui.md +++ b/openwiki/architecture/desktop-ui.md @@ -5,7 +5,7 @@ description: "How the macOS app composes its native window shell, persistent wor tags: [macos, tfm, ui, navigation, workspaces, swiftui, appkit, design] verified: - by: openwiki/0.5.2 - at: 2026-09-22T09:17:11.491Z + at: 2026-09-23T00:14:42.075Z sources: - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index 06eabbae..cf395f2d 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -6,18 +6,18 @@ tags: [architecture, invariants, lifecycle, security, persistence, remote-sessio sources: - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md + - id: openwiki-source-5c91b796f7e6d651aedf3023 + resource: repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f resource: repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift + - id: openwiki-source-7ca30f0e781e8c2df826b47a + resource: repo://Frameworks/TermindApplication/HistoryLimits.swift - id: openwiki-source-4f78037bc69b6dfc293aca3d resource: repo://Frameworks/TermindApplication/SSHConnectionTypes.swift - id: openwiki-source-cd3e43e60522274ce1f1908b resource: repo://Frameworks/TermindApplication/SSHSecret.swift - - id: openwiki-source-e4f5ea9b30063cdc7d027956 - resource: repo://Frameworks/TermindDomain/Connections/ConnectionHistoryEntry.swift - - id: openwiki-source-7367aa06bc3b7ce470e78113 - resource: repo://Frameworks/TermindDomain/DeviceRecord.swift - id: openwiki-source-1d1f503a260f304a969fcee4 resource: repo://Frameworks/TermindInfrastructure/Cloud/CloudSyncLifecycle.swift - id: openwiki-source-4957f13c836c497e7ebe4656 @@ -32,10 +32,16 @@ sources: resource: repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift - id: openwiki-source-fe1dc993163bb9ff61397be7 resource: repo://Frameworks/TermindPersistence/HostPersistence.swift - - id: openwiki-source-bc351b6c699ff4bbf9f8c2b1 - resource: repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift - id: openwiki-source-7a9c6d90cba4ebd1d1398673 resource: repo://Frameworks/TermindPersistence/TermindPersistence.swift + - id: openwiki-source-8185a608f0b79f1af097efbd + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift + - id: openwiki-source-0689f4693840cd29f5ac352c + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift + - id: openwiki-source-821c6451f936b3390d26080d + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalFontSettings.swift + - id: openwiki-source-07dbc9527b95676a55499eb8 + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift - id: openwiki-source-87b90af0bd504e35083a8414 resource: repo://Frameworks/TermindPresentation/VaultSyncStatusLabel.swift - id: openwiki-source-1c845e5af67c400566be83bd @@ -66,26 +72,42 @@ sources: resource: repo://TFI/App/MobileSceneModel.swift - id: openwiki-source-6c9f797fc6a1e7ce639b72b8 resource: repo://TFI/App/TermindIOSApp.swift + - id: openwiki-source-02810e1f8eac4d0983f8cd44 + resource: repo://TFI/Features/Settings/MobileTerminalSettingsView.swift + - id: openwiki-source-3c85f29e2d0bdf3d44f4e0e8 + resource: repo://TFI/Features/Terminal/MobileTerminalListPresentation.swift + - id: openwiki-source-3f37062f55fb57a9f2e97f35 + resource: repo://TFI/Features/Vault/MobileVaultView.swift - id: openwiki-source-492f738896225c6b3acf7d2f resource: repo://TFI/Features/Workspaces/MobileWorkspace.swift - id: openwiki-source-350d4a794cfef67e406ddf9b resource: repo://TFI/Infrastructure/MobileWorkspaceStore.swift + - id: openwiki-source-e396438eb6a8a7ca888da5f4 + resource: repo://TFI/Platform/TerminalFontCatalog.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-16ec96bd78162e7e62effd0e + resource: repo://TFITests/MobileConnectionPickerTests.swift - id: openwiki-source-9b78a7e39bfa08bea5192722 resource: repo://TFITests/MobileWorkspaceStoreTests.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f resource: repo://TFM/App/AppModel.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-0854be84b166268626a3cb08 + resource: repo://TFM/Features/Sidebar/WindowSidebarView.swift - id: openwiki-source-22b9fca540c3029c5ace451e resource: repo://TFM/Features/Workspaces/WindowModel.swift + - id: openwiki-source-385c953ef1c6bf095d1c1244 + resource: repo://TFM/Platform/TerminalFontCatalog.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift -generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } + - id: openwiki-source-9442022b3dba1be62a55f0f0 + resource: repo://TFMTests/TerminalThemeTests.swift +generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:59:31.979Z --- # Cross-Cutting Engineering Invariants @@ -108,7 +130,7 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r | TermindInfrastructure | In-memory repositories, metrics parsing, StoreKit, Vault/Settings/Metrics CloudKit; Application, Domain, Persistence. | | TermindRemoteInfrastructure | SSH/SFTP/Telnet, Keychain, keys, sampling, forwarding, transfers, scoped imports; Infrastructure, Application, Domain. C libraries stay outside portable framework tests. | | TermindPresentation | Reusable SwiftUI policy/content, Ghostty themes, `TerminalPreferences` bridge; Application and Domain only. | -| TermindPersistence | All SwiftData models, frozen V1–V31 history, V32 live models, migrations and container construction; no app dependency. | +| TermindPersistence | All SwiftData models, historical schemas, V34 live schema, migrations and container construction; no app dependency. | | TFM / TFI | Native UI/adapters/lifetimes; shared modules never import apps. | - Use direct owner imports/public APIs, not re-exports, forwarding aliases, `Shared` types or a generic Core module. @@ -119,6 +141,10 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r tests. Both apps share the CloudKit container and bundle-derived Keychain access group. - Architecture tests fail closed on absent/empty production roots. Source checks guard dependencies, native-list allowlists, AgentKit/SwiftData ownership only; UI/copy/wiring need behavioral or visual coverage. +- V31 adds scoped quarantine/recovery models. Released V31 stores migrate directly to V34; V32/V33 were not distributed + schema endpoints. V34 replaces Host, connection-history and Identity models, adds device-directory and Workspace + preference models, and backfills history with the installation ID plus its initial DeviceRecord. Local schema changes + do not change the independently versioned CloudKit envelopes. ## State, navigation and editing @@ -322,6 +348,13 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r missing local size from pre-upgrade local data before remote materialization; existing mirrors win. Ignore legacy remote font fields and omit outgoing fields. Themes/custom text (including authored fonts) still sync. Missing fonts fall back without deleting choice. Apply local overrides after theme/custom commands, resetting family lists; update surfaces in place. +- Shared `TerminalFontFields` binds local typography with an 8–36 point editing range, a macOS slider and an iOS stepper; + merely presenting the controls does not normalize saved sizes. Font discovery/refresh stays app-owned: a family qualifies + when any loadable member is monospaced, with localized ordering rather than a shared AppKit/UIKit adapter. +- Shared theme options are appearance-filtered and name-sorted. Keep unavailable or wrong-appearance saved names as picker + selections and show the actual fallback; opening Settings must not replace or persist them. Rendering uses the matching + Termind default, then custom commands, then local typography. Base-theme selection remains editable with custom commands + enabled, since commands overlay only their specified keys. Save/remote-apply and native terminal lifetimes stay with their owners. - Desktop copy-completed-selection/right-click-paste default off and sync in the terminal record; mobile preserves without exposing/applying. Keep native surfaces. Copy only local completion; paste once on release through Ghostty clipboard/input capture. Remote mouse/Option-right-click stay Ghostty-owned; mouse reports never broadcast. Empty paste arms no capture; @@ -334,7 +367,7 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r submits it once at the authenticated prompt boundary, including automatic reconnects; explicit launch commands override only until submitted. Missing/empty Snippets are no-ops. SFTP, forwarding, Metrics, Agent and route connections never run it. - History starts after authenticated start, retains optional remote status/duration (never receive-time inference), normalizes - backslash continuation only and caps at 1,000 rows/Host. Both UIs show first line plus ellipsis. + backslash continuation only and caps at the shared 250 rows/Host. Both UIs show first line plus ellipsis. ### SFTP and external edits @@ -587,16 +620,16 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo - One SSH session/SFTP Channel per mobile Workspace, no Welcome/pane composition even with Pro; Assistant independent. Peer switch retains connection, Recent opens on demand, cross-open retains live paths. Explicit close never retries; failures stay in Journey. Background expiration closes transports but retains intent; foreground resumes existing Workspaces. -- `mobile.workspaces.v1` persists ordered local descriptors only (ID/Host/kind/title/duplicate index), no commands/secrets/paths/content/transfers. +- `mobile.workspaces.v1` persists ordered local descriptors only (ID/Host/kind/title), no commands/secrets/paths/content/transfers. Cold launch after Hosts loads Recent only, no sessions/history/network. MainActor open-by-ID reuses or registers/connects once across scenes; - failure needs explicit retry. Save retains unopened entries/order. Read legacy arrays/v1, assign missing lowest-free indices and write envelope; + failure needs explicit retry. Save retains unopened entries/order. Read legacy arrays/v1 and migrate legacy arrays to the versioned envelope; only successful loads permit save. Corrupt/unknown bytes remain/write-blocked. Prune missing Hosts; close/exit/delete removes live+Recent entries; - Close All preserves connection history. Titles reserve indices without renumbering. Host-delete outbox cancels related runtime/local history. + Close All preserves connection history. Host-delete outbox cancels related runtime/local history. - Connection history is a synchronized Vault event stream for saved-host SSH/Telnet/SFTP only; Serial and temporary targets never enter it. Each event carries a stable `sourceDeviceID`, start time and optional end time, but no device name. The reusable synchronized device directory maps installation IDs to mutable display names, so a rename updates every feature that resolves that ID without rewriting history events. Closing a - pane/workspace ends its event; moving preserves it. Stores retain the newest 300 deterministically. Welcome/mobile Recents use local events and - aggregate by Host, while desktop/mobile History presents at most 100 all-device events. Aggregated Delete clears that Host's events across + pane/workspace ends its event; moving preserves it. Stores retain the newest 250 deterministically. Welcome/mobile Recents use local events and + aggregate by Host, while desktop History presents at most 100 all-device events and mobile History at most 50. Aggregated Delete clears that Host's events across devices; Clear History clears the complete timeline. - One serialized Live Activity for current Workspaces only. Secret/command-free validated links select ID/list without restoring unrelated sessions; cold launch ends leftover activities but links may open saved entries. Explicit start intent waits for foreground; last current close clears it diff --git a/openwiki/architecture/mobile-ui.md b/openwiki/architecture/mobile-ui.md index 6e01390e..9d43a29c 100644 --- a/openwiki/architecture/mobile-ui.md +++ b/openwiki/architecture/mobile-ui.md @@ -42,12 +42,14 @@ sources: resource: repo://TFITests/MobileAdaptiveLayoutTests.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-16ec96bd78162e7e62effd0e + resource: repo://TFITests/MobileConnectionPickerTests.swift - id: openwiki-source-11c91698354a4f95be3c4654 resource: repo://TFITests/MobileWorkspaceMenuTests.swift -generated: { by: "codex", at: "2026-09-22T11:34:21.807Z" } +generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:59:31.979Z --- # TFI Mobile UI Architecture @@ -143,7 +145,7 @@ The Workspaces destination and the Vault expose different projections over the s - Workspaces Recents uses only events from this installation, aggregates every protocol and attempt into one row per Host, and keeps the newest five Hosts. A primary tap uses current Host capability preference (SSH before Telnet); protocol context and swipe actions keep SSH, Telnet and SFTP visible while disabling unavailable choices. Deleting the aggregate clears that Host's events across devices. - Home Screen quick actions use the same local per-Host projection, publish at most three host-only `server.rack` items, and resolve the current preferred terminal protocol at activation. Older payloads that carried a protocol still decode, but that stale protocol is ignored. -- Vault's History page follows Known Hosts and displays up to 100 individual events from every synchronized device. The leading symbol remains protocol-specific, while the right column and search resolve the event's `sourceDeviceID` through the reusable synchronized device directory. Row deletion removes one event; **Clear History** clears the complete timeline. +- Vault's History page follows Known Hosts and displays up to 50 individual events from every synchronized device. The leading symbol remains protocol-specific, while the right column and search resolve the event's `sourceDeviceID` through the reusable synchronized device directory. Row deletion removes one event; **Clear History** clears the complete timeline. The unified Workspaces rows and local Recents align their text from the same 36-point Host distribution icon slot and 12-point gap. Workspace rows always show protocol at the common right edge and only live runtimes overlay the status dot; diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index 9556f790..615c17b5 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 05cb8d04..83660e8a 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -72,6 +72,8 @@ sources: resource: repo://TFM/Infrastructure/Networking/LoopbackHTTPServer.swift - id: openwiki-source-a0f4f642e37695357bc9150d resource: repo://TFM/Infrastructure/Security/MCPServerTokenStore.swift + - id: openwiki-source-68b5f4529ef349aac826b1d9 + resource: repo://TFM/Platform/MacDeviceName.swift - id: openwiki-source-28f740582e7f32c1d0099069 resource: repo://TFM/Platform/MacOSSpeechAudioInput.swift - id: openwiki-source-22e92f96394f292a731fc6fb @@ -82,10 +84,10 @@ sources: resource: repo://TFM/UI/Managed/ManagedCardPage.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift -generated: { by: "codex", at: "2026-09-22T10:02:02.985Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Native App Directory Responsibilities @@ -109,7 +111,7 @@ verified: `App`, `Platform`, `Design`, and `DesignSystem` are currently flat source directories. Their multiple concerns are listed above rather than presented as nonexistent child folders. -The app composition roots also adapt platform device naming into the shared device directory. Both TFM and TFI read DeviceKit's `Device.current.safeDescription`; TFM refreshes it when the application becomes active, and TFI refreshes it when a scene becomes active. The portable `DeviceRecord`, directory model and repositories remain in shared frameworks rather than either native app directory. +The app composition roots also adapt platform device naming into the shared device directory. TFM's `MacDeviceName` reads `kern.hostname`, trims whitespace and falls back to DeviceKit's `Device.current.safeDescription` when the hostname is unavailable or empty. TFI uses DeviceKit's description directly. TFM refreshes the name when the application becomes active, and TFI refreshes it when a scene becomes active. The portable `DeviceRecord`, directory model and repositories remain in shared frameworks rather than either native app directory. ## Application subdirectories diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index 744635f0..f22616f1 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -16,6 +16,8 @@ sources: resource: repo://TFI/App/MobileSceneModel.swift - id: openwiki-source-6c9f797fc6a1e7ce639b72b8 resource: repo://TFI/App/TermindIOSApp.swift + - id: openwiki-source-9c78befae6b387426c603305 + resource: repo://TFI/Application/Workspaces/MobileWorkspaceDescriptor.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift - id: openwiki-source-2c8d92432bb6dac7ede21141 @@ -30,16 +32,18 @@ sources: resource: repo://TFM/Features/Workspaces/Pane.swift - id: openwiki-source-22b9fca540c3029c5ace451e resource: repo://TFM/Features/Workspaces/WindowModel.swift + - id: openwiki-source-68b5f4529ef349aac826b1d9 + resource: repo://TFM/Platform/MacDeviceName.swift - id: openwiki-source-22e92f96394f292a731fc6fb resource: repo://TFM/Platform/MainWindowCoordinator.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-531f6567ecf96fd4b0c5e97a resource: repo://TFMTests/ExternalConnectionURLTests.swift -generated: { by: "codex", at: "2026-09-22T10:02:02.985Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Platform Runtime, App Composition, and Lifetimes @@ -70,7 +74,7 @@ A useful consequence is that sharing a desktop `AppModel` does **not** make its ## macOS composition -`TermindApp.init()` is the desktop composition root. It determines whether it is running tests or a debug screenshot demo, constructs one SwiftData `ModelContainer`, prepares settings and metrics persistence, makes the Keychain-backed secret store, and assembles `AppModel` with repositories, sync coordinators, logging, histories, the reusable device directory, and the common `RemoteConnectionCoordinator`. The directory uses the Vault journal's stable installation ID and DeviceKit's `Device.current.safeDescription`. Startup fails closed: an inability to open the local database calls `fatalError` rather than substituting an empty store. +`TermindApp.init()` is the desktop composition root. It determines whether it is running tests or a debug screenshot demo, constructs one SwiftData `ModelContainer`, prepares settings and metrics persistence, makes the Keychain-backed secret store, and assembles `AppModel` with repositories, sync coordinators, logging, histories, the reusable device directory, and the common `RemoteConnectionCoordinator`. The directory uses the Vault journal's stable installation ID and `MacDeviceName.current`: the trimmed `kern.hostname`, falling back to DeviceKit's `Device.current.safeDescription` when unavailable or empty. Startup fails closed: an inability to open the local database calls `fatalError` rather than substituting an empty store. ```mermaid flowchart TD @@ -113,7 +117,7 @@ A host-opening action is centralized in `AppModel`, which chooses the requested ### Desktop lifecycle and termination -The AppKit delegate starts native appearance/login handling and, outside tests, starts metrics activity and the metrics menu once `AppModel` is available. On app activation it refreshes both entitlement state and the current `DeviceRecord` display name from DeviceKit's `Device.current.safeDescription`; the synchronized directory can therefore update without rewriting connection events. TFI performs the equivalent DeviceKit refresh on active-scene transitions. On termination the desktop app first checks for pending external edits; after user confirmation, it saves window sizing and invokes `AppModel.shutdownForTermination()`. +The AppKit delegate starts native appearance/login handling and, outside tests, starts metrics activity and the metrics menu once `AppModel` is available. On app activation it refreshes both entitlement state and the current `DeviceRecord` display name from `MacDeviceName.current`; the synchronized directory can therefore update without rewriting connection events. TFI refreshes its name directly from DeviceKit's `Device.current.safeDescription` on active-scene transitions. On termination the desktop app first checks for pending external edits; after user confirmation, it saves window sizing and invokes `AppModel.shutdownForTermination()`. Shutdown is deliberately idempotent and bounded by the delegate. It suspends memory and edit monitoring, stops metrics, speech, windows, SFTP work, external authentication, forwarding, and the outbox; concurrently awaits MCP and SFTP cancellation, then closes agent background commands and connection pools. The delegate replies to macOS when cleanup completes or after a three-second fallback, preventing an unbounded quit wait. @@ -155,7 +159,7 @@ The app model wires cross-service invalidation at construction. Host deletion an `MobileAppModel`, rather than a scene, owns `workspaces` and the persisted descriptor list. `openWorkspace(_:)` first returns an already-live workspace; otherwise it validates the saved descriptor's Host, constructs the workspace, publishes it, and then starts its connection. Publishing before connecting is an invariant: multiple scenes selecting the same descriptor share an instance and cannot trigger duplicate first connections. A scene only selects/presents that shared workspace. -Mobile persistence deliberately restores **descriptors, not sessions**. On cold launch `loadRecentWorkspaces()` reads descriptors, removes those whose Hosts are absent, and publishes no live workspace or connection; it also clears leftover Live Activities. A descriptor contains local identity/title/kind/duplicate-index information, while initial commands, paths, connection secrets, and transports stay outside it. Opening a saved descriptor connects it on demand. Closing removes both the live instance and descriptor; externally linked temporary workspaces are not persisted. +Mobile persistence deliberately restores **descriptors, not sessions**. On cold launch `loadRecentWorkspaces()` reads descriptors, removes those whose Hosts are absent, and publishes no live workspace or connection; it also clears leftover Live Activities. A descriptor contains only workspace ID, Host ID, title, and connection kind, while initial commands, paths, connection secrets, and transports stay outside it. Opening a saved descriptor connects it on demand. Closing removes both the live instance and descriptor; externally linked temporary workspaces are not persisted. ### Mobile foreground and background boundaries diff --git a/openwiki/architecture/shared-module-directories.md b/openwiki/architecture/shared-module-directories.md index db608601..086cfdf2 100644 --- a/openwiki/architecture/shared-module-directories.md +++ b/openwiki/architecture/shared-module-directories.md @@ -58,8 +58,6 @@ sources: resource: repo://Frameworks/TermindInfrastructure/Logging/LaunchFileLogService.swift - id: openwiki-source-f18e361c4458ef5e475b60b6 resource: repo://Frameworks/TermindInfrastructure/Speech/AppleSpeechTranscriptionService.swift - - id: openwiki-source-ec0a64adad50721ec500ebc2 - resource: repo://Frameworks/TermindPersistence/AISecretStore.swift - id: openwiki-source-1366e71322413c98e0ac1534 resource: repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift - id: openwiki-source-e14697ea11a99078be3adee9 @@ -106,10 +104,10 @@ sources: resource: repo://Termind.xcodeproj/project.pbxproj - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift -generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Shared Framework Directory Responsibilities @@ -192,7 +190,7 @@ Details: [Persistent Data, Migrations, and Secrets](../concepts/persistence-and- | `Logging` | Launch-scoped file logging and log-file lifecycle. | | `Speech` | Apple speech-recognition implementation plus an audio-input abstraction; each app supplies its native audio capture. | -This layer can use Persistence. It does not depend on either native app, and a CloudKit coordinator is not a screen-level settings model. The shared safety adapter projects scoped persistent restrictions and recovery state into the lifecycle; it does not merge the categories' engines, accounts, subscriptions or journals. V31 added safety storage locally; V32 replaces current Host/connection-history shapes, adds the generic device-directory record, and keeps the existing CloudKit record format. Both received records and save-conflict replies use the same validation path before allowing a blocked record to become uploadable again. +This layer can use Persistence. It does not depend on either native app, and a CloudKit coordinator is not a screen-level settings model. The shared safety adapter projects scoped persistent restrictions and recovery state into the lifecycle; it does not merge the categories' engines, accounts, subscriptions or journals. V31 added safety storage locally; the current V34 schema replaces Host/connection-history and Identity shapes, adds the generic device directory and workspace preferences, and keeps the independently versioned CloudKit envelopes. Released V31 stores migrate directly to V34; V32 and V33 were never distributed schema endpoints. Both received records and save-conflict replies use the same validation path before allowing a blocked record to become uploadable again. ## TermindRemoteInfrastructure — protocol and remote-operation machinery diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index 5f23e4b5..94e1572f 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -36,12 +36,14 @@ sources: resource: repo://TFI/App/TermindIOSApp.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-68b5f4529ef349aac826b1d9 + resource: repo://TFM/Platform/MacDeviceName.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift -generated: { by: "codex", at: "2026-09-22T11:34:21.807Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # System Boundaries and Dependency Ownership @@ -101,7 +103,7 @@ The checks also prohibit all shared frameworks from importing `Termind` or `Term ## Persistence is a single-owner boundary -`TermindPersistence` exports the complete current schema (`TermindSchemaV33`) and builds a `ModelContainer` using its migration plan. Its factory rejects an in-memory request that also supplies a file URL and configures the store with CloudKit disabled; platform composition roots choose *where* the store lives and retain the container, but do not define the model set or construct a substitute container. +`TermindPersistence` exports the complete current schema (`TermindSchemaV34`) and builds a `ModelContainer` using its migration plan. Its factory rejects an in-memory request that also supplies a file URL and configures the store with CloudKit disabled; platform composition roots choose *where* the store lives and retain the container, but do not define the model set or construct a substitute container. Connection history follows the same boundary: Domain owns the portable event value, Application owns the observable model and repository protocol, and Persistence owns its SwiftData shape and Vault-journal transaction. The reusable ID-to-name mapping is deliberately separate: Domain owns `DeviceRecord`, Application owns `DeviceDirectoryModel` and `DeviceDirectoryRepository`, and Persistence owns `PersistentDeviceRecord` plus the SwiftData adapter. A history event merely references `sourceDeviceID`; it does not own or capture the device name. Platform apps inject connection lifecycles, acquire their native system display name, and choose local versus all-device presentation; they do not declare stored models. @@ -111,7 +113,7 @@ Both app roots call `TermindPersistence.makeContainer`. Desktop turns an unavail ## Composition, resource, and state ownership -The app targets are composition roots. `TermindApp` creates the desktop `ModelContainer`, keychain store, sync persistence/coordinators, repositories, connection coordinator, logging, and app-wide `AppModel`; it also adapts DeviceKit's `Device.current.safeDescription` into the shared device-directory port. It injects the shared model and container into its window scenes. `TermindIOSApp` chooses live or testing dependencies, creates one startup `MobileAppModel`, and injects that model's container into its `WindowGroup`; the mobile composition uses the same DeviceKit description for the device-directory port. This is where a concrete implementation or platform value becomes a port supplied to shared feature code. +The app targets are composition roots. `TermindApp` creates the desktop `ModelContainer`, keychain store, sync persistence/coordinators, repositories, connection coordinator, logging, and app-wide `AppModel`; it also supplies `MacDeviceName.current` to the shared device-directory port (trimmed `kern.hostname`, with DeviceKit's `Device.current.safeDescription` as fallback). It injects the shared model and container into its window scenes. `TermindIOSApp` chooses live or testing dependencies, creates one startup `MobileAppModel`, and injects that model's container into its `WindowGroup`; the mobile composition uses DeviceKit's description directly for the device-directory port. This is where a concrete implementation or platform value becomes a port supplied to shared feature code. Do not use this as permission to move live state into the app-wide layer. Durable stores and cross-window coordinators are app-wide; desktop navigation and workspaces are window-owned; mobile navigation is scene-owned; live SSH/SFTP transports remain with the workspace/session that opened them. Refer to [Platform Runtime, App Composition, and Lifetimes](platform-runtime.md) for the detailed ownership map. diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index f51e0e03..5f27bed3 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -4,6 +4,10 @@ title: Persistent Data, Migrations, and Secret Boundaries description: How Termind owns SwiftData and GRDB durable data, evolves compatible schemas, projects records through repositories, and keeps credentials in Keychain-backed and runtime-only boundaries. tags: [persistence, swiftdata, grdb, migrations, keychain, secrets, security] sources: + - id: openwiki-source-dc7b363138921b0923d2dc2f + resource: repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift + - id: openwiki-source-7ca30f0e781e8c2df826b47a + resource: repo://Frameworks/TermindApplication/HistoryLimits.swift - id: openwiki-source-4ddcdd630c9bc1c622a037df resource: repo://Frameworks/TermindApplication/SecretStoring.swift - id: openwiki-source-4f78037bc69b6dfc293aca3d @@ -42,6 +46,8 @@ sources: resource: repo://Frameworks/TermindPersistence/SwiftDataHostRepository.swift - id: openwiki-source-bc351b6c699ff4bbf9f8c2b1 resource: repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift + - id: openwiki-source-a0d614d618b11f103ec246ae + resource: repo://Frameworks/TermindPersistence/SwiftDataVaultStore.swift - id: openwiki-source-7a9c6d90cba4ebd1d1398673 resource: repo://Frameworks/TermindPersistence/TermindPersistence.swift - id: openwiki-source-8fda0c115d3df709fffe110c @@ -58,14 +64,18 @@ sources: resource: repo://FrameworkTests/PersistenceCompatibilityTests.swift - id: openwiki-source-906678adb91c2fce2d3fcaed resource: repo://TFI/App/MobileAppModel.swift + - id: openwiki-source-3f37062f55fb57a9f2e97f35 + resource: repo://TFI/Features/Vault/MobileVaultView.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-0854be84b166268626a3cb08 + resource: repo://TFM/Features/Sidebar/WindowSidebarView.swift - id: openwiki-source-1d1efb20e239b1f2142f6cb7 resource: repo://TFMTests/SettingsSyncTests.swift -generated: { by: "codex", at: "2026-09-22T11:34:21.807Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Persistent Data, Migrations, and Secret Boundaries @@ -92,7 +102,7 @@ This diagram distinguishes durable SwiftData and GRDB records from Keychain mate ## Store ownership and startup -`TermindPersistence.makeContainer(inMemory:url:)` is the single SwiftData entry point. It exposes the complete `TermindSchemaV33` schema, attaches `TermindMigrationPlan`, disables SwiftData CloudKit integration for this store, and rejects the invalid combination of an in-memory store and a file URL. Both apps retain one container for the process and construct separate `ModelContext` instances where independent repository contexts are needed; they do not assemble a partial schema per feature or scene. +`TermindPersistence.makeContainer(inMemory:url:)` is the single SwiftData entry point. It exposes the complete `TermindSchemaV34` schema, attaches `TermindMigrationPlan`, disables SwiftData CloudKit integration for this store, and rejects the invalid combination of an in-memory store and a file URL. Both apps retain one container for the process and construct separate `ModelContext` instances where independent repository contexts are needed; they do not assemble a partial schema per feature or scene. The desktop root makes container opening part of initialization and uses a `KeychainSecretStore` for live composition. The mobile root creates its Application Support parent before opening `Termind.store`, or chooses an in-memory container and `InMemorySecretStore` for test/injected configurations. Store-open errors propagate out of these composition paths rather than being silently replaced with a new empty database: data availability is an explicit startup failure condition. @@ -106,7 +116,7 @@ Agent conversations use a separate SwiftData repository with similarly deliberat ## Schema evolution: preserve the past exactly -Historical SwiftData model shapes are deliberately frozen. A `VersionedSchema` hashes the types it names, so retroactively editing a type used by an already shipped schema can make an installed store unopenable rather than migrated. The migration plan therefore retains every schema from V1 through V33 and stages each adjacent migration. When a persisted shape changes, introduce a version-specific model and replace it only in the new schema; do not “clean up” an old model definition. +Historical SwiftData model shapes are deliberately frozen. A `VersionedSchema` hashes the types it names, so retroactively editing a type used by an already shipped schema can make an installed store unopenable rather than migrated. The migration plan retains the released endpoints V1–V27, V30, V31, and V34, with direct V27→V30 and V31→V34 stages; V28/V29 and V32/V33 were never distributed endpoints. When a persisted shape changes, introduce a version-specific model and replace it only in the new schema; do not “clean up” an old model definition. Examples show why this matters: @@ -114,18 +124,18 @@ Examples show why this matters: - V11’s provider model remains frozen while V12 changes address representation; legacy columns in the live provider model are folded on read to preserve the former endpoint behavior. - V30 removes the obsolete persisted Known Host mismatch flag by replacing the V29 known-host model, while retaining hostname, port, algorithm, fingerprint, and date. - V31 additively introduces scoped CloudKit quarantine and recovery models with a lightweight V30→V31 migration. The existing V1–V30 model shapes remain unchanged; the local schema increment does not change the V1 CloudKit envelope formats. -- V32 replaces the live Host and connection-history representations and adds the generic device-directory representation. The Host replacement removes its obsolete `lastUsedAt` column. The custom migration preserves events, assigns the stable installation ID to their `sourceDeviceID`, and inserts a separate `DeviceRecord` for that installation. The event itself receives no hostname field, and the CloudKit envelope format remains V1. -- V33 replaces the live Identity representation to remove device-local `lastUsedAt` usage metadata. Its lightweight migration preserves Identity configuration and the independent Vault journal modification date; historical Identity shapes retain the old column solely for schema compatibility. +- V34 directly migrates released V31 stores to the Host/connection-history/device model types named V32 and the Identity type named V33, and adds workspace preferences. These type names do not imply intermediate released schema endpoints. Host and Identity replacements remove obsolete `lastUsedAt` columns. The custom migration preserves events, assigns the stable installation ID to their `sourceDeviceID`, and inserts a separate `DeviceRecord` for that installation. The event itself receives no hostname field, and the CloudKit envelope format remains V1. +- The V31→V34 compatibility test verifies retained Identity configuration and the independent Vault journal modification date, connection origin/backfill with no invented end time, and default workspace-preference journal bootstrap. Historical Host and Identity shapes retain the old columns solely for schema compatibility. `PersistenceCompatibilityTests` opens original V1 and V14 fixture stores and verifies their data survives; it also checks a file store can reopen, that in-memory containers use the full schema but remain independent, and that representative version upgrades retain data while giving new fields their intended defaults. Treat these compatibility fixtures as a release contract when changing entities, defaults, migration stages, or storage enum raw values. ### Synchronized connection events and device directory -`SwiftDataConnectionHistoryRepository` stores each saved-host SSH, Telnet or SFTP attempt as an independent event with stable ID, Host ID, protocol, start time, `sourceDeviceID` and optional end time. Every insert/end/delete/clear reconciles the complete local event set with `VaultSyncJournal` inside the same `ModelContext` commit, so a successful history mutation and its envelope or tombstone cannot diverge. The repository and remote-merge path sort by start time and UUID, then deterministically retain the newest 300. The 100-row History limit is presentation-only. +`SwiftDataConnectionHistoryRepository` stores each saved-host SSH, Telnet or SFTP attempt as an independent event with stable ID, Host ID, protocol, start time, `sourceDeviceID` and optional end time. Every insert/end/delete/clear reconciles the complete local event set with `VaultSyncJournal` inside the same `ModelContext` commit, so a successful history mutation and its envelope or tombstone cannot diverge. The repository and remote-merge path sort by start time and UUID, then deterministically retain the newest 250. The History limits of 100 desktop rows and 50 mobile rows are presentation-only. -`SwiftDataDeviceDirectoryRepository` independently persists reusable `DeviceRecord` values that map installation IDs to mutable display names. Its upsert and `.device` journal envelope commit together. Device records are not dropped when a Host is deleted, History is cleared, or the 300-event retention rule prunes old events; this lets any feature resolve the current name for a stable installation ID. A system-name change updates one directory row, so all connection events and future consumers display the new name without event rewrites. +`SwiftDataDeviceDirectoryRepository` independently persists reusable `DeviceRecord` values that map installation IDs to mutable display names. Its upsert and `.device` journal envelope commit together. Device records are not dropped when a Host is deleted, History is cleared, or the 250-event retention rule prunes old events; this lets any feature resolve the current name for a stable installation ID. A system-name change updates one directory row, so all connection events and future consumers display the new name without event rewrites. -The old Host `lastUsedAt` column is not a replacement source for this timeline and is removed from the live V32 Host model. V33 likewise removes Identity usage timestamps; neither Host nor Identity usage metadata drives connection History. +The old Host `lastUsedAt` column is not a replacement source for this timeline. The live Host and Identity model types selected by V34 both omit usage timestamps; neither Host nor Identity usage metadata drives connection History. ## Cloud sync restrictions are durable local state diff --git a/openwiki/concepts/ui-design-principles.md b/openwiki/concepts/ui-design-principles.md index 3a5bac24..724882fd 100644 --- a/openwiki/concepts/ui-design-principles.md +++ b/openwiki/concepts/ui-design-principles.md @@ -4,12 +4,26 @@ title: UI Design Principles and Shared Presentation Boundaries description: "Source-backed UI principles for TFM and TFI: semantic visual systems, native interaction, shared forms and presentation values, stable rendering identity, accessibility, localization and safe extension boundaries." tags: [ui, design-system, presentation, accessibility, localization, macos, ios] sources: + - id: openwiki-source-4ae2a0061a7bd729d6e132b4 + resource: repo://Frameworks/TermindApplication/FeatureModels/TerminalSettingsModel.swift + - id: openwiki-source-0ad8e888a4a6fb517fde6f7a + resource: repo://Frameworks/TermindDomain/Sessions/TerminalFontPreferences.swift - id: openwiki-source-400e4d9303a3f9d98f90660d resource: repo://Frameworks/TermindPresentation/AssistantTranscriptEntry.swift - id: openwiki-source-eec1a2bc6dfc2c60b2bee55c resource: repo://Frameworks/TermindPresentation/HostConnectionFields.swift - id: openwiki-source-db3eac4d3fbf63570c0e0d56 resource: repo://Frameworks/TermindPresentation/SingleLineTextInput.swift + - id: openwiki-source-8185a608f0b79f1af097efbd + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift + - id: openwiki-source-0689f4693840cd29f5ac352c + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalFontFields.swift + - id: openwiki-source-98bdcb8d33d425fe144907e1 + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalFontPicker.swift + - id: openwiki-source-821c6451f936b3390d26080d + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalFontSettings.swift + - id: openwiki-source-07dbc9527b95676a55499eb8 + resource: repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift - id: openwiki-source-906678adb91c2fce2d3fcaed resource: repo://TFI/App/MobileAppModel.swift - id: openwiki-source-c8d832474c03f5ebad7b0578 @@ -22,12 +36,18 @@ sources: resource: repo://TFI/Features/AI/MobileAssistantConversation.swift - id: openwiki-source-fc448263e4e5ce330b8cbda1 resource: repo://TFI/Features/HostEditor/MobileHostEditor.swift + - id: openwiki-source-02810e1f8eac4d0983f8cd44 + resource: repo://TFI/Features/Settings/MobileTerminalSettingsView.swift + - id: openwiki-source-e396438eb6a8a7ca888da5f4 + resource: repo://TFI/Platform/TerminalFontCatalog.swift - id: openwiki-source-a4f3a690a8395e2ac9b065b9 resource: repo://TFI/UI/Components/MobileNativeList.swift - id: openwiki-source-c99ab42abad536ed4396933c resource: repo://TFI/UI/Managed/MobileCards.swift - id: openwiki-source-fffc181d864b6caea779a6f5 resource: repo://TFITests/MobileAdaptiveLayoutTests.swift + - id: openwiki-source-8194e1eb3652c7ad61d75cfa + resource: repo://TFITests/MobileTerminalFontTests.swift - id: openwiki-source-873894299539cce9a737a860 resource: repo://TFM/App/RootView.swift - id: openwiki-source-f490cb5bfbb8ce236a7f1bf4 @@ -36,18 +56,28 @@ sources: resource: repo://TFM/Features/AI/TranscriptListView.swift - id: openwiki-source-1e0cf223ab8448c8eee3f345 resource: repo://TFM/Features/HostEditor/HostEditorView.swift + - id: openwiki-source-cfe2a6b22338023c37a5b503 + resource: repo://TFM/Features/Settings/SettingsPageView.swift + - id: openwiki-source-af7fbbca079c2cb6201a5151 + resource: repo://TFM/Features/Settings/TerminalCustomConfigurationEditorSheet.swift - id: openwiki-source-ba160208435f3410138a3539 resource: repo://TFM/Features/Workspaces/PersistentWorkspaceDeckLayout.swift + - id: openwiki-source-385c953ef1c6bf095d1c1244 + resource: repo://TFM/Platform/TerminalFontCatalog.swift - id: openwiki-source-f6a967b4f14bf50d1af51b8b resource: repo://TFM/UI/Managed/ManagedCardPage.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-dceb8d7e7c5bb09359e70052 resource: repo://TFMTests/ResponsivePageLayoutTests.swift -generated: { by: "omp", at: "2026-09-22T02:51:54.212Z" } + - id: openwiki-source-2891f940e682279163740c83 + resource: repo://TFMTests/TerminalFontRenderingTests.swift + - id: openwiki-source-9442022b3dba1be62a55f0f0 + resource: repo://TFMTests/TerminalThemeTests.swift +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T09:51:39.958Z + at: 2026-09-23T00:14:42.075Z --- # UI Design Principles and Shared Presentation Boundaries @@ -154,6 +184,16 @@ Both Host editors reuse `HostOrganizationSection`, `HostProtocolProfileControls` The data path is draft binding → shared fields → platform validation/save action. It is not shared fields → database or transport. +### Terminal preferences: shared fields, native adapters + +`TerminalFontFields` receives a `TerminalFontPreferences` binding and an app-supplied family list. Both apps reuse its family and Follow Theme/Custom controls and the 8–36 size-editing range; macOS uses a native `Slider`, while iOS uses a `Stepper`. Font discovery remains app-owned: AppKit/UIKit catalogs include a family when any loadable member is monospaced, sort with `localizedStandardCompare`, and refresh on settings appearance and active-scene transitions. The picker previews available families and retains an unavailable family name; resolving a renderable copy does not rewrite the saved choice. + +Typography is device-local through `TerminalFontSettings`/`@AppStorage`, separate from synchronized `TerminalPreferences`. Theme edits go through `TerminalSettingsModel`, which persists before publishing new preferences and exposes storage errors on failure. Sharing these fields does not move persistence or native font enumeration into the shared view. + +`TerminalThemePicker` uses the shared appearance-filtered, name-sorted catalog. Missing, empty, or wrong-appearance selections remain selected values, with a localized fallback hint; the renderer chooses the matching default without saving a replacement merely because Settings appeared. Custom configuration overlays the selected base theme, and explicit local font overrides apply last. The base picker remains editable while the overlay is enabled. Editor workflows remain native: desktop configuration editing autosaves and supports file import; mobile uses a draft sheet with explicit Save. + +Focused tests cover complete macOS monospaced-family membership, typography precedence and controller identity, appearance filtering and fallback, base-theme changes beneath an overlay, and opening the actual desktop settings page without a preference write. Mobile rendering cases exercise the font controls in English, Simplified Chinese and Traditional Chinese. + `SingleLineTextInput` illustrates a narrow shared policy. It replaces runs of newline scalars with one space, preserves attributed paste content, and normalizes only edits through the binding setter. Reading the original bound value does not rewrite stored data. This is not a general sanitizer for secrets, multiline commands, snippets or existing repository records. Native paste/input insertion is supplied by app-owned environment adapters so selection and input-method handling can stay platform-specific. ## 6. Native rendering is a deliberate workload boundary diff --git a/openwiki/integrations/mcp-server-and-bridge.md b/openwiki/integrations/mcp-server-and-bridge.md index 0f958362..e79d90e4 100644 --- a/openwiki/integrations/mcp-server-and-bridge.md +++ b/openwiki/integrations/mcp-server-and-bridge.md @@ -43,7 +43,7 @@ sources: generated: { by: "openwiki/0.5.2", at: "2026-09-22T00:31:10.149Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # MCP Server, Transfer APIs, and Stdio Bridge diff --git a/openwiki/operations/cloudkit-release-readiness.md b/openwiki/operations/cloudkit-release-readiness.md index 30f084ea..38390fe0 100644 --- a/openwiki/operations/cloudkit-release-readiness.md +++ b/openwiki/operations/cloudkit-release-readiness.md @@ -4,6 +4,8 @@ title: CloudKit Schema Deployment and Signed-Device Release Readiness description: Production CloudKit schema, compatibility, and signed-device checks for Vault, Settings, SFTP preferences, Metrics, and Keychain-backed secrets. tags: [cloudkit, sync, release, settings, vault, metrics, sftp] sources: + - id: openwiki-source-7ca30f0e781e8c2df826b47a + resource: repo://Frameworks/TermindApplication/HistoryLimits.swift - id: openwiki-source-9fbc733f79d51a7265ee9e4a resource: repo://Frameworks/TermindApplication/SettingsKey.swift - id: openwiki-source-46ef2fcd9e3aba751015021b @@ -30,6 +32,8 @@ sources: resource: repo://Frameworks/TermindPersistence/SettingsSyncJournal.swift - id: openwiki-source-bc351b6c699ff4bbf9f8c2b1 resource: repo://Frameworks/TermindPersistence/SwiftDataVaultJournal.swift + - id: openwiki-source-a0d614d618b11f103ec246ae + resource: repo://Frameworks/TermindPersistence/SwiftDataVaultStore.swift - id: openwiki-source-3cfc52217a7a6835b4bffac1 resource: repo://Frameworks/TermindPersistence/VaultSyncPersistence.swift - id: openwiki-source-bf7a64e7c819eb9279e2f9e4 @@ -54,10 +58,10 @@ sources: resource: repo://TFM/TFM.entitlements - id: openwiki-source-960109cd5bc0de7a879c1e16 resource: repo://TFMTests/VaultSyncTests.swift -generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # CloudKit Schema Deployment and Signed-Device Release Readiness @@ -73,12 +77,14 @@ Validate the Development schema first, then explicitly deploy it to Production. | Category | Zone | Record type | Subscription | | --- | --- | --- | --- | | Settings and AI configuration | `TermindAIConfigurationV1` | `AIConfigurationItem` | `termind-ai-configuration-v1-changes` | +| SSH preferences | `TermindAIConfigurationV1` | `SSHPreferencesItem` | `termind-ai-configuration-v1-changes` | | SFTP preferences | `TermindAIConfigurationV1` | `SFTPPreferencesItem` | `termind-ai-configuration-v1-changes` | +| Workspace preferences | `TermindAIConfigurationV1` | `WorkspacePreferencesItem` | `termind-ai-configuration-v1-changes` | | Metrics | `TermindMetricsV1` | `MetricsItem` | `termind-metrics-v1-changes` | Vault records use the same private container through their own coordinator and compatibility contract. A Vault envelope stores integer `formatVersion` and `counter`, Boolean `isDeleted`, strings `kind`, `entityID`, `writerID`, and `payloadHash`, date `modifiedAt`, inline bytes `payload`, or `payloadAsset` for payloads above the 700,000-byte inline limit. Preserve zone names, record types, subscription identifiers, envelope fields, and format compatibility: older clients must be able to ignore the distinct `SFTPPreferencesItem` record type before attempting to decode it. -Connection-history events and the reusable device directory use separate `connectionHistory` and `device` payload kinds inside the existing Vault `VaultItem` record type; neither requires a new CloudKit zone, record type, field or Production schema deployment. Release validation still needs mixed-version coverage because older clients cannot decode those payload kinds safely until the quarantine-capable build is deployed. On signed devices verify event start/end updates and tombstones, local-only Recents versus all-device History, and deterministic convergence to the newest 300 events. Independently rename a device and verify that one `device` update changes every history row resolving its stable ID, while event pruning, per-Host deletion and Clear History leave device records intact. +Connection-history events and the reusable device directory use separate `connectionHistory` and `device` payload kinds inside the existing Vault `VaultItem` record type; neither requires a new CloudKit zone, record type, field or Production schema deployment. Release validation still needs mixed-version coverage because older clients cannot decode those payload kinds safely until the quarantine-capable build is deployed. On signed devices verify event start/end updates and tombstones, local-only Recents versus all-device History, and deterministic convergence to the newest 250 events. Independently rename a device and verify that one `device` update changes every history row resolving its stable ID, while event pruning, per-Host deletion and Clear History leave device records intact. There is no public-database access in this design. Before release, verify the app entitlements, the private-container Development schema, and the deployed Production schema with credentials that actually have CloudKit management access. An unavailable management token or a successful local test is not deployment evidence. @@ -106,7 +112,7 @@ For a release check, change 30/10 to 100/50, close Settings, and verify that ano ## First-stage mixed-version safety -Local schema V31 adds replica-scoped quarantine and recovery checkpoints. It does **not** add a CloudKit record type, change the existing zones, or raise the V1 envelope formats. Treat local database migration and cloud schema deployment as separate decisions. +Local schema V31 adds replica-scoped quarantine and recovery checkpoints. The current V34 schema retains that safety storage and adds the connection-history/device shapes through the direct V31→V34 migration. Those local changes do **not** add a Vault record type, change the existing zones, or raise the V1 envelope formats. Treat local database migration and cloud schema deployment as separate decisions; the distinct Settings record types in the inventory still require deployment before use. The compatibility contract is now fail-closed for records a client detects it cannot safely understand: keep the received bytes and local pending edits, remove that record from upload scheduling, and recheck the final batch/provider. Other compatible records remain eligible. Unsupported fetches and `serverRecordChanged` replies share this path. Only successful supported-data reconciliation clears the restriction; pressing Retry, pausing a category or restarting does not discard it. diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index e70257b6..e4d14f62 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -25,7 +25,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:23:14.035Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Termind Engineering Quickstart diff --git a/openwiki/testing/behavioral-boundaries-and-fixtures.md b/openwiki/testing/behavioral-boundaries-and-fixtures.md index 2783da4d..9945125b 100644 --- a/openwiki/testing/behavioral-boundaries-and-fixtures.md +++ b/openwiki/testing/behavioral-boundaries-and-fixtures.md @@ -44,10 +44,10 @@ sources: resource: repo://TFMTests/VaultSyncTests.swift - id: openwiki-source-a08489338be00125d646c77e resource: repo://TFMTests/VisibleWindowTestTrait.swift -generated: { by: "codex", at: "2026-09-22T11:34:21.807Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Testing Strategy and Behavioral Boundaries @@ -94,9 +94,9 @@ The safety fixture substitutes only the CloudKit transport. It uses the producti Disk-backed tests separately migrate a V30 store to V31, import legacy quarantine, reopen interrupted state and verify that journal intent and recovery flags survive. Scope tests vary container, environment, account, zone and owner independently. Lifecycle tests hold fetches and inject persistence/lookup failures, exercise overlapping explicit operations and automatic callbacks, and prove that an old or failed engine cannot reopen upload authorization. -Connection-history coverage adds a V31-to-V32 disk migration, Vault codec/merge/tombstone tests, event start/end lifetime checks, stable local-device filtering and deterministic retention of 300 stored events. Migration and sync tests also verify the independent generic device directory: old events receive the installation ID, a separate `DeviceRecord` is created, and changing that record's name updates resolution without rewriting events. Desktop tests distinguish local Welcome Recents from the 100-row all-device sidebar projection. Mobile tests distinguish local per-Host Recents and host-only quick actions from the 100-event Vault History page, including preferred SSH/Telnet activation, disabled protocol actions, device-name search and multilingual light/dark screenshots. Serial remains outside the connection-history enum and mobile workspace routes. +Connection-history coverage adds a V31-to-V34 disk migration, Vault codec/merge/tombstone tests, event start/end lifetime checks, stable local-device filtering and deterministic retention of 250 stored events. Migration and sync tests also verify the independent generic device directory: old events receive the installation ID, a separate `DeviceRecord` is created, and changing that record's name updates resolution without rewriting events. Desktop tests distinguish local Welcome Recents from the 100-row all-device sidebar projection. Mobile tests distinguish local per-Host Recents and host-only quick actions from the 50-event Vault History page, including preferred SSH/Telnet activation, disabled protocol actions, device-name search and multilingual light/dark screenshots. Serial remains outside the connection-history enum and mobile workspace routes. -Compatibility coverage continues through V33: the V32-to-V33 fixture verifies that removing Identity usage metadata preserves Identity configuration and its independent Vault journal modification date. +The same direct V31-to-V34 fixture verifies that removing Identity usage metadata preserves Identity configuration and its independent Vault journal modification date, and that workspace preferences bootstrap with defaults. V32 and V33 are not distributed migration endpoints. These tests do not prove unknown-field round trips through an actual historical binary, Production schema deployment, or signed-device delivery. Keep those checks in [CloudKit Release Readiness](../operations/cloudkit-release-readiness.md); a fake transport passing is not evidence that iCloud has delivered a record. @@ -123,7 +123,7 @@ Those tests are intentionally actor-driven and use polling only to wait for obse External connection URLs are another ownership boundary: opening one creates an ephemeral workspace and does not create a Vault host or saved workspace descriptor. Conversely, closing all workspaces disconnects each live workspace and is repeatable. Background-expiration tests provide a controlled connection service and cover resume intent, foreground recovery, and rejection of retired callbacks. A lifecycle change must preserve the appropriate one of these contracts rather than merely asserting that a view updates. -`MobileWorkspaceStoreTests` protects the device-local restoration format. The store contains connection intent only—workspace descriptor identity, host reference, title, kind, and duplicate index—not credentials, commands, or live session state. It migrates legacy arrays into the versioned snapshot while allocating missing duplicate indices deterministically by host and kind. If bytes are malformed, unknown-version, or invalid, loading and later saving fail while the original bytes remain untouched; a successful load is required before replacement. Add a fixture for every format transition and for corruption behavior, because silently replacing unreadable restoration data with an empty list loses recoverable user state. +`MobileWorkspaceStoreTests` protects the device-local restoration format. The store contains connection intent only—workspace descriptor identity, host reference, title and kind—not credentials, commands, duplicate indices or live session state. It migrates legacy arrays into the versioned snapshot while preserving IDs, titles and kinds and discarding legacy indices. If bytes are malformed, unknown-version, or invalid (including duplicate IDs), loading and later saving fail while the original bytes remain untouched; a successful load is required before replacement. Add a fixture for every format transition and for corruption behavior, because silently replacing unreadable restoration data with an empty list loses recoverable user state. ## Fixture design rules diff --git a/openwiki/workflows/settings-and-cloud-sync.md b/openwiki/workflows/settings-and-cloud-sync.md index 4605b86a..4b770e50 100644 --- a/openwiki/workflows/settings-and-cloud-sync.md +++ b/openwiki/workflows/settings-and-cloud-sync.md @@ -4,6 +4,10 @@ title: Settings, Vault, Metrics, and Cloud Sync Workflow description: How Termind independently synchronizes Vault, Settings, and Metrics through CloudKit, including lifecycle gates, durable journals, account and environment transitions, conflict handling, and AI-secret upload authorization. tags: [cloudkit, synchronization, settings, vault, metrics, secrets, swiftdata] sources: + - id: openwiki-source-dc7b363138921b0923d2dc2f + resource: repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift + - id: openwiki-source-7ca30f0e781e8c2df826b47a + resource: repo://Frameworks/TermindApplication/HistoryLimits.swift - id: openwiki-source-9fbc733f79d51a7265ee9e4a resource: repo://Frameworks/TermindApplication/SettingsKey.swift - id: openwiki-source-1429ec01ac68a7770ce41977 @@ -62,10 +66,10 @@ sources: resource: repo://TFI/App/MobileAppModel.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f resource: repo://TFM/App/AppModel.swift -generated: { by: "codex", at: "2026-09-22T11:34:21.807Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # Settings, Vault, Metrics, and Cloud Sync Workflow @@ -79,7 +83,7 @@ This workflow is CloudKit replication built around a SwiftData journal, not Swif | Category | Coordinator and zone | Content boundary | Default control | | --- | --- | --- | --- | | Vault | `VaultSyncCoordinator`; `TermindVaultV1`; `VaultItem` | Hosts, identities, SSH-key references, known hosts, snippets, forwarding rules, workspace templates, connection-history events, and the reusable device directory | `vault.sync.enabled` plus `icloud.sync.enabled` | -| Settings | `SettingsSyncCoordinator`; `TermindAIConfigurationV1`; `AIConfigurationItem`, `SSHPreferencesItem`, and `SFTPPreferencesItem` | AI configuration, skills, search, terminal, SSH, and SFTP preferences—not credential material | `settings.sync.enabled` plus `icloud.sync.enabled` | +| Settings | `SettingsSyncCoordinator`; `TermindAIConfigurationV1`; `AIConfigurationItem`, `SSHPreferencesItem`, `SFTPPreferencesItem`, and `WorkspacePreferencesItem` | AI configuration, skills, search, terminal, SSH, SFTP, and workspace preferences—not credential material | `settings.sync.enabled` plus `icloud.sync.enabled` | | Metrics | `MetricsSyncCoordinator`; `TermindMetricsV1`; `MetricsItem` | Persisted metrics selections, not samples or active monitoring | `metrics.sync.enabled` plus `icloud.sync.enabled` | All master and category switches default on. A category is enabled only when the master `SettingsKey.iCloudSyncEnabled` and that category’s key are enabled; an absent preference uses its category default. `SettingsKey.migrateSyncPreferences(in:)` migrates the old Settings selection and derives an initial master choice without discarding explicit opt-outs. @@ -139,9 +143,9 @@ On fetch, coordinators process only their zone and accepted record types. They d Merge is deterministic: newer versions win; an older remote value causes the local value to be requeued. For equal versions whose payloads differ, encoded bytes provide a deterministic tie-breaker. Settings and Metrics additionally collapse multiple fetched versions of one key before deciding, so batch delivery order does not affect the result. A `serverRecordChanged` save failure runs the server record through the same validation path as a fetch. A supported envelope either establishes an identical local version as uploaded or requeues the winning local result; an unsupported envelope is quarantined instead of silently retrying a stale local payload. Successful sends clear `needsUpload` only if the journal still holds the sent version, so a newer local edit cannot be incorrectly acknowledged. -Vault projections deliberately remove device-local presentation metadata from envelopes: group and package expansion is normalized rather than synchronized. The live V33 Identity shape no longer contains `lastUsedAt` usage metadata. Settings likewise excludes explicitly device-local defaults such as debug/logging, review history, and terminal font family/size/follow-theme-size. Settings record-type compatibility is intentional: SSH and SFTP preferences use distinct types in the Settings zone so older clients can ignore newer records before decoding. +Vault projections deliberately remove device-local presentation metadata from envelopes: group and package expansion is normalized rather than synchronized. The live V33 Identity shape no longer contains `lastUsedAt` usage metadata. Settings likewise excludes explicitly device-local defaults such as debug/logging, review history, and terminal font family/size/follow-theme-size. Settings record-type compatibility is intentional: SSH, SFTP and workspace preferences use distinct types in the Settings zone so older clients can ignore newer records before decoding. -Connection history is an exception to “configuration-only Vault” by design: each event is an independent `connectionHistory` Vault key using the same `VaultItem` record type and V1 envelope. Existing local events bootstrap even after the original Vault bootstrap completed. Insert, end-time update, deletion and clear reconcile history rows with the journal in one transaction; logical deletions use ordinary envelope tombstones. Remote events may arrive before their referenced Host and remain stored but hidden until that Host exists. After local or remote merge, deterministic start-time/UUID ordering retains the newest 300 and journals tombstones for the overflow, allowing devices to converge. Platform views then choose a stable-source-device local projection or the complete synchronized event stream. +Connection history is an exception to “configuration-only Vault” by design: each event is an independent `connectionHistory` Vault key using the same `VaultItem` record type and V1 envelope. Existing local events bootstrap even after the original Vault bootstrap completed. Insert, end-time update, deletion and clear reconcile history rows with the journal in one transaction; logical deletions use ordinary envelope tombstones. Remote events may arrive before their referenced Host and remain stored but hidden until that Host exists. After local or remote merge, deterministic start-time/UUID ordering retains the newest 250 and journals tombstones for the overflow, allowing devices to converge. Launch-time pruning can discard overflow journal rows that never reached CloudKit; previously uploaded overflow receives tombstones. Platform views then choose a stable-source-device local projection or the complete synchronized event stream. The reusable device directory is a separate replication stream in the same Vault category. Each `DeviceRecord` uses a `.device` payload keyed by the stable installation ID; its repository upsert and journal envelope commit together independently of the event repository. Platform activation refreshes the current system display name, and app models invalidate the directory cache when remote `.device` keys arrive. Device reconciliation creates or updates records but does not tombstone mappings merely because they are absent locally, and connection-event retention, per-Host deletion, or Clear History never removes them. @@ -152,7 +156,7 @@ A scoped quarantine entry is an upload restriction, not only a diagnostic. It re There are three outbound checks: 1. **Journal eligibility:** return only unblocked pending names; keep the blocked row's `needsUpload` intent unchanged. -2. **Restored queue and batch:** remove blocked names from the engine's scheduling state, reject physical deletes, and synchronously materialize only eligible saves. A batch uses no suspension between its final checks and handoff. +2. **Restored queue and batch:** remove blocked names from the engine's scheduling state, reject physical deletes, and synchronously materialize at most 250 eligible saves per request. Invalid or blocked pending entries do not consume that record limit. A batch uses no suspension between its final checks and handoff. 3. **Final record provider:** check current-engine readiness and the scoped restriction again, including the journal's outgoing-entry lookup. Lookup or persistence failure closes the category gate rather than treating an error as an empty restriction set. An acknowledgement for an already blocked record does not clear its local intent. Local tombstones and console-deletion recreation follow the same restriction; neither can overwrite unknown remote data. Other records in the category can keep syncing, including while the UI reports a partial restriction. Local editing remains possible, but the pending change is not represented as successfully synchronized. diff --git a/openwiki/workflows/ssh-session-lifecycle.md b/openwiki/workflows/ssh-session-lifecycle.md index 45fb868a..bae784a6 100644 --- a/openwiki/workflows/ssh-session-lifecycle.md +++ b/openwiki/workflows/ssh-session-lifecycle.md @@ -24,6 +24,8 @@ sources: resource: repo://FrameworkTests/SSHConnectionPreparationTests.swift - id: openwiki-source-2635e6aecc48e14adbb07b91 resource: repo://FrameworkTests/SSHHostKeyAuthorizationTests.swift + - id: openwiki-source-906678adb91c2fce2d3fcaed + resource: repo://TFI/App/MobileAppModel.swift - id: openwiki-source-492f738896225c6b3acf7d2f resource: repo://TFI/Features/Workspaces/MobileWorkspace.swift - id: openwiki-source-350d4a794cfef67e406ddf9b @@ -32,14 +34,16 @@ sources: resource: repo://TFITests/MobileAppTests.swift - id: openwiki-source-9b78a7e39bfa08bea5192722 resource: repo://TFITests/MobileWorkspaceStoreTests.swift + - id: openwiki-source-6570374eaac2e0278fa84640 + resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-22b9fca540c3029c5ace451e + resource: repo://TFM/Features/Workspaces/WindowModel.swift - id: openwiki-source-85c3f954351f96433cb1fb4b resource: repo://TFMTests/ConnectionJourneyTests.swift - - id: openwiki-source-578f30372fe67f8688f44b76 - resource: repo://TFMTests/WindowStateTests.swift -generated: { by: "omp", at: "2026-09-22T02:21:47.705Z" } +generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T00:14:42.075Z --- # SSH, Telnet, Trust, and Workspace Session Lifecycle @@ -167,7 +171,7 @@ The platform rules intentionally differ: | Situation | Desktop macOS | Mobile iOS and iPadOS | | --- | --- | --- | -| Window or app restoration | Native scene/frame restoration restores window presentation, not live Workspace transports. Dynamic Workspaces are window-owned and are not reconnected simply because a window is restored. | `mobile.workspaces.v1` stores only ordered local descriptors such as workspace ID, Host ID, kind, title, and duplicate index. Cold launch shows Recent entries but creates no sessions, history, or network activity. | +| Window or app restoration | Native scene/frame restoration restores window presentation, not live Workspace transports. Dynamic Workspaces are window-owned and are not reconnected simply because a window is restored. | `mobile.workspaces.v1` stores only ordered local descriptors containing workspace ID, Host ID, kind and title. Cold launch shows saved descriptors but creates no sessions, history, or network activity. | | Reopen/select | Mounting a preserved desktop workspace does not repeat authentication; explicit retry or reopen may. Moving a workspace preserves its surfaces, transport, splits, and state. | Opening a descriptor reuses or registers one live workspace across scenes and makes at most one initial connection attempt; a failure needs explicit retry. Peer switching retains the live workspace. | | Background/foreground | Desktop quit/close follows owned-session teardown; independent transfers and services have their own lifetime. | Background expiration closes transports but retains workspace intent. Foreground resumes existing workspaces when their recorded foreground-resume intent is set. | | Explicit close | The owning window closes its own sessions; closing a pane removes empty workspaces without transferring ownership. | Explicit close removes the live and recent entry and never retries it. | From f79c4905d0f7f1e5d2eee6608bd488d3ef3264a7 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 09:33:15 +0800 Subject: [PATCH 03/30] Clarify agent tool availability contracts --- FrameworkTests/AgentSteeringTests.swift | 23 ++- .../Agent/AssistantExecutionController.swift | 26 +-- .../Agent/ConversationModel.swift | 8 +- .../Agent/AgentBuiltInSSHTools.swift | 10 +- .../Agent/AssistantToolRegistry.swift | 4 +- .../Agent/TermindAgentTools.swift | 3 - .../Agent/MobileAssistantModel.swift | 19 +- TFITests/MobileAssistantTests.swift | 47 ++++- TFM/App/AppModel.swift | 22 +-- .../MCPServer/MCPServerTools.swift | 2 +- .../AI/ConversationTranscriptView.swift | 6 +- TFM/Features/AI/ConversationViews.swift | 9 +- TFM/Features/Assistant/AssistantView.swift | 4 +- TFM/Features/Workspaces/WindowModel.swift | 4 +- .../AgentBuiltInToolAvailabilityTests.swift | 171 +++++++++++++++--- TFMTests/AgentHostProtocolListingTests.swift | 20 +- TFMTests/MCPServerToolsTests.swift | 33 +++- 17 files changed, 322 insertions(+), 89 deletions(-) diff --git a/FrameworkTests/AgentSteeringTests.swift b/FrameworkTests/AgentSteeringTests.swift index de047989..1c37fbfe 100644 --- a/FrameworkTests/AgentSteeringTests.swift +++ b/FrameworkTests/AgentSteeringTests.swift @@ -188,8 +188,11 @@ struct AgentSteeringTests { @Test func steeringCapturesTheSendingTerminalAndDoesNotChangeTheRunningContextUntilDelivery() { let conversation = ConversationModel() + let memorySessionID = UUID() let first = AgentTurnContextSnapshot( - sessionContext: "host A", skillCatalog: "fixed skills", planContract: "fixed mode") + sessionContext: "host A", skillCatalog: "fixed skills", planContract: "fixed mode", + toolAvailability: ["terminal_suggest": false, "future_surface_tool": false], + memorySessionID: memorySessionID) conversation.start( runtime: AgentRuntime( model: HangingAgentModel(), tools: [], @@ -201,18 +204,34 @@ struct AgentSteeringTests { conversation.composer.draft = "inspect here" conversation.submitSteeringDraft( context: SessionContext(hostName: "B", workingDirectory: "/srv"), - includesTerminalSuggestion: true) + terminalSuggestionsAvailable: true) let queued = try! #require(conversation.pendingSteering.first?.message) #expect(conversation.activeContextSnapshot == first) #expect(queued.contextSnapshot?.sessionContext?.contains("host_name: B") == true) #expect(queued.contextSnapshot?.skillCatalog == first.skillCatalog) #expect(queued.contextSnapshot?.planContract == first.planContract) + #expect(queued.contextSnapshot?.memorySessionID == memorySessionID) + #expect(queued.contextSnapshot?.toolAvailability?["terminal_suggest"] == true) + #expect(queued.contextSnapshot?.toolAvailability?["future_surface_tool"] == false) conversation.handle(.steeringDelivered(queued)) #expect(conversation.activeContextSnapshot == queued.contextSnapshot) #expect(conversation.transcript.last?.text == "inspect here") conversation.stop() } + @Test + func steeringKeepsLegacyUnknownContextUnknown() { + let conversation = ConversationModel() + startRun(conversation) + #expect(conversation.activeContextSnapshot == nil) + conversation.composer.draft = "inspect here" + + conversation.submitSteeringDraft(terminalSuggestionsAvailable: true) + + #expect(conversation.pendingSteering.first?.message.contextSnapshot == nil) + conversation.stop() + } + private func startRun(_ conversation: ConversationModel) { conversation.start( runtime: AgentRuntime( diff --git a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift index 3372dc45..6b2505c0 100644 --- a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift +++ b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift @@ -7,7 +7,7 @@ import TermindDomain @MainActor public final class AssistantExecutionController { public typealias RegistryFactory = - @MainActor (ConversationModel, AIModel, ModelProvider, AgentSkillCatalog, AgentEventChannel, Bool) -> + @MainActor (ConversationModel, AIModel, ModelProvider, AgentSkillCatalog, AgentEventChannel) -> AgentToolRegistry private let ai: AIConfigurationModel private let skills: SkillLibraryModel @@ -46,7 +46,7 @@ public final class AssistantExecutionController { public func sendAgentDraft( _ conversation: ConversationModel, context: SessionContext = .none, - includesTerminalSuggestion: Bool = false + terminalSuggestionsAvailable: Bool = false ) { guard authorize() else { return } let draft = conversation.composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) @@ -62,7 +62,7 @@ public final class AssistantExecutionController { let prompt = conversation.appendUserTurnContent( draft, contextSnapshot: contextSnapshot( - for: conversation, context: context, includesTerminalSuggestion: includesTerminalSuggestion)) + for: conversation, context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable)) conversation.composer.draft = "" // Compaction happens *after* the turn is recorded and before the run, // so the message the user just typed is inside the kept window rather @@ -90,7 +90,7 @@ public final class AssistantExecutionController { conversation, promptID: prompt.id, prompt: prompt.text, authoredPrompt: prompt.authoredText, promptImages: prompt.images, promptContextAttachments: prompt.contextAttachments, context: context, - includesTerminalSuggestion: includesTerminalSuggestion + terminalSuggestionsAvailable: terminalSuggestionsAvailable ) } } @@ -103,7 +103,7 @@ public final class AssistantExecutionController { promptImages: [AgentImageAttachment]? = nil, promptContextAttachments: [AgentContextAttachment]? = nil, context: SessionContext? = nil, - includesTerminalSuggestion: Bool = false + terminalSuggestionsAvailable: Bool = false ) { guard authorize(), !conversation.isStreaming, !conversation.isArchived else { return } guard preparingMemory.insert(conversation.id).inserted else { return } @@ -120,14 +120,15 @@ public final class AssistantExecutionController { performStartAgentRun( conversation, promptID: promptID, prompt: prompt, authoredPrompt: authoredPrompt, promptImages: promptImages, promptContextAttachments: promptContextAttachments, - context: context, includesTerminalSuggestion: includesTerminalSuggestion, memorySession: session) + context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable, memorySession: session + ) } } else { preparingMemory.remove(conversation.id) performStartAgentRun( conversation, promptID: promptID, prompt: prompt, authoredPrompt: authoredPrompt, promptImages: promptImages, promptContextAttachments: promptContextAttachments, - context: context, includesTerminalSuggestion: includesTerminalSuggestion) + context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) } } @@ -139,7 +140,7 @@ public final class AssistantExecutionController { promptImages: [AgentImageAttachment]? = nil, promptContextAttachments: [AgentContextAttachment]? = nil, context: SessionContext? = nil, - includesTerminalSuggestion: Bool = false, + terminalSuggestionsAvailable: Bool = false, memorySession: (context: AgentMemoryContext, access: any AgentMemoryAccessing)? = nil ) { guard authorize() else { return } @@ -225,7 +226,7 @@ public final class AssistantExecutionController { ? skills.catalog : AgentSkillCatalog() let baseRegistry = makeRegistry( - conversation, chat.model, chat.provider, skillCatalog, channel, includesTerminalSuggestion) + conversation, chat.model, chat.provider, skillCatalog, channel) let skillRegistry = AgentToolCatalog.registry( builtIn: .init( groups: [.skills, .memory], skills: skillCatalog, skillInventory: skills.inventory, @@ -288,7 +289,8 @@ public final class AssistantExecutionController { let frozenContext = lastUserTurn?.contextSnapshot ?? contextSnapshot( - for: conversation, context: context ?? .none, includesTerminalSuggestion: includesTerminalSuggestion) + for: conversation, context: context ?? .none, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) let isPlanning = frozenContext.planContract != nil // The catalog reaches the model boundary through the pipeline rather // than through the system prompt, so the prompt stays byte-identical for @@ -384,7 +386,7 @@ public final class AssistantExecutionController { } public func contextSnapshot( - for conversation: ConversationModel, context: SessionContext, includesTerminalSuggestion: Bool + for conversation: ConversationModel, context: SessionContext, terminalSuggestionsAvailable: Bool ) -> AgentTurnContextSnapshot { let catalog = ai.resolve(conversation: conversation.model)?.model.abilities.contains(.toolCall) == true @@ -393,7 +395,7 @@ public final class AssistantExecutionController { sessionContext: context.modelContext, skillCatalog: catalog.isEmpty ? nil : catalog.catalogBlock, planContract: conversation.isPlanning ? TermindAgentPolicy.planContract : nil, - toolAvailability: ["terminal_suggest": includesTerminalSuggestion], + toolAvailability: ["terminal_suggest": terminalSuggestionsAvailable], memorySessionID: memory?.isEnabled == true ? (conversation.transcript.first(where: { $0.contextSnapshot?.memorySessionID != nil })? .contextSnapshot?.memorySessionID ?? conversation.id) : nil diff --git a/Frameworks/TermindApplication/Agent/ConversationModel.swift b/Frameworks/TermindApplication/Agent/ConversationModel.swift index 9083a8ed..cf5ec485 100644 --- a/Frameworks/TermindApplication/Agent/ConversationModel.swift +++ b/Frameworks/TermindApplication/Agent/ConversationModel.swift @@ -1958,12 +1958,16 @@ public final class ConversationModel: Identifiable { /// later be shown a conversation that never happened. The bubble appears /// when the runtime reports the message delivered, which is where the model /// really saw it. - public func submitSteeringDraft(context: SessionContext = .none, includesTerminalSuggestion: Bool = false) { + public func submitSteeringDraft(context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false) { let text = composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) guard canSendDraft, let runtime else { return } let composed = composed(text) var snapshot = activeContextSnapshot?.withSessionContext(context.modelContext) - snapshot?.toolAvailability = ["terminal_suggest": includesTerminalSuggestion] + if snapshot != nil { + var availability = snapshot?.toolAvailability ?? [:] + availability["terminal_suggest"] = terminalSuggestionsAvailable + snapshot?.toolAvailability = availability + } let pending = PendingSteeringMessage( draft: text, attachments: composer.attachments, message: AgentTranscriptMessage( diff --git a/Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift b/Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift index e0668d37..01d938ff 100644 --- a/Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift +++ b/Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift @@ -13,9 +13,10 @@ nonisolated public struct ListHostsTool: AgentToolDefinition, AgentToolSchemaBui Self.descriptor( "list_hosts", """ - List Vault Hosts and temporary terminal targets without credentials, including each target's enabled \ - protocol list. SSH and SFTP tools require a returned protocol entry named ssh; Telnet targets are \ - discoverable but no Agent Telnet execution tool is currently available. + List Vault Hosts and temporary terminal targets without credentials, even when no session context \ + is available, including each target's enabled protocol list. SSH and SFTP tools require a returned \ + protocol entry named ssh; Telnet targets are discoverable but no Agent Telnet execution tool is currently \ + available. """, properties: [:], required: [], target: .host, approvalPolicy: .approve, concurrency: .parallel, @@ -132,7 +133,8 @@ nonisolated public struct TerminalSuggestTool: AgentToolDefinition, AgentToolSch """ Create a command card when the user wants a suggestion or needs to interact with a PTY. \ Only the user can insert or run the card. This tool does not execute commands or return \ - execution output. To run a remote command yourself, use ssh_exec. + execution output, and the sending surface may mark it unavailable at execution time. To run a remote \ + command yourself, use ssh_exec. """, properties: [ "command": Self.string(max: 32_768), diff --git a/Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift b/Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift index 45c979ca..01839111 100644 --- a/Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift +++ b/Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift @@ -11,8 +11,7 @@ public enum AssistantToolRegistry { skills skillCatalog: AgentSkillCatalog, channel: AgentEventChannel, ai: AIConfigurationModel, hosts: AgentHostResolver, metrics: any MetricsSnapshotReading, connections: AgentConnectionPool, - authentication: AgentSSHAuthenticationRegistry, background: AgentBackgroundCommands, - includesTerminalSuggestion: Bool = false + authentication: AgentSSHAuthenticationRegistry, background: AgentBackgroundCommands ) -> AgentToolRegistry { let native = ai.webSearch.isEnabled && ai.webSearch.prefersBuiltIn && provider.supportsNativeWebSearch(model: model) @@ -60,7 +59,6 @@ public enum AssistantToolRegistry { hosts: hosts, metrics: metrics, connections: connections, authentication: authentication, background: background, - includesTerminalSuggestion: includesTerminalSuggestion, workspace: scratch )) ) diff --git a/Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift b/Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift index c265c57a..45d8fd02 100644 --- a/Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift +++ b/Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift @@ -15,7 +15,6 @@ nonisolated public struct TermindAgentToolEnvironment: Sendable { public var connections: AgentConnectionPool public var authentication: AgentSSHAuthenticationRegistry public var background: AgentBackgroundCommands - public var includesTerminalSuggestion: Bool /// Shared with the runtime's scratch group. `scratch_pull` and /// `scratch_push` are ours rather than the runtime's precisely because they /// are the two ends that touch a host, and they must stage into the same @@ -28,7 +27,6 @@ nonisolated public struct TermindAgentToolEnvironment: Sendable { connections: AgentConnectionPool, authentication suppliedAuthentication: AgentSSHAuthenticationRegistry? = nil, background: AgentBackgroundCommands = AgentBackgroundCommands(), - includesTerminalSuggestion: Bool = false, workspace: AgentScratchWorkspace? = nil ) { self.metrics = metrics @@ -37,7 +35,6 @@ nonisolated public struct TermindAgentToolEnvironment: Sendable { authentication = suppliedAuthentication ?? AgentSSHAuthenticationRegistry(resolver: hosts) self.background = background - self.includesTerminalSuggestion = includesTerminalSuggestion self.workspace = workspace } } diff --git a/TFI/Application/Agent/MobileAssistantModel.swift b/TFI/Application/Agent/MobileAssistantModel.swift index bd021200..c5295634 100644 --- a/TFI/Application/Agent/MobileAssistantModel.swift +++ b/TFI/Application/Agent/MobileAssistantModel.swift @@ -11,6 +11,8 @@ import TermindRemoteInfrastructure @MainActor @Observable final class MobileAssistantModel { + static let terminalSuggestionsAvailable = false + let store = AssistantConversationStore() let speech: SpeechTranscriptionCoordinator let configuration: AIConfigurationModel @@ -63,7 +65,7 @@ final class MobileAssistantModel { execution = AssistantExecutionController( ai: configuration, skills: skills, repository: repository, memory: memory, authorize: authorize, - makeRegistry: { conversation, model, provider, catalog, channel, _ in + makeRegistry: { conversation, model, provider, catalog, channel in AssistantToolRegistry.make( conversation: conversation, model: model, provider: provider, skills: catalog, channel: channel, ai: configuration, hosts: resolver, metrics: metrics, @@ -116,16 +118,19 @@ final class MobileAssistantModel { !conversation.isCompacting, conversation.composer.pendingAttachments.isEmpty, authorize() else { return } if conversation.isStreaming { - conversation.submitSteeringDraft() + conversation.submitSteeringDraft( + terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) return } guard conversation.canSendDraft else { return } if library.selectedConversation == nil { guard let started = library.startDraftConversation() else { return } configure(library.draftConversation) - execution.startAgentRun(started) + execution.startAgentRun( + started, terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) } else { - execution.sendAgentDraft(conversation) + execution.sendAgentDraft( + conversation, terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) } } @@ -211,12 +216,14 @@ final class MobileAssistantModel { let prompt = try await operations.edit( conversation, messageID: messageID, text: text, contextSnapshot: execution.contextSnapshot( - for: conversation, context: .none, includesTerminalSuggestion: false)) + for: conversation, context: .none, + terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable)) else { return } execution.startAgentRun( conversation, promptID: prompt.id, prompt: prompt.text, authoredPrompt: prompt.authoredText, promptImages: prompt.images, - promptContextAttachments: prompt.contextAttachments) + promptContextAttachments: prompt.contextAttachments, + terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) } } diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 5c844b3a..262124eb 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -224,12 +224,39 @@ struct MobileAssistantTests { #expect(conversation.composer.draft.isEmpty) #expect(conversation.messages.map(\.text).contains("Follow-up question")) + #expect(conversation.transcript.last?.contextSnapshot?.toolAvailability?["terminal_suggest"] == false) #expect(await eventually { conversation.runState == .failed }) #expect(conversation.messages.map(\.role) == [.user, .user]) #expect(conversation.transcript.map(\.role) == [.user, .user]) try expectMissingModelFailure(in: conversation) } + @Test func steeringFromMobileSceneDisablesTerminalSuggestionsInTheQueuedSnapshot() async throws { + let app = try MobileAppModel(inMemory: true) + let assistant = app.assistant.makeSceneModel() + let conversation = ConversationModel() + assistant.library.insertAndSelect(conversation) + conversation.start( + runtime: AgentRuntime( + model: MobileDelayedAgentModel(), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "start", + permissionMode: .askForApproval, + contextSnapshot: .init( + toolAvailability: ["terminal_suggest": true, "future_surface_tool": false])) + ) + conversation.composer.draft = "Inspect again" + + assistant.send() + + let snapshot = try #require(conversation.pendingSteering.first?.message.contextSnapshot) + #expect(snapshot.toolAvailability?["terminal_suggest"] == false) + #expect(snapshot.toolAvailability?["future_surface_tool"] == false) + #expect(await eventually { conversation.runState.isTerminal }) + } + @Test func slashCatalogShowsAlphabetizedCompactPlanAndSkillCommandsOnMobile() throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() @@ -343,12 +370,11 @@ struct MobileAssistantTests { model.abilities = [.toolCall] let provider = ModelProvider(name: "Test") let conversation = ConversationModel() - func registry(_ model: AIModel, includesTerminalSuggestion: Bool = false) -> AgentToolRegistry { + func registry(_ model: AIModel) -> AgentToolRegistry { AssistantToolRegistry.make( conversation: conversation, model: model, provider: provider, skills: app.skills.catalog, channel: channel, ai: app.ai, hosts: hosts, metrics: app.metrics.core, - connections: connections, authentication: authentication, background: background, - includesTerminalSuggestion: includesTerminalSuggestion) + connections: connections, authentication: authentication, background: background) } let mobileDescriptors = registry(model).descriptors let names = Set(mobileDescriptors.map(\.name)) @@ -359,8 +385,6 @@ struct MobileAssistantTests { ] { #expect(names.contains(name), "Missing tool: \(name)") } - // Surface availability is enforced at execution, not by changing the cached tool definitions. - #expect(mobileDescriptors == registry(model, includesTerminalSuggestion: true).descriptors) let exec = try #require(mobileDescriptors.first { $0.name == "ssh_exec" }) #expect(exec.summary.contains("discover host_id with list_hosts")) #expect(exec.summary.contains("do not call request_user_secret for SSH login")) @@ -370,7 +394,6 @@ struct MobileAssistantTests { ) model.abilities = [] #expect(registry(model).descriptors.isEmpty) - #expect(registry(model, includesTerminalSuggestion: true).descriptors.isEmpty) } @Test func backgroundExpirationBlocksNewRunsUntilCleanupCompletes() async throws { @@ -408,6 +431,18 @@ struct MobileAssistantTests { } } +private struct MobileDelayedAgentModel: AgentModelStreaming { + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + try? await Task.sleep(for: .milliseconds(200)) + continuation.yield(.finished(.completed)) + continuation.finish() + } + } + } +} + @MainActor private func expectMissingModelFailure(in conversation: ConversationModel) throws { // In-memory apps have no Chat model. That must produce a failure row, not an authored answer. diff --git a/TFM/App/AppModel.swift b/TFM/App/AppModel.swift index 9a497715..2b3e42af 100644 --- a/TFM/App/AppModel.swift +++ b/TFM/App/AppModel.swift @@ -394,13 +394,13 @@ final class AppModel { @ObservationIgnored private lazy var assistantExecution = AssistantExecutionController( ai: ai, skills: skills, repository: agentRunRepository, memory: memory, authorize: { [weak self] in self?.proAccess.require(.assistant) == true }, - makeRegistry: { [weak self] conversation, model, provider, skills, channel, includes in + makeRegistry: { [weak self] conversation, model, provider, skills, channel in guard let self else { return AgentToolRegistry([AnyAgentTool]()) } return AssistantToolRegistry.make( conversation: conversation, model: model, provider: provider, skills: skills, channel: channel, ai: ai, hosts: agentHosts, metrics: serverMetrics, connections: agentConnections, authentication: agentAuthentication, - background: agentBackgroundCommands, includesTerminalSuggestion: includes) + background: agentBackgroundCommands) }, title: { [weak self] context in guard let self else { throw CancellationError() } @@ -410,23 +410,23 @@ final class AppModel { func sendAgentDraft( _ conversation: ConversationModel, context: SessionContext = .none, - includesTerminalSuggestion: Bool = false + terminalSuggestionsAvailable: Bool = false ) { assistantExecution.sendAgentDraft( - conversation, context: context, includesTerminalSuggestion: includesTerminalSuggestion) + conversation, context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) } func startAgentRun( _ conversation: ConversationModel, promptID: UUID? = nil, prompt: String? = nil, authoredPrompt: String? = nil, promptImages: [AgentImageAttachment]? = nil, promptContextAttachments: [AgentContextAttachment]? = nil, - context: SessionContext? = nil, includesTerminalSuggestion: Bool = false + context: SessionContext? = nil, terminalSuggestionsAvailable: Bool = false ) { assistantExecution.startAgentRun( conversation, promptID: promptID, prompt: prompt, authoredPrompt: authoredPrompt, promptImages: promptImages, promptContextAttachments: promptContextAttachments, - context: context, includesTerminalSuggestion: includesTerminalSuggestion) + context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) } @discardableResult @@ -477,10 +477,10 @@ final class AppModel { fork: { [weak self, weak window] id, messageID, inSidebar in self?.forkConversation(id, at: messageID, selectingIn: window, inSidebar: inSidebar) }, - edit: { [weak self, weak window] id, messageID, text, context, includes in + edit: { [weak self, weak window] id, messageID, text, context, terminalSuggestionsAvailable in self?.editConversation( id, messageID: messageID, text: text, context: context, - includesTerminalSuggestion: includes, reportingTo: window + terminalSuggestionsAvailable: terminalSuggestionsAvailable, reportingTo: window ) } ) @@ -597,7 +597,7 @@ final class AppModel { private func editConversation( _ id: UUID, messageID: UUID, text value: String, - context: SessionContext, includesTerminalSuggestion: Bool, reportingTo window: WindowModel? + context: SessionContext, terminalSuggestionsAvailable: Bool, reportingTo window: WindowModel? ) { guard let conversation = conversation(id) else { return } Task { [weak window] in @@ -607,13 +607,13 @@ final class AppModel { conversation, messageID: messageID, text: value, contextSnapshot: assistantExecution.contextSnapshot( for: conversation, context: context, - includesTerminalSuggestion: includesTerminalSuggestion)) + terminalSuggestionsAvailable: terminalSuggestionsAvailable)) else { return } startAgentRun( conversation, promptID: prompt.id, prompt: prompt.text, authoredPrompt: prompt.authoredText, promptImages: prompt.images, promptContextAttachments: prompt.contextAttachments, - context: context, includesTerminalSuggestion: includesTerminalSuggestion) + context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) } catch { window?.assistantOperationError = error.localizedDescription } } } diff --git a/TFM/Application/MCPServer/MCPServerTools.swift b/TFM/Application/MCPServer/MCPServerTools.swift index a23dd78b..91061e60 100644 --- a/TFM/Application/MCPServer/MCPServerTools.swift +++ b/TFM/Application/MCPServer/MCPServerTools.swift @@ -206,7 +206,7 @@ final class MCPServerTools { AnyAgentTool(MCPDownloadTool(downloads: downloads)), AnyAgentTool(MCPDownloadStatusTool(downloads: downloads)), ] - ).filtering { + ).available(in: .acting).filtering { exportedNames.contains($0.name) && !disabledTools.contains($0.name) } } diff --git a/TFM/Features/AI/ConversationTranscriptView.swift b/TFM/Features/AI/ConversationTranscriptView.swift index 2b6b2ffc..cce59ecd 100644 --- a/TFM/Features/AI/ConversationTranscriptView.swift +++ b/TFM/Features/AI/ConversationTranscriptView.swift @@ -171,7 +171,7 @@ struct ConversationView: View { conversation: conversation, maxContentWidth: isCompact ? nil : Metrics.Chat.contentMaxWidth, sessionContext: sessionContext, - includesTerminalSuggestion: showsTerminalActions, + terminalSuggestionsAvailable: showsTerminalActions, onNewChat: onNewChat, onSend: onSend ) @@ -321,7 +321,7 @@ private struct ConversationTranscriptSurface: View { messageID: id, text: text, context: sessionContext, - includesTerminalSuggestion: showsTerminalActions + terminalSuggestionsAvailable: showsTerminalActions ) }, forkAssistantMessage: { id in @@ -341,7 +341,7 @@ private struct ConversationTranscriptSurface: View { conversation.appendUserTurn(prompt) app.startAgentRun( conversation, prompt: prompt, context: sessionContext, - includesTerminalSuggestion: showsTerminalActions) + terminalSuggestionsAvailable: showsTerminalActions) }, canDeliverCommand: { guard let workspace = window.selectedWorkspace else { return false } diff --git a/TFM/Features/AI/ConversationViews.swift b/TFM/Features/AI/ConversationViews.swift index a80c451c..ac2dae56 100644 --- a/TFM/Features/AI/ConversationViews.swift +++ b/TFM/Features/AI/ConversationViews.swift @@ -506,8 +506,9 @@ struct ComposerView: View { /// Defaulting to `.none` is what makes a composer outside a Workspace — the /// Assistant destination — session-free by construction rather than by remembering. var sessionContext: SessionContext = .none - /// Only the Workspace sidebar gives its agent a command-card tool. - var includesTerminalSuggestion = false + /// The registry stays stable across surfaces; the Workspace sidebar permits + /// command-card execution while standalone Assistant surfaces do not. + var terminalSuggestionsAvailable = false var onNewChat: (() -> Void)? var onSend: (() -> Void)? @@ -992,7 +993,7 @@ struct ComposerView: View { } if conversation.isStreaming { conversation.submitSteeringDraft( - context: sessionContext, includesTerminalSuggestion: includesTerminalSuggestion) + context: sessionContext, terminalSuggestionsAvailable: terminalSuggestionsAvailable) return } if let onSend { @@ -1000,7 +1001,7 @@ struct ComposerView: View { } else { app.sendAgentDraft( conversation, context: sessionContext, - includesTerminalSuggestion: includesTerminalSuggestion + terminalSuggestionsAvailable: terminalSuggestionsAvailable ) } } diff --git a/TFM/Features/Assistant/AssistantView.swift b/TFM/Features/Assistant/AssistantView.swift index 2f94ff4f..fe78a78b 100644 --- a/TFM/Features/Assistant/AssistantView.swift +++ b/TFM/Features/Assistant/AssistantView.swift @@ -213,12 +213,12 @@ struct AssistantPanelView: View { conversation: window.sidebar.assistantDraft, prompt: "Ask anything…", sessionContext: window.sidebar.sessionContext, - includesTerminalSuggestion: true, + terminalSuggestionsAvailable: true, onSend: { if let conversation = window.sendAssistantSidebarDraft() { app.startAgentRun( conversation, context: window.sidebar.sessionContext, - includesTerminalSuggestion: true + terminalSuggestionsAvailable: true ) } } diff --git a/TFM/Features/Workspaces/WindowModel.swift b/TFM/Features/Workspaces/WindowModel.swift index 1c500113..5d005c17 100644 --- a/TFM/Features/Workspaces/WindowModel.swift +++ b/TFM/Features/Workspaces/WindowModel.swift @@ -743,9 +743,9 @@ final class WindowModel { } func editAssistantUserMessage( _ id: ConversationModel.ID, messageID: UUID, text: String, - context: SessionContext = .none, includesTerminalSuggestion: Bool = false + context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false ) { - assistantCommands?.edit(id, messageID, text, context, includesTerminalSuggestion) + assistantCommands?.edit(id, messageID, text, context, terminalSuggestionsAvailable) } @discardableResult diff --git a/TFMTests/AgentBuiltInToolAvailabilityTests.swift b/TFMTests/AgentBuiltInToolAvailabilityTests.swift index 7511a1a4..bcb318c7 100644 --- a/TFMTests/AgentBuiltInToolAvailabilityTests.swift +++ b/TFMTests/AgentBuiltInToolAvailabilityTests.swift @@ -11,11 +11,66 @@ import Testing @testable import Termind +private actor SurfaceToolExecutionLog { + private var names: [String] = [] + + func record(_ name: String) { names.append(name) } + func snapshot() -> Set { Set(names) } +} + +private actor SurfaceAvailabilityModel: AgentModelStreaming { + private var turn = 0 + + private func nextTurn() -> Int { + defer { turn += 1 } + return turn + } + + nonisolated func stream( + _ request: AgentModelRequest + ) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + if await nextTurn() == 0 { + for (index, name) in ["terminal_suggest", "ssh_exec", "sftp_read"].enumerated() { + continuation.yield( + .toolCallSnapshot( + id: "call-\(index)", providerItemID: nil, + name: name, arguments: "{}")) + } + continuation.yield(.finished(.toolCalls)) + } else { + continuation.yield(.textDelta("done")) + continuation.yield(.finished(.completed)) + } + continuation.finish() + } + } + } +} + +private func surfaceTool(_ name: String, log: SurfaceToolExecutionLog) -> AnyAgentTool { + AnyAgentTool( + descriptor: AgentToolDescriptor( + name: name, summary: name, + inputSchema: .object([ + "type": .string("object"), "properties": .object([:]), + "required": .array([]), "additionalProperties": .bool(false), + ]), + target: .host, approvalPolicy: .approve, concurrency: .parallel + ), + execute: { invocation, _ in + await log.record(name) + return AgentToolResult(callID: invocation.call.id, content: "ok") + } + ) +} + @Suite @MainActor struct AgentBuiltInToolAvailabilityTests { @Test - func terminalSuggestionDefinitionIsStableAcrossSurfaces() throws { + func termindCatalogHasStableDefinitionsAndExactRunModeSets() async throws { let hosts = AgentHostResolver(store: HostLibraryModel()) let connections = AgentConnectionPool( configuration: { _ in throw AgentToolError.invalidHost }, @@ -24,46 +79,110 @@ struct AgentBuiltInToolAvailabilityTests { openSFTP: { _ in throw AgentToolError.invalidHost }, sweepInterval: nil ) + let workspace = AgentScratchWorkspace( + conversationID: UUID(), base: FileManager.default.temporaryDirectory + ) - let mainAssistant = TermindAgentTools.tools( + let catalog = TermindAgentTools.tools( in: .init( - hosts: hosts, metrics: TestMetricsReader(), connections: connections - ) - ).map(\.descriptor) - let workspaceSidebar = TermindAgentTools.tools( - in: .init( - hosts: hosts, metrics: TestMetricsReader(), connections: connections, includesTerminalSuggestion: true + hosts: hosts, metrics: TestMetricsReader(), connections: connections, + workspace: workspace ) ).map(\.descriptor) - #expect(mainAssistant.contains { $0.name == "get_metrics" }) - #expect(workspaceSidebar.contains { $0.name == "get_metrics" }) - #expect(mainAssistant.contains { $0.name == "terminal_suggest" }) - #expect(mainAssistant == workspaceSidebar) - #expect(workspaceSidebar.contains { $0.name == "terminal_suggest" }) + #expect(catalog.contains { $0.name == "get_metrics" }) + #expect(catalog.contains { $0.name == "terminal_suggest" }) let registry = AgentToolRegistry( TermindAgentTools.tools( in: .init( - hosts: hosts, metrics: TestMetricsReader(), connections: connections + hosts: hosts, metrics: TestMetricsReader(), connections: connections, + workspace: workspace ) ) ) - let planning = Set(registry.available(in: .planning).descriptors.map(\.name)) - let acting = Set(registry.available(in: .acting).descriptors.map(\.name)) + let planningRegistry = registry.available(in: .planning) + let actingRegistry = registry.available(in: .acting) + let planning = Set(planningRegistry.descriptors.map(\.name)) + let acting = Set(actingRegistry.descriptors.map(\.name)) #expect(registry.available(in: .reviewing).descriptors.isEmpty) - #expect(planning.contains("ssh_exec")) - #expect(!planning.contains("sftp_write")) - #expect(!planning.contains("sftp_copy")) - #expect(acting.contains("sftp_copy")) - #expect(acting.isSuperset(of: ["ssh_exec", "sftp_write"])) - let exec = try #require(mainAssistant.first { $0.name == "ssh_exec" }) + #expect( + planning == [ + "get_metrics", "list_hosts", "ssh_exec", "ssh_wait", "terminal_suggest", + "sftp_list", "sftp_stat", "sftp_read", "scratch_pull", + ]) + #expect( + acting == [ + "get_metrics", "list_hosts", "ssh_exec", "ssh_wait", "terminal_suggest", + "sftp_list", "sftp_stat", "sftp_read", "sftp_write", "sftp_mkdir", + "sftp_move", "sftp_copy", "sftp_chmod", "sftp_delete", "scratch_pull", + "scratch_push", + ]) + let exec = try #require(catalog.first { $0.name == "ssh_exec" }) #expect(registry.available(in: .planning).descriptors.first { $0.name == "ssh_exec" } == exec) #expect(exec.summary.contains("discover host_id with list_hosts")) #expect(exec.summary.contains("do not call request_user_secret for SSH login")) #expect( - mainAssistant.first { $0.name == "list_hosts" }?.summary.contains("no session context") == true + catalog.first { $0.name == "list_hosts" }?.summary.contains("no session context") == true ) + + let planningExec = try #require(planningRegistry["ssh_exec"]) + let actingExec = try #require(actingRegistry["ssh_exec"]) + let hostID = UUID() + func invocation(_ command: String) -> AgentToolInvocation { + AgentToolInvocation( + runID: UUID(), + call: AgentToolCall( + id: UUID().uuidString, name: "ssh_exec", + arguments: .object([ + "host_id": .string(hostID.uuidString), "command": .string(command), + ]))) + } + + let destructive = invocation("rm -rf /var/log/nginx") + #expect(try await planningExec.preflight(destructive).approvalPolicy == .deny) + #expect(try await actingExec.preflight(destructive).approvalPolicy == .ask) + + let read = invocation("systemctl status nginx") + let planningRead = try await planningExec.preflight(read) + let actingRead = try await actingExec.preflight(read) + #expect(planningRead.approvalPolicy == .approve) + #expect(planningRead.concurrency == .parallel) + #expect(actingRead.approvalPolicy == .approve) + #expect(actingRead.concurrency == .parallel) + } + + @Test(arguments: [false, true]) + func contextSnapshotGatesOnlyTerminalSuggestionsAtExecution(terminalSuggestionsAvailable: Bool) async { + let log = SurfaceToolExecutionLog() + let runtime = AgentRuntime( + model: SurfaceAvailabilityModel(), + tools: [ + surfaceTool("terminal_suggest", log: log), + surfaceTool("ssh_exec", log: log), + surfaceTool("sftp_read", log: log), + ], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + ) + var results: [String: AgentToolResult] = [:] + for await event in await runtime.start( + AgentRunRequest( + conversationID: UUID(), prompt: "inspect", permissionMode: .askForApproval, + contextSnapshot: .init( + toolAvailability: ["terminal_suggest": terminalSuggestionsAvailable])) + ) { + if case .toolFinished(let invocation, let result) = event { + results[invocation.call.name] = result + } + } + + let executed = await log.snapshot() + #expect(executed.contains("ssh_exec")) + #expect(executed.contains("sftp_read")) + #expect(results["ssh_exec"]?.isError == false) + #expect(results["sftp_read"]?.isError == false) + #expect(executed.contains("terminal_suggest") == terminalSuggestionsAvailable) + #expect(results["terminal_suggest"]?.isError == !terminalSuggestionsAvailable) } @Test @@ -100,10 +219,10 @@ struct AgentBuiltInToolAvailabilityTests { } @Test(arguments: [true, false]) - func standaloneContextKeepsHostDiscoveryInstructions(includesTerminalSuggestion: Bool) { + func standaloneContextKeepsHostDiscoveryInstructions(terminalSuggestionsAvailable: Bool) { let snapshot = AgentTurnContextSnapshot( sessionContext: SessionContext.none.modelContext, - toolAvailability: ["terminal_suggest": includesTerminalSuggestion] + toolAvailability: ["terminal_suggest": terminalSuggestionsAvailable] ) let context = AgentContextSnapshotReplay().transform( .init( @@ -143,7 +262,7 @@ struct AgentBuiltInToolAvailabilityTests { additional: TermindAgentTools.tools( in: .init( hosts: hosts, metrics: TestMetricsReader(), connections: connections, - includesTerminalSuggestion: true, workspace: workspace + workspace: workspace )) ).descriptors diff --git a/TFMTests/AgentHostProtocolListingTests.swift b/TFMTests/AgentHostProtocolListingTests.swift index 0cdf0ae4..ab69e707 100644 --- a/TFMTests/AgentHostProtocolListingTests.swift +++ b/TFMTests/AgentHostProtocolListingTests.swift @@ -1,3 +1,5 @@ +import AgentKit +import Foundation import TermindApplication import TermindDomain import TermindRemoteInfrastructure @@ -6,7 +8,7 @@ import Testing @Suite @MainActor struct AgentHostProtocolListingTests { @Test - func listingIncludesTelnetOnlyHostsWithProtocolMetadata() { + func listingIncludesTelnetOnlyHostsWithProtocolMetadata() async throws { let ssh = Host(name: "SSH", host: "ssh.invalid") let telnet = Host( name: "Telnet", host: "telnet.invalid", @@ -18,5 +20,21 @@ struct AgentHostProtocolListingTests { #expect(listing.hosts[0].protocols.map(\.name) == [.ssh]) #expect(listing.hosts[1].protocols.map(\.name) == [.telnet]) #expect(!resolver.canResolve(telnet.id)) + + let invocation = AgentToolInvocation( + runID: UUID(), + call: .init(id: "list", name: "list_hosts", arguments: .object([:]))) + let result = try await ListHostsTool(hosts: resolver).execute( + invocation, + context: AgentToolExecutionContext( + runID: invocation.runID, userIntent: "Discover hosts", + secretBroker: SecretBroker(), userInteraction: UnavailableAgentUserInteraction(), + preflightMetadata: [:], authorize: { _ in .allow })) + let payload = try #require(result.untrustedPayload) + let hosts = try #require( + try AgentJSONValue.decode(Data(payload.utf8)).objectValue?["hosts"]?.arrayValue) + let telnetJSON = try #require( + hosts.first { $0.objectValue?["id"] == .string(telnet.id.uuidString) }?.objectValue) + #expect(telnetJSON["protocols"]?.arrayValue?.first?.objectValue?["name"] == .string("telnet")) } } diff --git a/TFMTests/MCPServerToolsTests.swift b/TFMTests/MCPServerToolsTests.swift index 138e76a1..4b54d3e4 100644 --- a/TFMTests/MCPServerToolsTests.swift +++ b/TFMTests/MCPServerToolsTests.swift @@ -97,12 +97,43 @@ struct MCPServerToolsTests { } @Test - func exportsTheCompleteHostToolSet() { + func exportsTheCompleteActingHostSubsetAndTransferTools() { let server = makeMCPServerTools() let descriptors = server.descriptors + let resolver = AgentHostResolver(store: HostLibraryModel()) + let connections = AgentConnectionPool( + configuration: { _ in throw MCPTestFailure.unused }, + routeFingerprint: { _ in throw MCPTestFailure.unused }, + openConnection: { _ in throw MCPTestFailure.unused }, + openSFTP: { _ in throw MCPTestFailure.unused }, + sweepInterval: nil + ) + let acting = AgentToolRegistry( + TermindAgentTools.tools( + in: .init( + hosts: resolver, metrics: TestMetricsReader(), connections: connections, + authentication: AgentSSHAuthenticationRegistry(resolver: resolver), + background: AgentBackgroundCommands(sweepInterval: nil) + )) + ).available(in: .acting) + let transferNames: Set = [ + "sftp_upload", "sftp_upload_status", "sftp_download", "sftp_download_status", + ] + let runtimeNames: Set = ["request_user_secret"] + let sharedNames = MCPServerTools.exportedNames.subtracting(transferNames).subtracting(runtimeNames) + let excludedNames: Set = [ + "terminal_suggest", "scratch_pull", "scratch_push", "scratch_list", "scratch_read", + "scratch_write", "scratch_edit", "scratch_diff", "scratch_delete", "fetch", "web_search", + "skill_read_file", "skill_install", "skill_manage", "present_plan", "manage_tasks", + ] #expect(descriptors.count == 18) #expect(Set(descriptors.map(\.name)) == MCPServerTools.exportedNames) + #expect(excludedNames.isDisjoint(with: MCPServerTools.exportedNames)) + #expect(sharedNames.isSubset(of: Set(acting.descriptors.map(\.name)))) + for name in sharedNames { + #expect(server.descriptors.first { $0.name == name } == acting[name]?.descriptor) + } for descriptor in descriptors { #expect(descriptor.inputSchema.objectValue?["type"]?.stringValue == "object") } From cef76ce1fca17784c7895fb94205d2d331861976 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 09:40:58 +0800 Subject: [PATCH 04/30] Document agent tool availability contracts --- openwiki/.claims/architecture/desktop-ui.json | 12 +- .../architecture/engineering-invariants.json | 12 +- openwiki/.claims/architecture/mobile-ui.json | 6 +- openwiki/.claims/architecture/module-map.json | 10 +- .../architecture/native-app-directories.json | 10 +- .../architecture/platform-runtime.json | 10 +- .../shared-module-directories.json | 8 +- .../supporting-system-directories.json | 8 +- .../architecture/system-boundaries.json | 10 +- .../concepts/agent-runtime-and-assistant.json | 200 ++++++++++++++++-- .../remote-services-and-forwarding.json | 6 +- .../integrations/mcp-server-and-bridge.json | 40 +++- openwiki/.claims/quickstart.json | 8 +- .../workflows/ssh-session-lifecycle.json | 6 +- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 114 +++++----- openwiki/architecture/desktop-ui.md | 2 +- .../architecture/engineering-invariants.md | 2 +- openwiki/architecture/mobile-ui.md | 2 +- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 2 +- .../architecture/shared-module-directories.md | 2 +- .../supporting-system-directories.md | 2 +- openwiki/architecture/system-boundaries.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 68 +++++- .../remote-services-and-forwarding.md | 2 +- .../integrations/mcp-server-and-bridge.md | 12 +- openwiki/quickstart.md | 2 +- openwiki/workflows/ssh-session-lifecycle.md | 2 +- 30 files changed, 408 insertions(+), 160 deletions(-) diff --git a/openwiki/.claims/architecture/desktop-ui.json b/openwiki/.claims/architecture/desktop-ui.json index 5d97d851..c5570b0b 100644 --- a/openwiki/.claims/architecture/desktop-ui.json +++ b/openwiki/.claims/architecture/desktop-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:fad06b683f4e91f808959ed85ce962b1c86d64a67078b39c7ba20f8aa12ad35b", + "pageVersion": "sha256:9e4dcfe15396af0c8e9ed77ff7347eac829aeff9d111ad37757a2e3f1e63b2c1", "claims": [ { "id": "claim_5a53c5a47b7445f7a7b6f8865297a805", @@ -104,13 +104,17 @@ "id": "claim_1361b995f8e646c6a534186edcd83dc7", "statement": "The workspace compact Assistant has routing separate from the full Assistant destination and composes transcript/interaction regions with explicit terminal context; Escape cannot fall through from a blocking interaction into closing the conversation.", "evidence": [ + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L152-L225", + "version": "repo-lines-v1:sha256:d47d11a81fc91f016d97ec23e87c1a89e608cb049d364bda5cc33966ce5e6a5e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0YmFkZWYzNjBmZDM2NTYwNGE2ODg4NGU5NDJiYzA4M2RjZDQ0ZTJhNTBlYzYyMjkzOTExZDYxOTg1MmVhNDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MDMyZTk2NGQwOTMyMzUxZTVkMTg1MjY5MjgxYjE3MWEzYTA5YjA4YjI4NmU2NjFlYTQ3MDMyM2M4MmViNGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEyZjUyZjI3YTJiNzI0ZTlmYTM4NjA0ZGJkZGZlODczYTlkMTJhOTNiZWI0YWNiYWU5ZjFmNzk3ZDZkOTg1OTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L7-L14", "version": "repo-lines-v1:sha256:a3755412e818d877b2c9c59bc75105e21157bb4519e47f13f4ee6fd2764de97b:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmFlZDhjNWNjMzg0MGRjMjMwYTRiNGY3MTExN2UxZGE4MjRmZmJmYjA5ZTZjMWVmYWY4M2U1Mzc5MTNkNjkzZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNTcxZGE3NDM0NTdjMDNhMjQwNDRhMTk3NTQ2MGE2YzQ4OTllODBiNzUyOGUzMDRkMzE0ZDY3ZmU1NWE0MTYwNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWMwMDRmMzdkODZkOTFjYzUyZGY1ZDhiYjAyYjU0YmI1ODk5YjA1MWYzY2M0YTU5ODk2ZDFiNTZjYjQ1NGNiMiJ9" }, { - "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L89-L225", - "version": "repo-lines-v1:sha256:a235a02d0e28feb4b79c8d8a6f5e3844a9598fd35568c864ee8ed9cabfbc4c8d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTM3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5MTFlOGI2MmUzM2JjNDg0MWM4NmE1MmJiZmQ0MjRhZTQxZTBiYmVhODI0MjJkMTBlMGQyMGI5ZjMwNWZiNDBjIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1NTAzMmU5NjRkMDkzMjM1MWU1ZDE4NTI2OTI4MWIxNzFhM2EwOWIwOGIyODZlNjYxZWE0NzAzMjNjODJlYjRkIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYTFiOTczN2RlNTU1MTEwZjU1YmYyZmI2N2UxNzk1ZTU1ODA4NDdjYTQzOWI0YjBkZmJjN2U1YmZjNGU1YjcxIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI5OTliZGVmZWVhY2E2MzQwZTAyOTBkYzg2ZTg4OWM4OWQ1ZTE4OWFhNWJhNDRiNTg1YjNiNzA3MzRmNGE2MmQ4In0" + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L89-L96", + "version": "repo-lines-v1:sha256:b2fe9ba7bbc30e22c4c7c13247a4e7f913020f78afc2693088e31914f9c554ac:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTExZThiNjJlMzNiYzQ4NDFjODZhNTJiYmZkNDI0YWU0MWUwYmJlYTgyNDIyZDEwZTBkMjBiOWYzMDVmYjQwYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmExYjk3MzdkZTU1NTExMGY1NWJmMmZiNjdlMTc5NWU1NTgwODQ3Y2E0MzliNGIwZGZiYzdlNWJmYzRlNWI3MSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGE0ZjU3YTVlYzJkYWUxY2Q0ZmY3MDczMDE0Zjg4YTM0NWJiMzVlMmJmNDFjNDMzMzc4NjE2ZjAzNjM2OGM2MSJ9" } ] }, @@ -203,6 +207,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index e3d1bf4a..e415c36d 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6f98da9e8f070ebc2a0b91545058eb3aa5a411129c7a18c9b33ee9eb6da8e799", + "pageVersion": "sha256:75fe464fb65e761e5eeaab3f31f1a2d852175cc5f78f5b099ddffee4a8655971", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -52,11 +52,11 @@ }, { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:1ac6f933da6e8e14162cab3e53a64ad3f1b4bd1bb3c55f6f3ead3286054c0cc1" + "version": "repo-file-v1:sha256:ba89018766f45b430033efc2969c0307ba30c0d298bd8b55a51e17a08915c767" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -110,11 +110,11 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift", - "version": "repo-file-v1:sha256:486ca4d2997672c82c417051e24392ee610f9f838dff89cffedaceff0891f9fd" + "version": "repo-file-v1:sha256:8ccfafe881aacc3f7232d30cef9c97588787771dd925d726b5591efa15c92021" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift", - "version": "repo-file-v1:sha256:9a6ac5989bbe56b568db0e639c84f0608a8dc5b4cd089438c64d91a587a91527" + "version": "repo-file-v1:sha256:48b3039f44d716afaacc3e0986a88fcaad3a32d6fa00a68dba265b82e0efc284" }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift#L137-L160", @@ -325,6 +325,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:59:31.979Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/mobile-ui.json b/openwiki/.claims/architecture/mobile-ui.json index b2a4b991..1da889e3 100644 --- a/openwiki/.claims/architecture/mobile-ui.json +++ b/openwiki/.claims/architecture/mobile-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:769919ef95916ccc91f27ef2666730600005e79c052893c2ae098644ac389a07", + "pageVersion": "sha256:a1e240ca495ddf76ace3e9861b2f459a2fa61e2d53179352e87b15452e6828ee", "claims": [ { "id": "claim_fbb919f62a2d4ab88aafcc4c1186731b", @@ -142,7 +142,7 @@ }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L9-L39", - "version": "repo-lines-v1:sha256:82b51c51f7632b9209f536cc1ff9f4eff1e9b61477387dbf7369a46f05bd8809:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4ZDUwMzc5NDJmNThhMWQwZWRlN2QxNDYwNmMwZTJlOTE2OGUwMzRkNDFiYTU2ZThkMTA5YTRlNzY5NTgxZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwYWZkMDliMDljYTY2Y2Y0YmIyN2QwZWMxOWQ2ODUyMzA1OTYxNWE4OGQ2MzRjYTIzYjMxYjdmYmM1OGY5MDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ4NjlhNmI3MWE1OTA2NzY4MWZkOTE5ZWJlNThkZmMzODZkMzI3YTRhNmJlMDFiMGI0YWYwYmFkMmViYWY3MzcifQ" + "version": "repo-lines-v1:sha256:f6690fa9486d7a196714cdb9b22349dae117807c62649feffd2126f03dba2f3f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4ZDUwMzc5NDJmNThhMWQwZWRlN2QxNDYwNmMwZTJlOTE2OGUwMzRkNDFiYTU2ZThkMTA5YTRlNzY5NTgxZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwYWZkMDliMDljYTY2Y2Y0YmIyN2QwZWMxOWQ2ODUyMzA1OTYxNWE4OGQ2MzRjYTIzYjMxYjdmYmM1OGY5MDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ4NjlhNmI3MWE1OTA2NzY4MWZkOTE5ZWJlNThkZmMzODZkMzI3YTRhNmJlMDFiMGI0YWYwYmFkMmViYWY3MzcifQ" }, { "resource": "repo://TFI/Features/AI/MobileAssistantConversation.swift#L447-L544", @@ -263,6 +263,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:59:31.979Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 49074100..09535e72 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:8c5b708036c7d089f4821e24e31a00e1684e44d78bf3a8182b9df0800ffe7f00", + "pageVersion": "sha256:cd736475053bed4751ff7b4f06ee0433c8d3ac3e74f3866d585bc958dd6b25d5", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -44,7 +44,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -88,7 +88,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L68", - "version": "repo-lines-v1:sha256:b37728d2b6eed175b24a9d2761439467c6455e58de8c995e99c80bcdd740c58f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" + "version": "repo-lines-v1:sha256:8ec2078dc1ab7a5f6087ab497ecb18aa1b47942cd6213e8f611f6ee6b14c1d26:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" }, { "resource": "repo://Termind.xcodeproj/project.pbxproj", @@ -102,7 +102,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L6-L44", - "version": "repo-lines-v1:sha256:c45de1cccebc1889e8de88bb9520e33aeaba05e6c2c8faa7876eba2acb7f3493:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" + "version": "repo-lines-v1:sha256:acf89cdfba502ef9c32f962dc43493dd515971c9c8b203533c43044e3ebca670:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" }, { "resource": "repo://Frameworks/TermindApplication/Transfers/RemoteFileSystem.swift#L4-L14", @@ -151,6 +151,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index 7813df32..156bf01c 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:411c153b0239360e9fc0a413c9ff012607f8aa71dfd42cf349bcfc83eb2fb690", + "pageVersion": "sha256:daaecfb65b33663c617a5627c7c65fc6e45cd05b85017d53e6a48c818a1e15b5", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -20,7 +20,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -56,7 +56,7 @@ }, { "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift", - "version": "repo-file-v1:sha256:ad52ce8d73955e9220c24a05db6827b45359c2de46b59d5f315f733354bfc0fd" + "version": "repo-file-v1:sha256:7fb10e9423fc06bfc8c45a3b189ffd03cc3b8f588915c1e9afa3cc33b91d6020" }, { "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift", @@ -112,7 +112,7 @@ }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift", - "version": "repo-file-v1:sha256:f452636d57e54e873af2d27b0a25492e6ae8f2cb2eff8543ae001d09c5a6b265" + "version": "repo-file-v1:sha256:3d9babddce2d3da3e2f1787094a40692a2b7a4a2d2c22eed61f5372b4221b205" }, { "resource": "repo://TFM/Features/Shell/WindowRoute.swift", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index 6cb8c320..d38210aa 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1928bdff8b5655653f6d6eaa66489a0a3c967dfebdf9c9c86245df87bf2011c1", + "pageVersion": "sha256:f6b1eae907640a27121b4c2f41cc040490003e2944d38858bc7a4719581bd0b3", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:1ac6f933da6e8e14162cab3e53a64ad3f1b4bd1bb3c55f6f3ead3286054c0cc1" + "version": "repo-file-v1:sha256:ba89018766f45b430033efc2969c0307ba30c0d298bd8b55a51e17a08915c767" }, { "resource": "repo://TFM/App/RootView.swift", @@ -48,7 +48,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -224,7 +224,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -257,6 +257,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/shared-module-directories.json b/openwiki/.claims/architecture/shared-module-directories.json index ca03951f..9d33af90 100644 --- a/openwiki/.claims/architecture/shared-module-directories.json +++ b/openwiki/.claims/architecture/shared-module-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6ba56baf56fd5439756c528b55b7765f506ed716677142f0298cbad9f4cf2f93", + "pageVersion": "sha256:42206a596cfd036cd1cf7bd7bc2f0e8ec5f96a4577648a864cebd3f8db5d0a3f", "claims": [ { "id": "claim_6e30bc8cde134c23815d274543d0786c", @@ -60,11 +60,11 @@ "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L6-L44", - "version": "repo-lines-v1:sha256:c45de1cccebc1889e8de88bb9520e33aeaba05e6c2c8faa7876eba2acb7f3493:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" + "version": "repo-lines-v1:sha256:acf89cdfba502ef9c32f962dc43493dd515971c9c8b203533c43044e3ebca670:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L68", - "version": "repo-lines-v1:sha256:b37728d2b6eed175b24a9d2761439467c6455e58de8c995e99c80bcdd740c58f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" + "version": "repo-lines-v1:sha256:8ec2078dc1ab7a5f6087ab497ecb18aa1b47942cd6213e8f611f6ee6b14c1d26:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L92-L162", @@ -291,6 +291,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/supporting-system-directories.json b/openwiki/.claims/architecture/supporting-system-directories.json index 132f5718..26a9203c 100644 --- a/openwiki/.claims/architecture/supporting-system-directories.json +++ b/openwiki/.claims/architecture/supporting-system-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:fa243e662bcccf538fa4c830c781851a26ec9aea0c86163cfc99644ac7364159", + "pageVersion": "sha256:2711b0e7d443185dd21a3d96c2cb80f84a5e2149a5a6fc64fda5363afcbc86be", "claims": [ { "id": "claim_8809151c43594803b83cf0451d849112", @@ -60,7 +60,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L68", - "version": "repo-lines-v1:sha256:b37728d2b6eed175b24a9d2761439467c6455e58de8c995e99c80bcdd740c58f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" + "version": "repo-lines-v1:sha256:8ec2078dc1ab7a5f6087ab497ecb18aa1b47942cd6213e8f611f6ee6b14c1d26:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" } ] }, @@ -70,7 +70,7 @@ "evidence": [ { "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift", - "version": "repo-file-v1:sha256:ad52ce8d73955e9220c24a05db6827b45359c2de46b59d5f315f733354bfc0fd" + "version": "repo-file-v1:sha256:7fb10e9423fc06bfc8c45a3b189ffd03cc3b8f588915c1e9afa3cc33b91d6020" }, { "resource": "repo://TFM/Infrastructure/MCP/MCPServerSession.swift", @@ -237,6 +237,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T10:02:02.985Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index 9401f70b..5383f219 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:db9f09d0c4799f6c8ecc04c8b993af8c45c2c71fbb32827587c3c99c945c3c2b", + "pageVersion": "sha256:3cbf1be9eb94f0eb87d83be0a1277c9801cb4df9a3da898ef95b63b20b72a785", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -106,7 +106,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", - "version": "repo-lines-v1:sha256:f00dc05b6b14f14e9071bb2c3725c64e133622f8e78ffc3e87a20fba2c5752af:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + "version": "repo-lines-v1:sha256:5e034e35b4291fbd3ada9dbf3613ec9e3f6865bf8b3945545efb078073d866f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" }, { "resource": "repo://TFMTests/ArchitectureBoundaryTests.swift#L137-L160", @@ -120,7 +120,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", - "version": "repo-lines-v1:sha256:f00dc05b6b14f14e9071bb2c3725c64e133622f8e78ffc3e87a20fba2c5752af:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + "version": "repo-lines-v1:sha256:5e034e35b4291fbd3ada9dbf3613ec9e3f6865bf8b3945545efb078073d866f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L45-L62", @@ -128,7 +128,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L23", - "version": "repo-lines-v1:sha256:a6707f4eac40956e70af3b606852201b6d17b54d295c771bb743f37fbad2797f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1OTRjZTkxMWRiMTE2YWVlNzAyNWMyYzAzNzE5NzZmZTVjN2M3Yzc1ZmY2MGQ3MTA4NDgyYzY0NjNmNDM4YTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzNjg4OWY0NDhlNDhjMWE0MjM5NjQ5ZDdkZTEyNzBlNDU1NDk0NDgyODY3NjQ4M2ZkYWY1YjhmYjkwZWUzN2EifQ" + "version": "repo-lines-v1:sha256:5e755dd6ce0298ca36f329631b3f5a5cc90482b69508938631e903a92f654856:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1OTRjZTkxMWRiMTE2YWVlNzAyNWMyYzAzNzE5NzZmZTVjN2M3Yzc1ZmY2MGQ3MTA4NDgyYzY0NjNmNDM4YTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzNjg4OWY0NDhlNDhjMWE0MjM5NjQ5ZDdkZTEyNzBlNDU1NDk0NDgyODY3NjQ4M2ZkYWY1YjhmYjkwZWUzN2EifQ" } ] }, @@ -191,6 +191,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index a5a5a15a..f6181567 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6ba8f89374ad018a257cf1c10e91f9d4b3641420f6187d07c8829858671948d1", + "pageVersion": "sha256:47fa6bbaa4a43b7bac039520e0bd6d4cf3f003b8969821fecaebe1cd9bdeac6b", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -12,29 +12,33 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L201-L328", - "version": "repo-lines-v1:sha256:02c4383ae6885f3dea6168d146dfc1dfc12cfe9af93cd56bead4023f25f8bf25:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2NTVlMmVkYTc3Yzc3ODBiNjg2ODJiMmZiYTExNGQ4YWE5YmIwOTBkMDYzN2ZmY2RmMzgwNWViODkzNjZlYWZkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1OTA5YTUzYTYxOGQwMjhlMWQzMDAwYzBhN2FiY2VlY2JlNWZiMzdmNGU1MTFlZmU0NjY0M2ZhZDZlNjY4NzJmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YmE4NGEwNzc5OGYyNzk4NjliNDM1ZTdiZGU2MmZkYzg2ZDRhZjkzNzJkYTcyMmY5NWM2MjY5YTJhYjkwM2U3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNGJkNzA2YzdkNTlkMjVmM2MxNTUzNDM5MDAxZmYyOWQ2ZGEzZjVlNjQ1YWIyZTE5ZjM2N2MwZmM3MDQ0MGU0In0" + "version": "repo-lines-v1:sha256:2744f8cd1de07ff0f99433383a73d1424c77a6dee3edce41f125c652bfe7e1a7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI5LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2NTVlMmVkYTc3Yzc3ODBiNjg2ODJiMmZiYTExNGQ4YWE5YmIwOTBkMDYzN2ZmY2RmMzgwNWViODkzNjZlYWZkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1OTA5YTUzYTYxOGQwMjhlMWQzMDAwYzBhN2FiY2VlY2JlNWZiMzdmNGU1MTFlZmU0NjY0M2ZhZDZlNjY4NzJmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YmE4NGEwNzc5OGYyNzk4NjliNDM1ZTdiZGU2MmZkYzg2ZDRhZjkzNzJkYTcyMmY5NWM2MjY5YTJhYjkwM2U3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNGJkNzA2YzdkNTlkMjVmM2MxNTUzNDM5MDAxZmYyOWQ2ZGEzZjVlNjQ1YWIyZTE5ZjM2N2MwZmM3MDQ0MGU0In0" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", - "version": "repo-lines-v1:sha256:f00dc05b6b14f14e9071bb2c3725c64e133622f8e78ffc3e87a20fba2c5752af:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + "version": "repo-lines-v1:sha256:5e034e35b4291fbd3ada9dbf3613ec9e3f6865bf8b3945545efb078073d866f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" } ] }, { "id": "claim_28c4b1e064e94a798fe0220965a3951e", - "statement": "A submitted Termind turn records the user prompt, freezes run context and tool availability, constructs a new AgentRuntime, and starts it through ConversationModel; the runtime persists a running snapshot before requesting the model and persists transcript snapshots around each tool-result round.", + "statement": "A submitted Termind turn records the user prompt and its context snapshot, resolves the catalog and run mode, constructs a fresh AgentRuntime, and starts it through ConversationModel; the runtime persists a running snapshot before requesting the model and persists transcript snapshots around each tool-result round.", "evidence": [ { - "resource": "repo://.build/verify-zh-hans/ios/SourcePackages/checkouts/AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L161-L241", - "version": "repo-lines-v1:sha256:ca6910405404893e5f20a65facc669d891dab442eb019ea645c30141cb27798a:eyJzZWxlY3RlZExpbmVDb3VudCI6ODEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVkNWE5M2M2ZjljMzg2NWRjZjkwOTliYWE0YWNiZTI3MjI4MmY2ZTFhZDE5OGU3MWQ5NGU5MzRlMjIzNzNiMTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBhZWFjOGZkNTg4ZjE5OWZlNzFmOWJiMGY3Mjk1NDI5NGNiMmVlOWRkNzgyYzQzMDJlYTg4NWE3MTAzMDNjMTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI3ZTA4ZDYzNDU5ZjAxMzEzODQ4MDgxMGIwZmI3ZjBiYTJhYWY3MmM5MzhkZGViZDEyYzRhZWZiNTEyZDQxMzYifQ" + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L213-L274", + "version": "repo-lines-v1:sha256:0dfd924390c397077812263deb5dd48a301f249f10478b62641a686cfd342a4c:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiY2NmYjg3NmVhNWUyNGFhMjgxNzFhOTdlM2NlNDFhYjIyNjE1N2VlZjcyMzhjNDMxMmVkOGQ3NmJjOGEwODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzNDE3M2M2NjQ2NDEzMTc3YjQ1NmZmZjdiM2MxY2Y4Y2Q3Mjg1Y2IzMzAxYzI0ZDM3MGU4YmZiNzNiZWVmOTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI3ZTA4ZDYzNDU5ZjAxMzEzODQ4MDgxMGIwZmI3ZjBiYTJhYWY3MmM5MzhkZGViZDEyYzRhZWZiNTEyZDQxMzYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L285-L346", - "version": "repo-lines-v1:sha256:00d555bfa02408a1b7619b54dfce3ea70403a2f114b8332941de455674e9c86c:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI4YjA1OGQwZDE5MTQ0NzUxZmFhNDYzZmM5OTk3NjQyYjQ0OGNjMDVhZmFjZmMxYzY3YTg5NjhhZTA4MTgzMGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNWYxMjI0YzZkNDJlNjVhZWMzNmY2ZGNiMGIxNzZjYWQwYmNiNTQzZTVmMzcwMzM4ZDhmNjNmOTRkYTBlYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYThlMTdlZjdhMzg5MDQ1NjczYTQyYWZkOGFlYWFiMjk5OTg3NGIwYjdmNmEyMGY1YzEyNDc2OTU2ZGI1NzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYzAyYjAwYTc2Mjk0MTQ4MDZkMTIyMjc2OWI4MGUwZjZiMWM1MWZmYTQwYzU5YzExMjliMmY0MThjYTNlYzYifQ" + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L303-L318", + "version": "repo-lines-v1:sha256:fd811671f5fdc47d264e5c3d26b3fb815385a39f5747a7d460403896bb8c0081:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlMmZjMTRlMmFjYmU5MjgxYmI5OTRmZjQ1NGUyNDAyMTE3MDQ1OWI5ODJjM2ExNTlkOGJlODM5NWI2NmJkNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkN2RhMWFmYjFkMTc3MmUwNTY5YmU0MWZkOGQ0YTA4OTczNWNlMzczNDExNGU3OWZkYjJmMGJiMDdmN2ZmODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGI3YjgwZDA1MzcwODYxOGIzZTUzNTJmYWM1YWE3NTUxN2M5OGE4OWVjNGNkYzU0MWUzZjE4YTgxMjY1ZGEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L286-L351", + "version": "repo-lines-v1:sha256:d7a10bdad07278a40279af680e0f3630aa1febdcb7851cb87e6a48a0509e7759:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI4YjA1OGQwZDE5MTQ0NzUxZmFhNDYzZmM5OTk3NjQyYjQ0OGNjMDVhZmFjZmMxYzY3YTg5NjhhZTA4MTgzMGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQzOThhZDkxNzk1ZTc5MjQwYjEzY2Q0MTU4NDY3NjI3Y2U3OGRjY2FiNzdkNmM3ZWRhZjczMTBlMTZhYjU3ODgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYThlMTdlZjdhMzg5MDQ1NjczYTQyYWZkOGFlYWFiMjk5OTg3NGIwYjdmNmEyMGY1YzEyNDc2OTU2ZGI1NzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmNjk4YzQ5MTQ3ZGQzNjlmZDIwOTBmYzRkYmI2MGY1ODhiMzU3ODY1MmFjZTVkOWQwYjg2NzU0MjE0YWY3OTEifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L46-L95", - "version": "repo-lines-v1:sha256:2a4b30082e0de792ca959a899e5cdbe5073c3fed89b37a6767845f2231d7a6b8:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzNTMzM2MzZTA4M2NlNjk4MjRiZTAyZTMyZWFlMzcxNjc0ZDhmOGZhZDMyYzNlOGU1MDViMmJmNTE0M2VmMTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzYWVlMmNjZDYwMjA1YzRmNTY0NWM0NjE3MzZlZTZmYjkyZTU1ZGQzZjE2MmQ2NGQ0Y2UzOGIwY2I1NjExZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiNWI5MWZmNzRmYmZjZWY0OGZjOTZhMzNmMGFlNzk2NzkzODE3N2U1YTJlN2Q1OWE1YTg0MWJiZDU5OTY0NWMifQ" + "version": "repo-lines-v1:sha256:42d8c99afe5dceebcaaabd0abd43c95d68ee3d1bacc4f900e60db34dfc667958:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzNTMzM2MzZTA4M2NlNjk4MjRiZTAyZTMyZWFlMzcxNjc0ZDhmOGZhZDMyYzNlOGU1MDViMmJmNTE0M2VmMTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzYWVlMmNjZDYwMjA1YzRmNTY0NWM0NjE3MzZlZTZmYjkyZTU1ZGQzZjE2MmQ2NGQ0Y2UzOGIwY2I1NjExZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiNWI5MWZmNzRmYmZjZWY0OGZjOTZhMzNmMGFlNzk2NzkzODE3N2U1YTJlN2Q1OWE1YTg0MWJiZDU5OTY0NWMifQ" } ] }, @@ -107,20 +111,20 @@ "statement": "Steering messages are queued while a run is active and only become transcript history at the next model boundary; cancellation stops the runtime, cancels its task, discards run secrets, and returns undeliverable steering to the composer.", "evidence": [ { - "resource": "repo://.build/verify-zh-hans/ios/SourcePackages/checkouts/AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L135-L159", - "version": "repo-lines-v1:sha256:1e7ee3251e0671722c1fa5ceddf8acaf9c387c1214e7d87e25b3061dc64e2dbd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4N2Q1NGYyZWZiNDBlN2ZjNDdkYmUzNjJmOTgzNzNmYzJkZmI3NDM3Yzc1NTlkMzEzYWFmNzAyMWZjMDkyNGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhYmQ3YjdjN2RkYTI0MTcyNTJhMDY3ZmY2YjU5NWQyMDllNGI5OTc1ZGRhYmJkODc2M2ZkYzFkOWQyMDQ0ZDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyZmQ2NTFmYjQ2OWY4YjVmMTQ0NjRiY2M2ZGMwZDZhYjJkZTM1Njg4MDhhZDcyNzYxYjhiMGNlNTFlNDZjNjgifQ" + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L139-L165", + "version": "repo-lines-v1:sha256:1893129dd01a1c8a79a01cdf4625cb226ff4ebca0b1b11cdfc4a9827340a17ca:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4N2Q1NGYyZWZiNDBlN2ZjNDdkYmUzNjJmOTgzNzNmYzJkZmI3NDM3Yzc1NTlkMzEzYWFmNzAyMWZjMDkyNGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhYmQ3YjdjN2RkYTI0MTcyNTJhMDY3ZmY2YjU5NWQyMDllNGI5OTc1ZGRhYmJkODc2M2ZkYzFkOWQyMDQ0ZDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyZmQ2NTFmYjQ2OWY4YjVmMTQ0NjRiY2M2ZGMwZDZhYjJkZTM1Njg4MDhhZDcyNzYxYjhiMGNlNTFlNDZjNjgifQ" }, { - "resource": "repo://.build/verify-zh-hans/ios/SourcePackages/checkouts/AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L217-L252", - "version": "repo-lines-v1:sha256:33f1041e47578bf10577d59d8e680d52acc08b9618c31112ee86070cda232bb9:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0OTgzZGE0OWNjMWViMzBkM2MzODMzNjMzM2I4NmM0ODc0ZmEwNDA2YWE3OTIxNDI3ZTU5YzgwN2Y3OWI2NDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyNmU0MzY4ZjIxZjg4OTFjZWQyNGNmYWY3Y2EzYmY5OTg1YjNmNDY5ZDRhNjgyNTZlMTYwOWQ2NTZmMWE3OWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWQ2MDRlNmM4MjFhMGNkZDdjNjhjMGQ5MjM2ZGYzNGQzYWY1NjM5ZDI2YzAxNDRiMDQ5MDQzZTgxMjBmZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg3YzhmMzI1N2Q0YmMwNGQ2ZjViYjFjYmRiY2FiN2QwNmQyMTA1MzJjMDhkZTExM2MyNzJmZDdjZjkzYmZlNzQifQ" + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L249-L265", + "version": "repo-lines-v1:sha256:dd7262281b0133a9a56570381c1b52df5dededbbb751d9e81871e8fd08bb10b4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjMjU4YjdiNGY1YjRhZjg4NjE4ZDI4NGJkODg1Y2VmYzNiMmQxOWRhMTk5OTlmN2ExNDI0YmQxMDliMjNjZjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmMGE5Mjg5N2Q0YzU4ODhhNzJmMDY5NDJjNmZkYmViM2NiNDAyMzBlY2U1ZmZjMThiNWJhYzU2ZTM0Mjk1YzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWQ2MDRlNmM4MjFhMGNkZDdjNjhjMGQ5MjM2ZGYzNGQzYWY1NjM5ZDI2YzAxNDRiMDQ5MDQzZTgxMjBmZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1N2YwZDhiZTM1MGE0NGY1YjRhYjlmZmE1MDUyMjc5NTBjODk3YmI4MGVhZjI3NzNkMTAxODNkZTljNjE1ZWQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1909-L1987", - "version": "repo-lines-v1:sha256:09a1dfd058b41e37f5cc8489c4536b2c966c4c0d75a6d766e18567e4b0934c92:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmMThmMGI1OGIyNjAwNzUwYzBkMjdlZDlmMDA2ZGRhNjZiMjBmYWM4NDg1NzFlYTVlODUwOWFlZDQ3NWJmNjkifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1995", + "version": "repo-lines-v1:sha256:6cda8b5a391dcf8703619fb4c97e81ac032fb3dc591c2369a20d2b9e8ba28f97:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY0NDkzM2JlYmY3OGIxOGFkNjc4Mjk0MzM1NjdjYzk4MWNlYjdiM2FlOGIyZTRlNjA5NjRlOWYwNDQ4MjJmNTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyZjFlOGM4MDY5ZDRmY2Q0OTQyZDIyYzQ1MGZmZmJmYjJmODE4NjAxYjE5ZDE3Njk0YWM0M2FjNDVmMTc0YTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5NjI4ZWNhNWJkNzUyYzJmOThkZGFkY2VjOTU5YWNkNmFiMWZhMTA1MmVhYTY3MzZiZWFjODRmNDc4NjEwMDcifQ" }, { - "resource": "repo://TFMTests/AgentRuntimeTests.swift#L428-L457", - "version": "repo-lines-v1:sha256:5d7811288d8d6d02c029b2d582688bcaf07e08796cd196f6f69823d757672b3c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjYTZkN2QzNTY5ZTQwMDNkM2VkZTM4M2Y1NGZiMzM4ZTM3OTU1NmZmNGFlYTQ5NDNlNDIwMGFmZWJhMjM5MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlZTI3YzAxZDY3NDFjMWI2Y2YzNmJhMTM4YTk1NWU1YTA1MjhhZGRhMzQwOTNjNDEwNGRjNDE5MDUxNDFhMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIxM2RlZDMwMDA2NTNlYzc5ZDMxNGQwOTIwZmE2NTFiY2M3OWY1ZmFiYjcwMTkyN2ZiODBjYjAxZTcyMWVjYzQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2507-L2534", + "version": "repo-lines-v1:sha256:fa1732f1c428eebb9fe5c6720dee5116ca6a8436ed3f480122180630d6cbb740:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5MGQ2NzM0OTMwMjgxMjBkMGRhN2IyZGI0MmRmMjU0NGU3YjI0YTNjNGY0MjVhZjkzMDI4NGI0ZGE2NTg2OGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjA1NGIyODExNGMxNmJlMWM4MmQ5NDFjNzc1OGM1NzFhNzcxNzBkYzM0ZTA1ZDUwZDllMDk3NGEyMjRlODQifQ" } ] }, @@ -184,7 +188,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L218-L234", - "version": "repo-lines-v1:sha256:5c7b2d3892cdad34fcb9cb2996fde4ff824188998dc264d7b3600bf156a37ad9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMDg3ZmZlODE0NTk2NGYwZWIwOTlhM2FiMzQxOTRjMzlkM2E5YTY2YTM2NWJhYTQ3OWQ0OGIyOWJhY2ZiNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyZGFjYzM0NjMzZGIxZjRlY2ZjYjYzOGI5OTNjYWY1ZWE1MGU3ZThmZTk5MTFjNjBmODBhZjIxNWM4ODk5ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhkZjJlZWYyYmU0YTM1MGU3ZjI3MmVmZjExNDc2MWJlM2I1Y2MyYzMyYmI1NzllZTc3NjIyY2JkN2MxOWIxNDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNjZiMzQxNmZhZmQ5NWY2NjY2OTQ5MzJmMjc5ZmVjNWM4ZTliODY4N2I0NDk2NGM4MTU4Y2UzZDJmYWYxZjcifQ" + "version": "repo-lines-v1:sha256:65ddf8be8b7a5adeab788e5df286c4169da771dbfd9f2a9919a9ee789c4ee562:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMDg3ZmZlODE0NTk2NGYwZWIwOTlhM2FiMzQxOTRjMzlkM2E5YTY2YTM2NWJhYTQ3OWQ0OGIyOWJhY2ZiNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyZGFjYzM0NjMzZGIxZjRlY2ZjYjYzOGI5OTNjYWY1ZWE1MGU3ZThmZTk5MTFjNjBmODBhZjIxNWM4ODk5ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhkZjJlZWYyYmU0YTM1MGU3ZjI3MmVmZjExNDc2MWJlM2I1Y2MyYzMyYmI1NzllZTc3NjIyY2JkN2MxOWIxNDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNjZiMzQxNmZhZmQ5NWY2NjY2OTQ5MzJmMjc5ZmVjNWM4ZTliODY4N2I0NDk2NGM4MTU4Y2UzZDJmYWYxZjcifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/TermindAgentPolicy.swift#L46-L49", @@ -235,10 +239,168 @@ "version": "repo-lines-v1:sha256:63a4e0832eb58af19f1014b010116530bd83aa6cfe5c62c9e3643d154d77383e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MmY5Njk1Yzc0NjdjNzQ3NzNjOWNlN2ZjNDI5YzVkMGQwZTQ4N2YzYzUxZDE1ZDY2NjhhM2QyNDA3MmJiOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyNWZmZTlhNjZiY2Q1Y2QwNWE0NDhhYjFlYmQ1NmVjMTJkNTg1ZjYyN2RhOGM5YmI2ZjYxYTVhNWUzOWU2YzcifQ" } ] + }, + { + "id": "claim_160c57c07ff642b2b69e08cf0cb7a9cd", + "statement": "Termind's static host-tool registrations are shared across Assistant surfaces; injected workspace capability controls scratch transfer construction, and AssistantToolRegistry returns no tools for models without toolCall rather than using a surface-registration flag.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L9-L67", + "version": "repo-lines-v1:sha256:915c7bd8855c34a13501e84f0aadb71d52d5c58dfa1f7088814d1cc76cbfe214:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNDlhZWQwZGQ2OTMyYzRkOTUyOTMzYWQwMWVlNDFkZmM4Mjk3NDBhZjk2NTFiOGFiMmZkZmZiMTQ4NWVhMmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5YTE2NTM1ODdkZjYyOWQzM2I2Y2FmY2I2ODQwNGNjMjAxYjIxODdjMjI3MzNjYTQyM2RkNDUzYzcwMjE1NDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L131-L146", + "version": "repo-lines-v1:sha256:115a05de9a5dfe6bb311e843026f4ed7d847445773c7c667c42bdaa0f904cb0f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1MmVhOTZlMjgzMzE3ZTg1M2E5MjFkNTJmYzg0OTllNGQyM2MxMTI3NDQ1ZDEzOWUyYzlmM2E4NDBkOGE3ODciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNjczYWJkNDU3YTk3ZWVhMTdhNjAwNjJlYjBmYWVhNmJkZjA5NDg2OGNjNmI4ZjhjMGViZGEyZjYxZWQ0MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI3NmI3Y2RhMDE5NDc2YjllZmM4NmVlNGU2YzQ0NzNkNWEyYTA3ZjZiMjk1MDFlN2QyZDNjMTcyMWZkZjFlYmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3OTQxNzg4NDc3NTljMDg1ODZkYWRkZjhjMThjNjI1YzYyYzAzMjFhOWI4NGMwMzQ4MThiMTA3YzM2OTZiNjMifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L51", + "version": "repo-lines-v1:sha256:fc358f76511590c37149cb7d366c5ea14c99d6725c9f39630daefb9d4193c103:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyNTZmZGZhNTNlMTBjZjU2N2FjODRhOTE3NzBmOTFjYmUzMTI2NWNiY2Q0MzMwNThiZTkzNGE3MTI4ZDZjYzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwZWNmZjVkZTA1NjBhMmI3MTUzMWYxNzMyZGUwODYwNTQwZDBiZmI5NTFiZWRiYjc4YTEwMTIzZGMwNDYzMTgifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L356-L397", + "version": "repo-lines-v1:sha256:95a9040323f538be29d9e56bfbb0b5cc9027f1f81c0405c95b5b8c3f5c4352a3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI4Yjg4ZWMxM2YwNTU4YTZkMWMyNDE5NjVlOTI3Y2NkODdkNzlhY2Q3NzNhMjcwYzJjZjhjMDU3ODE0MDljNjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFmNmY2NDA3ZmVjZjhlZWM3YzI0ZmZjYzY2YjgwNmQzZTA5ZWQyZmI3MzUwNjlkYWJjMzBjMGVjMTY2MTliNmYifQ" + } + ] + }, + { + "id": "claim_5065d1f6ebd64ecab27645b10fd07fc8", + "statement": "Termind planning excludes remote SFTP mutations and scratch_push and reviewing receives no Termind tools; planning and acting share the ssh_exec schema while destructive-command preflight denies in planning and asks in acting, and proven-safe reads approve in parallel.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/PlanningSSHTools.swift#L7-L37", + "version": "repo-lines-v1:sha256:63c13612b283532ff8687a7aa1b8efa537e435acfb72f67c612bf4b7ba4b10ff:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkZWRiZjg4M2E5OTFkYzA3ZjQwMzVhZGFkODU3OTYyMzIxNzRjNzQ5NWJhOTMwNjlhZDJiZGQyOGYxZjQ3YTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiMzg5MzZjYTRiYTA5NDE1ZWFhYjk0ZTI2OTNlMzk3NzJhMmQ3NzQ4MjZiMzVjZTliZmQ2ZDAzNGJiMTc0NzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L81-L146", + "version": "repo-lines-v1:sha256:d28a016e97cfc3923d1b0e6641e2531a6c7d866c1ba850ef6bcae3f1586f6677:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJmMWY5ZGIzYzBkNGZjYzA4YjgxOWIxZmNkNzE5YjMzYzc5ZDljZGVjNDdkMWJhOWYxZGRhMmRmM2MwOWZhZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNjczYWJkNDU3YTk3ZWVhMTdhNjAwNjJlYjBmYWVhNmJkZjA5NDg2OGNjNmI4ZjhjMGViZGEyZjYxZWQ0MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiNzJhMmNhNDdjMTY2YWY3M2MzZDdiZjQxMzQwYTdjMmVjYWE1ZTYwMWEzYTlhOTFiMjZhNTUzMGYwM2NkZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3OTQxNzg4NDc3NTljMDg1ODZkYWRkZjhjMThjNjI1YzYyYzAzMjFhOWI4NGMwMzQ4MThiMTA3YzM2OTZiNjMifQ" + }, + { + "resource": "repo://TFMTests/AgentBuiltInToolAvailabilityTests.swift#L104-L152", + "version": "repo-lines-v1:sha256:809a5de195526464131f750190928f8a7f174538c892091e1b1ca5a04e3e1303:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxYzk1MmUxNDJjNjkzMjk4NmMwOTRjYmU3MzIxZmRmNzc2MWEwN2MyNTMxZTg0YmVkYzA1ODllZWQ0NzU0N2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE0YTFlOTRhZTRhNGU2NTI4YjQxODUxYzM4NWViMzIyMzhhMmY0YjQ2NTkxYzcyMTBkYWNmYWIwZTJmMTBjMzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlZjg1MGYyMjYzODY4ZTAzMTRiZWE3NmU0NTlmMWRiYzQ3NWY0NWFkN2RjMGRiZGJlMzQxYzE3ZDZhOTAyODYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhlZjg4NTUyZmE5YjUzZDI2ZDY4NDE4MmZmNDEwODg4NTAxNzVhM2EzMTcyYmY5Mjg3ZDY0Y2UxMWZmNzkzYjgifQ" + } + ] + }, + { + "id": "claim_b8c40ec0ba40450ea891e04123a70f0f", + "statement": "The terminal_suggest schema remains registered across Assistant surfaces, while its execution gate is captured in toolAvailability: TFM Workspace sidebar sends true, TFM standalone and TFI send false; this partial gate does not disable independent SSH/SFTP execution.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L388-L403", + "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L11-L16", + "version": "repo-lines-v1:sha256:a43583fa28f32662ba3edc9845073bfd7bfacb29b7c16c21536c56a4fe75a32e:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDQ3NTE0MGUyNmU5YzVhMTcxMWQ2NDQ1YTE4YjdmNGFlMjAyMmY5OTNlZjYyODQwNzU1NDMwMWI5NmUwZGMyMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMmI2ODNiYzA0Njk2MWVmOTJkOWI3Y2JkNmQ5YWNhZjJlOTk5Yzg2Zjg2ZjM1MzM1MTFiNWIyZTNmNjZhYjZmMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTJlNWI4YTJmMzJlNTczMDY2NDkyZTk4ZTUyNjFmZDQ1ZGQxMGYzZGQ1MDk3NjY2MzYwMzQzZjVjMWRmYzZhNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzlhM2ZjZTUzZjAwNjc0MmEyNjdhOThkZTNhZTI5Mjg2MzgzNmUwNzA3YTlmODgyZDllZGFhMzdmNjU5MTU1MiJ9" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L134", + "version": "repo-lines-v1:sha256:05f9c81933d87f71280921366aef45ca6b7595c2a37bbc09748f17358ab5a4ad:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiNmZkYjFiZmUzOGYyNzNlMzRkZjZjYzA4YTViMmRlMWNkN2IwM2M0N2FhZDE5Zjg5YjM1ZWU2ZjUwMzdjOTAifQ" + }, + { + "resource": "repo://TFM/Features/AI/ConversationTranscriptView.swift#L103-L110", + "version": "repo-lines-v1:sha256:541872e190e6d37c8cbb564bd335c76a333ede612d55b5bebc52340c1b584861:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2EyMDdlOTU2YmFkYTg5NzAwZTdkZDYyMzI4NjA3NzRmODk3YmYyZmIyNzlhN2EyZDFmNzgxMjU1YTQ5MTk4YSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODQxMTE4MWVlMmVkMzkyYTZmNzJlZGYyMTBhY2I1MjA0YjBmMzQ5YzM4NWM4NTAyYTBmOGRiY2RkYWJlMTZiZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2MxYTA5MGM3OTQzNmQyY2ExMTBlOGZkZDk2YTJhYjkzMGMzZTI4Njc1MzQ5NjU3ZGU4MWNlNDAzZmZkYWY2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTU1N2E1YTU0OGJlOGIyZjAzMzVlZmNjMDc2ZTVmZDQ2ZDMwYzQyNTdhMGY5Mzc0ZWM3MDE5ODczOTYyNDVmNiJ9" + }, + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L201-L227", + "version": "repo-lines-v1:sha256:3cd1bb860992a27cf2aee06e68ee551298ee0464e891894fee9736399936f21f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI3ZjNmZmEwM2YxMTJmOWYzOWMwOWNjYjQ4YmIxYzY4MmUzODBmNmY0YTI2ZmQzOWU2NDJmMmI5ZTllMGJhYjAifQ" + }, + { + "resource": "repo://TFMTests/AgentBuiltInToolAvailabilityTests.swift#L155-L186", + "version": "repo-lines-v1:sha256:322e4385cb4f30c48e2aede580612d470b10baca410ceceed97de8fc9a9b385e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4ZmRiMWE0ZmJiNDNlODMyMWI3MmU0Y2Y1ZmI2MDViMzJjMGRhMDlmMDVjZjQ2NjRkOTk1ZWFhYWZmZjk3YjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZlYzUwNDQwYjc4NjEyMDAwOWZkYmFiMzJiY2U1YmUyMjI2NGU4YTI3NjBkOWQ4NDY5MGM4ZTlmMDFmMTVmMzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVjYTRkZTNjNjI1OWM4ZmNkNTlmZGJkYWI2NWI1MDM4ZjRhM2YwYmNjYWU1OGRkYTAzMTJlZDNiNWQyNWNlYTIifQ" + } + ] + }, + { + "id": "claim_4296080efd51499ab9a11b9100c41210", + "statement": "Conversation steering changes the sending session and only the terminal_suggest availability key, preserves other overrides and skill/plan/memory context, leaves a legacy absent snapshot unknown, and updates activeContextSnapshot only when delivery occurs.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentSteeringTests.swift#L187-L233", + "version": "repo-lines-v1:sha256:c063ee9ee073185632ec59778a870a9595ae3c988770a96bd2dfee10f2ba5338:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyNTlmOTQwOWQzOTg3ODRhY2EzOWEzMGU1M2VmY2E0MjlkZDdhNzExZWY1NTZhMzhmYTZlNGRlY2VmYzIwNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZjljNDg3Y2RiYjcyYTAyZDg2OWMyNmNmMGM5ZDYwYTdmZDg4ZTBkMjcxYzM1MGM5MTgyNDM4YjYzY2E3YmIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1961-L1981", + "version": "repo-lines-v1:sha256:2f0d3e9bf56ee4865528a8d17aa69083d4c03d3719d26929e40b6b7fe5c80a70:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5MzFhZDdmZGZiNjJkOTVjY2U1NTA2NDE1NmIxYmM1Nzg5ZTU3ZjhiZDc5MDQ4OGM3MzU1ZWM4MDhkZTJlNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg1MjU4NmJjYTA5MDczZjY4Y2EyNDQxMWJiNTUzYTk2MjNiOWEwNTk4ZWNjM2QxNWE0ZmEzOWI2ODNjYzhkYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YjgzYmUzYzE2ZmJiMzE3MTQ1NzRmYjUyZDFhYjU4MWI3YTFiZmI2MTI2ZTI5YmI5ZjkxMWUxMzNiN2QzZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE1N2RkZTZiYTE3NTBhYTU3ZjU2MzdlZTdjM2JiYjU4YzU3NWJjMmU0YTA1ZWQyNGJjMTBmNWMyYTM2YjM0YzYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2507-L2534", + "version": "repo-lines-v1:sha256:fa1732f1c428eebb9fe5c6720dee5116ca6a8436ed3f480122180630d6cbb740:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5MGQ2NzM0OTMwMjgxMjBkMGRhN2IyZGI0MmRmMjU0NGU3YjI0YTNjNGY0MjVhZjkzMDI4NGI0ZGE2NTg2OGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjA1NGIyODExNGMxNmJlMWM4MmQ5NDFjNzc1OGM1NzFhNzcxNzBkYzM0ZTA1ZDUwZDllMDk3NGEyMjRlODQifQ" + } + ] + }, + { + "id": "claim_0f474c4805ab4e409b923408dd4549ab", + "statement": "Host discovery and SSH/SFTP work require no sidebar session or PTY; list_hosts exposes enabled protocols without credentials, including Telnet-only hosts, while SSH operations independently require SSH capability.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift#L256-L271", + "version": "repo-lines-v1:sha256:fce45075c99663778285b25c7abe8ece6aa908ebb6d1c53269dcbb03aa1a48bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhZDUwNmZjNWRkYjMwYTRlNTIyN2RiYzM1M2VkMWU2MTE5M2YwMjNmODhhNjU0YWRlODcyYmY3NmM2NTYxOTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjYmQ3MmQ2YTYyMzBhYTIyYTg4NTliMDc5OWM0ZjM4MjIxMmE1ZmFlYjI2ZGRiZDAxN2M1MmM3YzBhNWRkZmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5MWRhOWI2NzM5MGE2NWQxOWMzMWQwMDVkMjZmYzA4ODUwOTI1YWZlNWY3NzJmODc5MmVmMTI2Nzc1MDI5YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiZWNlODZhMjEwODAzNGFjYjkwNTA5OGY2YjBkNjIyOWIyOWMxMTM2Y2E2MzFiOWZkYjU0ZmZkY2Q2M2Y0YzkifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift#L6-L64", + "version": "repo-lines-v1:sha256:48da14999a958a09cb035525dafba38750e0bb081cc66da4c0f47e9e22b5143c:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNmU1MDljYjBiNDFmOGJhOTkxNzEwMDk3MDUwNGY5ZTUxMDNlMDI4OWIwNGNlODNhNDhjMWE1ZDAzZmQzNjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkZGI4NTc4MDBhNWQ3MjQ3ZmYyM2M1Y2Q1ZTUyYzU0N2JiZjRiYzM2NWI5YTc0MjY5OWVlMzNkYmIyODkyYzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3Y2ExZDJlZTI1NTc2ZjQwMmI0YjhkMWQzYTAzMzcxYTJmMjY1ZjNlMmIzYjJkZTllNWYyNzExM2UwY2JkOTkifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L190-L204", + "version": "repo-lines-v1:sha256:ddc000d6744d96886c57972da1be80dbc195567cc5d08777a91970d51d14d78a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4ZGM4Zjg1ZTg4MTk2NjcxOThmNDBjZmM2MDNmODg2NWQzOTUzN2Y4YTIwODdkZDdjMDkwZmU0MmZmYTE2MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliYjI1MGMxMWI4ZWVlZDllMDA0ODIxOGFlMDBmMmEzM2E3ODAyZmEzMDY2ZTA0MWMzY2RiNzI0MGYzMzVjMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNmNDIyYjk4MDFlMWIwNTliNWYwNzViNjFkZDAwZTZlYjY4MWFmOGY2OTJmZDU4ZDg2NTE2YjRmMzM4ODg1NDMifQ" + }, + { + "resource": "repo://TFMTests/AgentHostProtocolListingTests.swift#L8-L40", + "version": "repo-lines-v1:sha256:52cfc9d8f6ee2c924351cec308f735e811f4a107c9a394b3fac62324c76d104f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjODI4MTNjZjZlYzQ4NTFmYzUxZTEzMGMzM2Y3ZWY4MGI3MzhhMDlmYWFiY2VkZjE1NDNhN2M4NWQ5ZTMwNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwNjZjMGUyZDJhNzZhZGViYTcyYzdmMjY3OWJlMTNkM2UzZWM5MzgzNzFhODliNTUzM2I0OTk0OTQwZTBhOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_8c4ca4222f6048c1a1219542352d56ec", + "statement": "Historical tool cards resolve persisted presenter IDs through the immutable combined presenter registry or a closed legacy tool-name list; the terminal_suggest context key and presenter identities are independent of surface-registration arguments.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L388-L403", + "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L149-L158", + "version": "repo-lines-v1:sha256:c2cd49fd1e16036c717fbc1b42f836d5e74f05a170aa2948e44d0b2d1bf02f67:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjMzVhOTk3YWU3ZDJmYmEwOGI0NzEwMzY2MmEyMjJhN2Q2MjAyN2UyZjYzNjU4MTZiNjUwNjE4ODg0NjlhNjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkZDI0NDU4MmI2ZmIwM2QyM2I4NDE1YWJlY2QzNDZiMGM5ZjQ2NmI2Zjc1MmIwNjgxODljZTdlOWEyZGMzNTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1ZTIzNWFhNDZkYTJjNzQyOTRhYzg2MGNiMjNhMTI4NzQ5OGQ0MzU0ZTg5MDJlZDFiY2Y0NDc0ZmZhZDE4YmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L43-L78", + "version": "repo-lines-v1:sha256:bfbfe308d074e8ffa2b4e02d12f57a78b6d8948b8955edb455cab1248c9f8747:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxMWU4YjYyZTMzYmM0ODQxYzg2YTUyYmJmZDQyNGFlNDFlMGJiZWE4MjQyMmQxMGUwZDIwYjlmMzA1ZmI0MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU0ZWE1OTQ0YTVlNDQxYjEzN2M5ZmExN2VhNDhhNWE4OTUwMzgxZTMyNTk4MDk0YWQxNzg0ZDIxODY0ZDc1NjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxNWYzNzg5MWI0ZDRiYzMxOGEwMGY5MmRmZTE0YzVlMDIyYTczMzIwODc4MDYyNTdjOTUwYmZlZWE2NzE3MTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjYmU1NTQ5ODQ4YjQzMjZiN2RlMWJjYTM0NjI4MmQwZGNmOWYzZDk4OTM2ZTZmZDM5YWU1NTA1NTg0NGJiMGEifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L18-L80", + "version": "repo-lines-v1:sha256:d51c0c4c465d3934681538007cfee163e4405202fcc8ee34d0f5e06a361d8040:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMmZmNGQxNjM3ZTA0MzVlZjQ1OTNmZmZhNTE1MWI5ZDRkMmI0ODA4OWNiOWM0OWU5OTM0YjYyN2UyMjQ2ZGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI2YzYwOTBhNTdkNWMwNjNmMTJmMjgyMDZlZGUwM2VkODQzMDdlZTY1YTE5MmEwNTg0ZjY4YzA4ZjVkNTRiZjEifQ" + } + ] + }, + { + "id": "claim_3a0928a1336e4d36b595ecc6fb11fbb2", + "statement": "The desktop MCP server selects acting tools before exported-name and disabled-tool filtering, exposing shared host tools, request_user_secret and four HTTP transfer tools but no terminal, scratch, web, skill, plan or task tools; its stdio bridge only forwards and TFI provides no local server.", + "evidence": [ + { + "resource": "repo://TFI/App/MobileAppModel.swift#L47-L93", + "version": "repo-lines-v1:sha256:1056a9ca82b4056925102595ba293ee161bb0294cefe1f6475a3b90bb0c96c83:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ0NzUxNDBlMjZlOWM1YTE3MTFkNjQ0NWExOGI3ZjRhZTIwMjJmOTkzZWY2Mjg0MDc1NTQzMDFiOTZlMGRjMjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg4YTBiYjFhNzA4Y2RkN2FjN2YzMzAzN2QzYTFkNzNhZDA2NWMyODA2YTI3NTczYmE2OGYxM2M1Y2YwZWQ4YzMifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L179-L212", + "version": "repo-lines-v1:sha256:2bb74f25d66d630c0c93ab18f3f9d8a3d417d7ef27dfab256a29790e5c73ecd7:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdiZGIzN2RmNzgzMDk5M2FlNGFjYmU5ZjMzNzc5ZmY0ZDM4NDNjYTllZTVmNjRkMGUzNTM1YmVlYWE2ZWY1MWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkODIwMmZkOGJmZDcwYzFlMjFiM2RmMGEwOGY5YzI2MDYyMTMxMmQxNmVlZmY1YzdlODMzYmRlMDU5ZTg4MzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUzZGZiMWU0NTkxZTcxZTBmM2UxNmQwZTU2M2VjZjNlYWE5ZmJhNjZhNTg3MmZkOWNmYWZmOTA2ZTliZWM2YmIifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L7-L14", + "version": "repo-lines-v1:sha256:fad9923aac1ae9624586bae0f53dac344511e7a50b49909b279154a2e345c6ad:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGI0NjVkOWExMDQwNDZiYTI3NTgwNTQ3ZjNlZDgwMmEyNWExYWUxZjhjZWYxODYyNjMyZTY0ZGQwYjUwN2U3OCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmE0ZDY1ODhmOWI3YWQwODA5MzgyODExMDk1NTQ3ZTJjZDE4OGEzYTM0ZDMzM2E0OGQ4YmM5NzgxZDY0NTM5NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDRkMTAwNzkwOTYxYjkzNmIzNmM0MzJiZjdlOTY4NGI0MmIwZWJhYTE2MDM1YzdjMmQ0OTU4YmFkYmQyM2Y2YSJ9" + }, + { + "resource": "repo://TFMTests/MCPServerToolsTests.swift#L100-L138", + "version": "repo-lines-v1:sha256:48ab8222146a818c183b835519f4a64ebafbfab173934945b9fb1a14a4d986df:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0MWJjMzU3ZWE2YzA2MmNiNjE1MGRiZmIwY2Q3YTBhZTM4ZTA3MTllYzU1Y2QwOWY0OGY3Y2U4MGVjOTcyZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzY2FiZTJjOWMxZDJhNGE4MGNjMTVhNWE0ZDVjMDBhZjQxMGNlYmNhNGZhYTM4YWYwNmE3YzM4NDhhNWUzZWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + }, + { + "resource": "repo://TermindMCPBridge/main.swift#L4-L87", + "version": "repo-lines-v1:sha256:ecccf30acbd284a663e31c1aa7f40e224b2264fb038003a81dceaa30c6449866:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjZmJkYmI1MDE2YWM3ZGVlYTU2ZTg5MGFlOTNhZDc3MzY1Y2I0ZDhhMGQ4NjhiMmU2YzliZTNjYzExYzQ2NGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3M2Y1YTY5MGEwZjAwODY0Y2RmNDU3MjY3OTAxZjNkNzg5ZmRjYzgwNzQ5NzlmNWUzMDQ2ODVlMjFiMzNlNTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE0ODcxYWY5NDg1Yzg3ZThjMGM0ZGNhN2IyYTI3MThiOWY5YTJmN2QxM2RlYWM1Y2MyYTY5YzgyNWVhY2ViZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T03:23:14.035Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/concepts/remote-services-and-forwarding.json b/openwiki/.claims/concepts/remote-services-and-forwarding.json index 0543c203..7afb9d78 100644 --- a/openwiki/.claims/concepts/remote-services-and-forwarding.json +++ b/openwiki/.claims/concepts/remote-services-and-forwarding.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:df4482efaa9bf8c8456a93e90ca849b5c53f58754ec6b5f653cc9acffd52fda9", + "pageVersion": "sha256:e2720aa6320a8e366bf71c3fa0c7a03da39dc3ef8d3f20aebaabdaef585258a8", "claims": [ { "id": "claim_7c677489fd8b4a4fadd006a4da4e9937", @@ -12,7 +12,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift", - "version": "repo-file-v1:sha256:486ca4d2997672c82c417051e24392ee610f9f838dff89cffedaceff0891f9fd" + "version": "repo-file-v1:sha256:8ccfafe881aacc3f7232d30cef9c97588787771dd925d726b5591efa15c92021" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/SSH/PortForwardingController.swift", @@ -195,6 +195,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T11:34:21.807Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/integrations/mcp-server-and-bridge.json b/openwiki/.claims/integrations/mcp-server-and-bridge.json index 5d6185d3..0e048e1f 100644 --- a/openwiki/.claims/integrations/mcp-server-and-bridge.json +++ b/openwiki/.claims/integrations/mcp-server-and-bridge.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:d5354c90144215080870d9769a26cb62524794d9f08d9222bf13f1dfaab336a0", + "pageVersion": "sha256:25994ac645085f10b88bf658588df753039ac6bd5c79e6cdde785a0323fb495e", "claims": [ { "id": "claim_b47979774aa94e7dae517a6894c508d4", @@ -136,19 +136,23 @@ "evidence": [ { "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L183-L211", - "version": "repo-lines-v1:sha256:3c47a9f75666c7e0501e5cfe9367d0bf69ecdb6d889fe10cd823e8c92bfa297f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5OGU0MzcyYjgwNTM0ZmI5NjNmYzVlNGFmZDlhY2JlZGQ0NTNhZWEzYjU0ODAzMThmMWE2MWZmZTQzNGMyMTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYTYxNGM5ZTJkZjc0NWU4Y2ZkZTdiNGIwZThiMmE5OWYxYTNjMTM3YmEzMmE5YjE4YzQ4MzBkMDg1NTY0M2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" + "version": "repo-lines-v1:sha256:e65acffc19af44a25ec55b13ae180d24988b848bf32107c1dc55fdbb203c10dd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5OGU0MzcyYjgwNTM0ZmI5NjNmYzVlNGFmZDlhY2JlZGQ0NTNhZWEzYjU0ODAzMThmMWE2MWZmZTQzNGMyMTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYTYxNGM5ZTJkZjc0NWU4Y2ZkZTdiNGIwZThiMmE5OWYxYTNjMTM3YmEzMmE5YjE4YzQ4MzBkMDg1NTY0M2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" }, { "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L7-L13", "version": "repo-lines-v1:sha256:37e1cc2fd471a7feaba49f4edbfcbc40f04e035bbf4b08b3f68416aa598e3d88:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGI0NjVkOWExMDQwNDZiYTI3NTgwNTQ3ZjNlZDgwMmEyNWExYWUxZjhjZWYxODYyNjMyZTY0ZGQwYjUwN2U3OCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWRkMjQ0NTgyYjZmYjAzZDIzYjg0MTVhYmVjZDM0NmIwYzlmNDY2YjZmNzUyYjA2ODE4OWNlN2U5YTJkYzM1MCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmE0ZDY1ODhmOWI3YWQwODA5MzgyODExMDk1NTQ3ZTJjZDE4OGEzYTM0ZDMzM2E0OGQ4YmM5NzgxZDY0NTM5NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGY0ZWNlYzhkOWUyZDdhM2I4MjVjMTVlOTc2MjQwMmExZDZlZDIxMGI1YzVhNTQ5NWEwMjBjYmJiZDU1MzA1ZiJ9" }, { - "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L81-L112", - "version": "repo-lines-v1:sha256:9a47d3738e9223dc103b59b1c730fef89233e69f4ef011e9ff4a31d4646cf9e8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE4ZWM0MWMzYmVkYjQ5MGE1MzIyZDJkZWRmNGYzZDYyY2U0NjE5YzIwOWNlNmY5ODk2NWM5ZjgzMzRlMDVmYTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQwOWQxYTBlZGQxY2RmNzllMjI1Yzg3ZDFlODMxYmMwMTU0ZGExNzA1NTZhZTFkMzBlMzZkNDg2ZjhkZTg3OTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM2OGE1Yjk5MDRlNmQzOTZhNzllNGY1OWYzMmNjODc2NDA3MmUwZmMxZTJiYzg4MWM2YmIzNWViNGU3MjliM2IifQ" + "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L81-L118", + "version": "repo-lines-v1:sha256:7f52edfeccd8a684b8b897873405fa21702498b14be32417802dd124eaa3b2ae:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE4ZWM0MWMzYmVkYjQ5MGE1MzIyZDJkZWRmNGYzZDYyY2U0NjE5YzIwOWNlNmY5ODk2NWM5ZjgzMzRlMDVmYTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNTYyYzdmNzMzNjJhZTBlYzk1MmM4M2ViMGJjYjIxYmE2YjBjNTM4MzY0YjExYzAwMGNlM2ZkZWU2NjNhZDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQwOWQxYTBlZGQxY2RmNzllMjI1Yzg3ZDFlODMxYmMwMTU0ZGExNzA1NTZhZTFkMzBlMzZkNDg2ZjhkZTg3OTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3ZmZjZjA0MWQ2MGRmODliNjFiYTQ4MDFmZGUxMmE0ZDI0MTBiZjJiZmJiZjRiZjc3YTcxOWQ4MTNhYzg4NWIifQ" }, { - "resource": "repo://TFMTests/MCPServerToolsTests.swift#L99-L144", - "version": "repo-lines-v1:sha256:ac61705a90aec4fc5ad8fe90ba5336d9d529b77717009ddbf93e6f52d1fbfe85:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkZDA4NDk0MjZmMmNmMDE4MTZiNTkwMzQ0YTBkZTI0NDUzNTVkZjliNDdmNjYyMGY4YjA0ZTI1OWYyMDg0YzgifQ" + "resource": "repo://TFMTests/MCPServerToolsTests.swift#L100-L140", + "version": "repo-lines-v1:sha256:bbe609256f29f5d3f57df07fd079fd2175c599b92d24faf60060b97a17b592d7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0MWJjMzU3ZWE2YzA2MmNiNjE1MGRiZmIwY2Q3YTBhZTM4ZTA3MTllYzU1Y2QwOWY0OGY3Y2U4MGVjOTcyZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjYzgwYmFjZGQ0ZWUwOTdkYmRlMzFjZTEzNGIzM2YyODlhMzVhMWM1ZTBmZjNhYmFhZWI3YTVhNmY5ZmVmNDEifQ" + }, + { + "resource": "repo://TFMTests/MCPServerToolsTests.swift#L163-L175", + "version": "repo-lines-v1:sha256:bdac8ae222cc6ca064fa19fd6dff53d846496c96a833df866044e6f194e139c4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5MTFhNjcyMTJjNDdjOGU2Zjc3YzQyOWVmM2YyYmIzMTQ3MDY3OWVhNjEzMDc1OWJkZDZkZWUyYzVhY2MwNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkZDA4NDk0MjZmMmNmMDE4MTZiNTkwMzQ0YTBkZTI0NDUzNTVkZjliNDdmNjYyMGY4YjA0ZTI1OWYyMDg0YzgifQ" } ] }, @@ -289,10 +293,32 @@ "version": "repo-lines-v1:sha256:676f68b4f8a905336cf6f8b0b5bc81a72bcf7ea601c3b8f4e551d921c3876c36:eyJzZWxlY3RlZExpbmVDb3VudCI6OTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiOTg0YjBlZTVlMDZlNWRlNzQzMTYwZjc2MTlkMDNiMjNmMTFjOTU2ODUxYmU5MWRmNDUzYmVlY2UzMzU4MzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjExMTM5NDQ5ODE1YWRkNzZiMTM3OGJkODhiMDU3MmQ5MTk5MjA2YTdhYTQyZmM2ZGVjYTk2MjkzYWRjMGMxZjQifQ" } ] + }, + { + "id": "claim_42f9ef4c43524e90b670a096c7be1b1c", + "statement": "The MCP registry composes shared Termind tool definitions without a scratch workspace, AgentKit's user-interaction group, and four transfer tools; it explicitly selects acting mode before exported-name and disabled-tool filtering, so its 18 exports use acting ssh_exec and exclude terminal_suggest and scratch tools. A regression test checks shared descriptor parity with the acting host subset.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L131-L146", + "version": "repo-lines-v1:sha256:115a05de9a5dfe6bb311e843026f4ed7d847445773c7c667c42bdaa0f904cb0f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1MmVhOTZlMjgzMzE3ZTg1M2E5MjFkNTJmYzg0OTllNGQyM2MxMTI3NDQ1ZDEzOWUyYzlmM2E4NDBkOGE3ODciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNjczYWJkNDU3YTk3ZWVhMTdhNjAwNjJlYjBmYWVhNmJkZjA5NDg2OGNjNmI4ZjhjMGViZGEyZjYxZWQ0MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI3NmI3Y2RhMDE5NDc2YjllZmM4NmVlNGU2YzQ0NzNkNWEyYTA3ZjZiMjk1MDFlN2QyZDNjMTcyMWZkZjFlYmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3OTQxNzg4NDc3NTljMDg1ODZkYWRkZjhjMThjNjI1YzYyYzAzMjFhOWI4NGMwMzQ4MThiMTA3YzM2OTZiNjMifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L88-L103", + "version": "repo-lines-v1:sha256:dbe61a3ba9b1ab79a313f4945a50099e4d2aea4d3be81879cf5988c566ff33f4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY2ZGRhY2EwZGI3ZWZlN2MzYWRlNDlmMDM0ZGYyMmFhNmY2YTkyZGFlOWRkZGU3YzI4NjUyYjg3ZWFmNmMyNWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNjczYWJkNDU3YTk3ZWVhMTdhNjAwNjJlYjBmYWVhNmJkZjA5NDg2OGNjNmI4ZjhjMGViZGEyZjYxZWQ0MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFjNjdjZjE4NTFhZDE4ZDRiZmI3ODYyYjQxNGFkODRlZTI3N2Y5Y2MwZDQ5MTQxOGVkMTUwNzM0YTc4MWM0NWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1OTk0MWEwZmZkZmM0OGQ3NTA1NzFhMjc0Zjc2YjdjNGNhOTA2NjMwNjBmNTcxZjVhNGY3ZDZhNjExOTBmNDEifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPServerTools.swift#L183-L211", + "version": "repo-lines-v1:sha256:e65acffc19af44a25ec55b13ae180d24988b848bf32107c1dc55fdbb203c10dd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5OGU0MzcyYjgwNTM0ZmI5NjNmYzVlNGFmZDlhY2JlZGQ0NTNhZWEzYjU0ODAzMThmMWE2MWZmZTQzNGMyMTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYTYxNGM5ZTJkZjc0NWU4Y2ZkZTdiNGIwZThiMmE5OWYxYTNjMTM3YmEzMmE5YjE4YzQ4MzBkMDg1NTY0M2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" + }, + { + "resource": "repo://TFMTests/MCPServerToolsTests.swift#L100-L140", + "version": "repo-lines-v1:sha256:bbe609256f29f5d3f57df07fd079fd2175c599b92d24faf60060b97a17b592d7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0MWJjMzU3ZWE2YzA2MmNiNjE1MGRiZmIwY2Q3YTBhZTM4ZTA3MTllYzU1Y2QwOWY0OGY3Y2U4MGVjOTcyZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjYzgwYmFjZGQ0ZWUwOTdkYmRlMzFjZTEzNGIzM2YyODlhMzVhMWM1ZTBmZjNhYmFhZWI3YTVhNmY5ZmVmNDEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 28641f57..0e928087 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:36dd6088760d21cfecb2ca2fffc6e6ea9aa667c3f14011c61dcbb354b0377150", + "pageVersion": "sha256:61210d26025511ef69e04ecbb09c4b5266e8a8b365fdc612edc432f355271193", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -38,7 +38,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -90,7 +90,7 @@ }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift", - "version": "repo-file-v1:sha256:9a6ac5989bbe56b568db0e639c84f0608a8dc5b4cd089438c64d91a587a91527" + "version": "repo-file-v1:sha256:48b3039f44d716afaacc3e0986a88fcaad3a32d6fa00a68dba265b82e0efc284" }, { "resource": "repo://Termind.xcodeproj/project.pbxproj", @@ -129,6 +129,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.claims/workflows/ssh-session-lifecycle.json b/openwiki/.claims/workflows/ssh-session-lifecycle.json index 50cc14dd..e0e246f5 100644 --- a/openwiki/.claims/workflows/ssh-session-lifecycle.json +++ b/openwiki/.claims/workflows/ssh-session-lifecycle.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:5cad513345c5cd73a1bb915584cf0566e8f1a8f543c5e5dd4da91692e6407352", + "pageVersion": "sha256:2440bbfacd6925e8874548c87942c4f02c29835160dde94f309fb7abc29758f5", "claims": [ { "id": "claim_69a9de4ce918417c8a44c6c87c9f6564", @@ -146,7 +146,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4bff3bbe87d980066344224e6c189b784360bb2c0fc360d138b4bb0a6d816420" + "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" } ] }, @@ -175,6 +175,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T01:34:03.251Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index ac5160f5..ce389c2c 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T01:00:12.175Z", + "updatedAt": "2026-09-23T01:40:20.785Z", "command": "update", - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index e6b56146..b8200e54 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,74 +2,74 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:fad06b683f4e91f808959ed85ce962b1c86d64a67078b39c7ba20f8aa12ad35b", + "pageVersion": "sha256:9e4dcfe15396af0c8e9ed77ff7347eac829aeff9d111ad37757a2e3f1e63b2c1", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:6f98da9e8f070ebc2a0b91545058eb3aa5a411129c7a18c9b33ee9eb6da8e799", + "pageVersion": "sha256:75fe464fb65e761e5eeaab3f31f1a2d852175cc5f78f5b099ddffee4a8655971", "completedBy": "codex", - "completedRunId": "9d54b362-29b2-42d6-9960-30b93a494d0b", - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/mobile-ui.md": { - "pageVersion": "sha256:769919ef95916ccc91f27ef2666730600005e79c052893c2ae098644ac389a07", + "pageVersion": "sha256:a1e240ca495ddf76ace3e9861b2f459a2fa61e2d53179352e87b15452e6828ee", "completedBy": "codex", - "completedRunId": "9d54b362-29b2-42d6-9960-30b93a494d0b", - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/module-map.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:8c5b708036c7d089f4821e24e31a00e1684e44d78bf3a8182b9df0800ffe7f00", + "pageVersion": "sha256:cd736475053bed4751ff7b4f06ee0433c8d3ac3e74f3866d585bc958dd6b25d5", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/native-app-directories.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:411c153b0239360e9fc0a413c9ff012607f8aa71dfd42cf349bcfc83eb2fb690", + "pageVersion": "sha256:daaecfb65b33663c617a5627c7c65fc6e45cd05b85017d53e6a48c818a1e15b5", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/platform-runtime.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:1928bdff8b5655653f6d6eaa66489a0a3c967dfebdf9c9c86245df87bf2011c1", + "pageVersion": "sha256:f6b1eae907640a27121b4c2f41cc040490003e2944d38858bc7a4719581bd0b3", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/shared-module-directories.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:6ba56baf56fd5439756c528b55b7765f506ed716677142f0298cbad9f4cf2f93", + "pageVersion": "sha256:42206a596cfd036cd1cf7bd7bc2f0e8ec5f96a4577648a864cebd3f8db5d0a3f", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/supporting-system-directories.md": { - "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", - "sourceFingerprint": "sha256:c257a6e01df7463636d1e57348f233f373c8ca070007bc8d15c824601c77538f", - "pageVersion": "sha256:fa243e662bcccf538fa4c830c781851a26ec9aea0c86163cfc99644ac7364159", + "pageVersion": "sha256:2711b0e7d443185dd21a3d96c2cb80f84a5e2149a5a6fc64fda5363afcbc86be", "completedBy": "codex", - "completedRunId": "8bdb277a-cf85-4174-a91f-b6a4f89a046d" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/architecture/system-boundaries.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:db9f09d0c4799f6c8ecc04c8b993af8c45c2c71fbb32827587c3c99c945c3c2b", + "pageVersion": "sha256:3cbf1be9eb94f0eb87d83be0a1277c9801cb4df9a3da898ef95b63b20b72a785", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", - "sourceFingerprint": "sha256:8456f71ffdd978736ff6bf9aed042724573fd633b1d40f6d2b74fec0acb445f6", - "pageVersion": "sha256:6ba8f89374ad018a257cf1c10e91f9d4b3641420f6187d07c8829858671948d1", - "completedBy": "omp", - "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" + "pageVersion": "sha256:47fa6bbaa4a43b7bac039520e0bd6d4cf3f003b8969821fecaebe1cd9bdeac6b", + "completedBy": "codex", + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -86,11 +86,11 @@ "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/concepts/remote-services-and-forwarding.md": { - "gitHead": "7bd483abc1213e20114deb41e71be4a6bce3b048", - "sourceFingerprint": "sha256:81824d0e5565b003414e71b5a66a7cd8b72768e06ea52f5391c6b25a6fefa67c", - "pageVersion": "sha256:df4482efaa9bf8c8456a93e90ca849b5c53f58754ec6b5f653cc9acffd52fda9", + "pageVersion": "sha256:e2720aa6320a8e366bf71c3fa0c7a03da39dc3ef8d3f20aebaabdaef585258a8", "completedBy": "codex", - "completedRunId": "3202e468-3230-40c9-80ee-55ba3e53e4b6" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/concepts/shell-integration.md": { "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", @@ -114,11 +114,11 @@ "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/integrations/mcp-server-and-bridge.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:d5354c90144215080870d9769a26cb62524794d9f08d9222bf13f1dfaab336a0", + "pageVersion": "sha256:25994ac645085f10b88bf658588df753039ac6bd5c79e6cdde785a0323fb495e", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/operations/build-test-and-release.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -156,11 +156,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:36dd6088760d21cfecb2ca2fffc6e6ea9aa667c3f14011c61dcbb354b0377150", + "pageVersion": "sha256:61210d26025511ef69e04ecbb09c4b5266e8a8b365fdc612edc432f355271193", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:5cad513345c5cd73a1bb915584cf0566e8f1a8f543c5e5dd4da91692e6407352", + "pageVersion": "sha256:2440bbfacd6925e8874548c87942c4f02c29835160dde94f309fb7abc29758f5", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" } } } diff --git a/openwiki/architecture/desktop-ui.md b/openwiki/architecture/desktop-ui.md index 85280a2f..0a171d66 100644 --- a/openwiki/architecture/desktop-ui.md +++ b/openwiki/architecture/desktop-ui.md @@ -5,7 +5,7 @@ description: "How the macOS app composes its native window shell, persistent wor tags: [macos, tfm, ui, navigation, workspaces, swiftui, appkit, design] verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z sources: - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index cf395f2d..a7bc49e0 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -107,7 +107,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:59:31.979Z + at: 2026-09-23T01:34:03.251Z --- # Cross-Cutting Engineering Invariants diff --git a/openwiki/architecture/mobile-ui.md b/openwiki/architecture/mobile-ui.md index 9d43a29c..1a48dfdc 100644 --- a/openwiki/architecture/mobile-ui.md +++ b/openwiki/architecture/mobile-ui.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:59:31.979Z + at: 2026-09-23T01:34:03.251Z --- # TFI Mobile UI Architecture diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index 615c17b5..ebaeacaa 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 83660e8a..5509802a 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index f22616f1..eceeb8ba 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # Platform Runtime, App Composition, and Lifetimes diff --git a/openwiki/architecture/shared-module-directories.md b/openwiki/architecture/shared-module-directories.md index 086cfdf2..0c7db681 100644 --- a/openwiki/architecture/shared-module-directories.md +++ b/openwiki/architecture/shared-module-directories.md @@ -107,7 +107,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # Shared Framework Directory Responsibilities diff --git a/openwiki/architecture/supporting-system-directories.md b/openwiki/architecture/supporting-system-directories.md index 0c290e8f..23521070 100644 --- a/openwiki/architecture/supporting-system-directories.md +++ b/openwiki/architecture/supporting-system-directories.md @@ -81,7 +81,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:45:03.442Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T10:02:02.985Z + at: 2026-09-23T01:34:03.251Z --- # Supporting Products, Packages, and Tooling diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index 94e1572f..ed6e9a8a 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # System Boundaries and Dependency Ownership diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index 8ba9a726..f1faa9af 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -3,12 +3,9 @@ type: agent runtime concept title: Assistant, Agent Runtime, Skills, and Memory description: How Termind assembles AgentKit into durable assistant conversations, applies approval and host-security boundaries, and manages skills, scratch workspaces, and device-local Hermes memory. tags: [assistant, agent-runtime, approval, persistence, skills, memory, ssh, sftp] -verified: - - by: openwiki/0.5.2 - at: 2026-09-22T03:23:14.035Z sources: - - id: openwiki-source-66f32c7d0994c9f18c0e5a98 - resource: repo://.build/verify-zh-hans/ios/SourcePackages/checkouts/AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift + - id: openwiki-source-0e8cfe7d74554d8a17af60b7 + resource: repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md - id: openwiki-source-5f99e8af445e073ffe9bc257 @@ -25,10 +22,36 @@ sources: resource: repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift - id: openwiki-source-1fc00d0407016efa5a0378eb resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift + - id: openwiki-source-0c82bf45bc197f879a54fd3a + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift - id: openwiki-source-e1a028bd99d4dbe6d1e14f6a resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentScratchTransferTools.swift - id: openwiki-source-1c845e5af67c400566be83bd resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift + - id: openwiki-source-2a267454c356289bd66e8b40 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/PlanningSSHTools.swift + - id: openwiki-source-d2d4c06f734241db3badc1f5 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift + - id: openwiki-source-6d759b4cb61f68a067dae42c + resource: repo://FrameworkTests/AgentSteeringTests.swift + - id: openwiki-source-a62bfbbc7de13a894444718b + resource: repo://TermindMCPBridge/main.swift + - id: openwiki-source-906678adb91c2fce2d3fcaed + resource: repo://TFI/App/MobileAppModel.swift + - id: openwiki-source-d44869be46c03fbd0ce9c64c + resource: repo://TFI/Application/Agent/MobileAssistantModel.swift + - id: openwiki-source-a154b5d36ec59496392d45e4 + resource: repo://TFITests/MobileAssistantTests.swift + - id: openwiki-source-76b1cdca4a02038756187f92 + resource: repo://TFM/Application/MCPServer/MCPServerTools.swift + - id: openwiki-source-16db5d5459897d40fe080701 + resource: repo://TFM/Features/AI/ConversationTranscriptView.swift + - id: openwiki-source-d35453512ef488d0cacdc06e + resource: repo://TFM/Features/Assistant/AssistantView.swift + - id: openwiki-source-49c5ca81284394384f3c3913 + resource: repo://TFMTests/AgentBuiltInToolAvailabilityTests.swift + - id: openwiki-source-8f676dd306369e67985f3dd0 + resource: repo://TFMTests/AgentHostProtocolListingTests.swift - id: openwiki-source-715de8d16be5a3d06ae915ad resource: repo://TFMTests/AgentPersistenceTests.swift - id: openwiki-source-d57db929d0047db53dd73cdd @@ -37,7 +60,12 @@ sources: resource: repo://TFMTests/AgentScratchTests.swift - id: openwiki-source-7e117096f381ecc32caff068 resource: repo://TFMTests/AgentSkillTests.swift -generated: { by: "openwiki/0.5.2", at: "2026-09-22T00:31:10.149Z" } + - id: openwiki-source-8d265a8732f0af93c5d4e274 + resource: repo://TFMTests/MCPServerToolsTests.swift +generated: { by: "codex", at: "2026-09-23T01:34:03.251Z" } +verified: + - by: openwiki/0.5.2 + at: 2026-09-23T01:34:03.251Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -52,7 +80,7 @@ This split is a security boundary as well as a modularity boundary. A model prop A runtime is deliberately single-run. It owns one event stream and one event consumer. `ConversationModel` owns the live runtime and projects its events into visible messages, tool cards, approval and input queues, run summaries, and the authoritative model transcript. The visible message list is not replay history: tool calls and results are separately rendered as cards, so replay must use `transcript`. -Tool availability is fixed at run start. A tool-capable model receives the resolved skill catalog, AgentKit built-ins, and Termind tools; a model without tool-call capability does not receive the skill/tool catalog. Session facts, plan posture, surface availability, and the memory-session identity are captured in an `AgentTurnContextSnapshot`, rather than reread from mutable UI state while the run is executing. +The registered tool catalog and run mode are resolved at run start. A tool-capable model receives the resolved skill catalog, AgentKit built-ins, and Termind tools; a model without tool-call capability does not receive the skill/tool catalog. Session facts, plan posture, surface availability, and the memory-session identity are captured in an `AgentTurnContextSnapshot`. Surface availability follows the latest delivered user snapshot rather than mutable UI focus. Termind composes the registry in this order: @@ -60,6 +88,23 @@ Termind composes the registry in this order: 2. Termind's host tools: host/metrics discovery, `ssh_exec`, terminal suggestions, SFTP operations, and the host-facing `scratch_pull` / `scratch_push` endpoints. 3. Discovered MCP tools, which are additive and cannot shadow a built-in or Termind tool name. +### Catalog, run mode, and surface availability + +`TermindAgentTools` is the shared static registration point. Its injected environment supplies host resolution, metrics, connection/authentication pools, background commands, and an optional scratch workspace. Dependencies determine which capabilities can be constructed: `scratch_pull` and `scratch_push` need that workspace, while `AssistantToolRegistry` returns an empty registry for a model without `toolCall`. A sending-surface Boolean is not a registration input; equivalent dependencies produce the same definitions for desktop and mobile Assistant surfaces. + +AgentKit selects the executable registry for the run's mode. Planning retains discovery, metrics, SSH investigation, SFTP reads and `scratch_pull`; remote SFTP mutations and `scratch_push` are acting-only, and Termind contributes no tools to reviewing. Planning and acting share the `ssh_exec` descriptor and schema, but planning uses a preflight that denies destructive commands. Acting asks for those commands; commands proven safe can be approved and run in parallel in either mode. + +`terminalSuggestionsAvailable` is the application argument that captures the separate execution gate in `AgentTurnContextSnapshot.toolAvailability["terminal_suggest"]`. It does not remove or change the advertised tool schema: + +| Surface | Terminal suggestion execution | Tool definitions | +| --- | --- | --- | +| TFM standalone Assistant | `false` | Shared Assistant catalog. | +| TFM Workspace Assistant sidebar | `true` | Shared Assistant catalog. | +| TFI standalone Assistant | `false` | Shared Assistant catalog. | +| macOS external MCP server | Not exported | Acting host subset, `request_user_secret`, and four HTTP transfer tools. | + +This snapshot dictionary is a partial override: disabling terminal suggestions does not disable independent SSH/SFTP work. The MCP server explicitly selects acting definitions, then filters by its exported-name allowlist and disabled-tool settings. It excludes terminal suggestions, scratch, web, skill, plan and task tools; its four extra tools are `sftp_upload`, `sftp_upload_status`, `sftp_download` and `sftp_download_status`. The stdio bridge forwards requests to this server and registers no second tool set; TFI has no local MCP server. See [MCP Server and Bridge](../integrations/mcp-server-and-bridge.md) for the external authorization and byte-transfer boundaries. + ## Turn lifecycle A submitted prompt is persisted as part of the run snapshot before model work begins. AgentKit streams a provider turn, emits ordered UI and durable events through a shared `AgentEventChannel`, validates any complete tool call, then schedules execution. Tool results are appended to the model transcript and persisted before the following model round. This preserves a grammatically answerable transcript even for refusal, cancellation, or failure paths. @@ -103,6 +148,8 @@ This sequence shows one assistant turn, including the persistence points around While a run is live, a further user message is queued as steering rather than starting another run. It becomes visible and durable only at the next model boundary, when AgentKit delivers it to the model; if the run has already ended, the UI returns queued text and attachments to the composer. This prevents history from claiming that a message was delivered when it was not. +Steering captures the sending session context and updates only the `terminal_suggest` availability entry. It preserves other tool overrides, the fixed skill catalog and plan contract, and the memory-session identity. Queuing does not change `ConversationModel.activeContextSnapshot`; delivery does. If the conversation has no legacy snapshot, steering keeps that context unknown instead of filling it from current UI state. The persisted availability key remains `terminal_suggest`. + Stopping a conversation cancels the runtime and its tool work, discards run-scoped secrets, marks running cards interrupted in the presentation, and returns undelivered steering text. `stopAndWait()` waits for the runtime's cancellation/persistence path before clearing live ownership. Termind also invalidates pending preparation when its controller is cancelled, recording an interrupted snapshot rather than launching a stale run. ## Approval, secrets, and trust boundaries @@ -121,7 +168,7 @@ SSH authentication and host-key prompts are Termind interactions, not model-visi ## Host tools and plan mode -Termind tools operate through noninteractive per-command SSH channels and exclusive SFTP leases; they never type into the user's interactive terminal. `terminal_suggest` creates a user-operated command card and never executes it. The standalone Assistant can discover and target configured hosts even without a sidebar session; session context is only an optional default target. +Termind tools operate through noninteractive per-command SSH channels and exclusive SFTP leases; they never type into the user's interactive terminal. `terminal_suggest` creates a user-operated command card and never executes it. The standalone Assistant can discover and target configured hosts even without a sidebar session or open PTY; session context is only an optional default target. `list_hosts` lists enabled protocols without credentials, including Telnet-only hosts. SSH/SFTP execution requires SSH to be enabled; discovering a Telnet-only host does not provide an Agent Telnet execution capability. Plan mode freezes at submission. It permits investigation, reads, and policy-allowed commands, but host-specific planning registrations refuse direct remote writes, deletes, and scratch pushes. A plan is drafted in scratch space and presented as the final action; presenting it does not itself suspend a run awaiting a decision. @@ -139,6 +186,8 @@ A fork copies its scratch scope before creating the forked conversation and remo A saved context snapshot preserves the original session and policy baseline for replay, retry, edit, and fork behavior; legacy absent context remains unknown rather than being replaced by current UI state. Persisted text, tool arguments, titles, reasoning, and attachments go through redaction. In particular, secret handles are replaced with expired lifecycle content on restore, and sensitive header values are redacted. +Tool cards resolve their persisted presenter ID through the immutable presenter registry. Cards predating those IDs use the closed legacy tool-name list and its `builtin.` IDs. Registration and surface-availability changes preserve those identities and the persisted context key; they do not require a SwiftData schema change. + Compaction changes the **model-facing** transcript, not the scrollable display history. Its summary record is persisted before the live conversation applies it; after recovery the model receives a synthetic compaction summary plus recent messages while the user can still read the full original conversation. Editing a user message truncates the target run and later runs atomically, creates the replacement turn, and removes compactions whose boundary was removed. At repository initialization, every nonterminal persisted run becomes `interrupted`; Termind never replays unfinished execution automatically. Restore reconstructs completed tool cards and can recover a durable tool result that was recorded before its next transcript snapshot. If an outcome remains unknown, repair records an explicit unknown/refusal result rather than assuming a call was never dispatched. @@ -188,6 +237,9 @@ The focused tests establish behavior that is important when changing this area: - `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, deletion of the persistent graph, edit truncation, fork persistence, and compaction restoration. - `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order mutations, activity guards, and immediate stop before a queued runtime can begin. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. +- `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. +- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, and tool-incapable models. +- `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. ## Change guidance diff --git a/openwiki/concepts/remote-services-and-forwarding.md b/openwiki/concepts/remote-services-and-forwarding.md index 6ab397e5..7f014cbf 100644 --- a/openwiki/concepts/remote-services-and-forwarding.md +++ b/openwiki/concepts/remote-services-and-forwarding.md @@ -36,7 +36,7 @@ sources: generated: { by: "omp", at: "2026-09-22T02:43:23.140Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-22T11:34:21.807Z + at: 2026-09-23T01:34:03.251Z --- diff --git a/openwiki/integrations/mcp-server-and-bridge.md b/openwiki/integrations/mcp-server-and-bridge.md index e79d90e4..a2e2b9ff 100644 --- a/openwiki/integrations/mcp-server-and-bridge.md +++ b/openwiki/integrations/mcp-server-and-bridge.md @@ -6,6 +6,8 @@ tags: [mcp, macos, loopback, authentication, sftp, bridge] sources: - id: openwiki-source-1fc00d0407016efa5a0378eb resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift + - id: openwiki-source-d2d4c06f734241db3badc1f5 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift - id: openwiki-source-a62bfbbc7de13a894444718b resource: repo://TermindMCPBridge/main.swift - id: openwiki-source-906678adb91c2fce2d3fcaed @@ -40,10 +42,10 @@ sources: resource: repo://TFMTests/MCPTransferTicketTests.swift - id: openwiki-source-47dc468698b2904f0004ee04 resource: repo://TFMTests/MCPUploadTests.swift -generated: { by: "openwiki/0.5.2", at: "2026-09-22T00:31:10.149Z" } +generated: { by: "codex", at: "2026-09-23T01:34:03.251Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # MCP Server, Transfer APIs, and Stdio Bridge @@ -119,7 +121,9 @@ A session owns a UUID run ID and a `SecretBroker`. The protocol supports at most ## Tools, policy, and external authentication -The exposed registry is an allowlist—not the complete AgentKit catalog. It includes host discovery and cached metrics; `request_user_secret`; `ssh_exec` and `ssh_wait`; ordinary SFTP inspection/mutation/copy operations; and upload/download ticket/status tools. Scratch workspaces and generic fetch tools are intentionally excluded because an external MCP client has no Termind conversation backing them. +The exposed registry is an allowlist—not the complete AgentKit catalog. It includes host discovery and cached metrics; `request_user_secret`; `ssh_exec` and `ssh_wait`; ordinary SFTP inspection/mutation/copy operations; and four upload/download ticket/status tools, for 18 exported names. `terminal_suggest` is not exported. Scratch workspaces and generic fetch tools are intentionally excluded because an external MCP client has no Termind conversation backing them. + +`MCPServerTools.makeRegistry` composes AgentKit's user-interaction group, the same `TermindAgentTools` definitions used by Assistant, and the four MCP transfer tools. It supplies no scratch workspace, so the dependency-based factories cannot construct `scratch_pull` or `scratch_push`. It then explicitly selects `.available(in: .acting)` **before** applying the exported-name allowlist and disabled-tool filter. Thus MCP uses the acting `ssh_exec` implementation, not Assistant's planning wrapper; mode selection, dependency injection, and export policy are distinct boundaries. The stdio bridge forwards requests to this registry rather than building another catalog. Disabled tools are removed from discovery **and** invocation, not merely hidden. Changing a tool switch rebuilds the registry immediately; `sftp_upload` and `sftp_download` also enable or revoke their directional transfer coordinators. This makes disabling a transfer direction revoke existing tickets rather than allowing old URLs to revive after re-enablement. @@ -184,7 +188,7 @@ For direct HTTP clients, settings generates quoted setup commands for Claude Cod * Enable the server in macOS settings only after Pro access is available, generate or rotate a token, then copy a client registration command. Treat that token like a local credential and update every client after rotation, port change, or moving the app when using the bridge. * Use the server status and connected-client count to distinguish stopped, starting, ready, and failed listener state. Listener failures stop transfers; invalid port/token storage disables activation rather than exposing a partially configured server. * To immediately cut off an external client, disable the server or rotate its token. To reduce surface area, disable individual tools; disabling either transfer tool revokes its tickets. -* Test changes at the correct seam: `MCPServerSessionTests` exercises initialization, version/session requirements, tool discovery, legacy SSE, and deletion cancellation; `MCPServerToolsTests` verifies allowlisting, disabled tools, session-bound secrets, and ticket creation; `MCPUploadTests` and `MCPDownloadTests` cover resume, claim, digest, cancellation, status, and wire behavior; `MCPClientSetupCommandTests` checks exact, shell-safe client registration arguments. +* Test changes at the correct seam: `MCPServerSessionTests` exercises initialization, version/session requirements, tool discovery, legacy SSE, and deletion cancellation; `MCPServerToolsTests` verifies all 18 exports, shared descriptor parity with the acting host subset, excluded tools, disabled invocation, session-bound secrets, and ticket creation; `MCPUploadTests` and `MCPDownloadTests` cover resume, claim, digest, cancellation, status, and wire behavior; `MCPClientSetupCommandTests` checks exact, shell-safe client registration arguments. ## Related pages diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index e4d14f62..08d1d927 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -25,7 +25,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:23:14.035Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # Termind Engineering Quickstart diff --git a/openwiki/workflows/ssh-session-lifecycle.md b/openwiki/workflows/ssh-session-lifecycle.md index bae784a6..49187151 100644 --- a/openwiki/workflows/ssh-session-lifecycle.md +++ b/openwiki/workflows/ssh-session-lifecycle.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T01:34:03.251Z --- # SSH, Telnet, Trust, and Workspace Session Lifecycle From 2b1c5668b6fa608269c4b6c897aab672adf0b64c Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 10:40:15 +0800 Subject: [PATCH 05/30] Freeze Assistant turns before preparation --- .../AssistantExecutionControllerTests.swift | 682 ++++++++++++++++ .../AssistantConversationOperations.swift | 16 +- .../Agent/AssistantExecutionController.swift | 733 ++++++++++++++---- .../Agent/AssistantLibraryModel.swift | 23 + .../Agent/ConversationModel.swift | 69 +- .../SwiftDataAgentRunRepository.swift | 1 + .../Agent/MobileAssistantModel.swift | 45 +- TFI/Features/AI/MobileAssistantComposer.swift | 4 +- .../AI/MobileAssistantConversation.swift | 2 +- TFITests/MobileAssistantTests.swift | 27 + TFM/App/AppModel.swift | 59 +- .../AI/ConversationTranscriptView.swift | 7 +- TFM/Features/AI/ConversationViews.swift | 6 +- TFM/Features/Assistant/AssistantView.swift | 16 +- TFM/Features/Workspaces/WindowModel.swift | 5 + 15 files changed, 1457 insertions(+), 238 deletions(-) create mode 100644 FrameworkTests/AssistantExecutionControllerTests.swift diff --git a/FrameworkTests/AssistantExecutionControllerTests.swift b/FrameworkTests/AssistantExecutionControllerTests.swift new file mode 100644 index 00000000..48e42649 --- /dev/null +++ b/FrameworkTests/AssistantExecutionControllerTests.swift @@ -0,0 +1,682 @@ +import AgentKit +import Foundation +import TermindDomain +import Testing + +@testable import TermindApplication + +@Suite @MainActor +struct AssistantExecutionControllerTests { + @Test + func captureFreezesTurnContextAndConfigurationAcrossMemoryPreparation() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let oldSkill = AgentSkill(name: "old-skill", summary: "Old procedure", body: "Do the old procedure") + let skills = SkillLibraryModel(skills: [oldSkill]) + let (ai, provider) = configuredAI(secret: "captured-secret") + let registryCapture = RegistryCapture() + let controller = makeController( + ai: ai, skills: skills, repository: repository, memory: memory, + makeRegistry: { _, model, provider, catalog, _ in + registryCapture.record(model: model, provider: provider, catalog: catalog) + return AgentToolRegistry([]) + }) + let conversation = ConversationModel() + conversation.permissionMode = .askForApproval + conversation.reasoningEffort = .low + conversation.isPlanning = true + conversation.composer.draft = "inspect" + + #expect( + controller.sendAgentDraft( + conversation, + context: SessionContext(hostName: "captured-host", workingDirectory: "/captured"), + terminalSuggestionsAvailable: true)) + await memoryStore.waitUntilSnapshotEntered() + + conversation.permissionMode = .fullAccess + conversation.reasoningEffort = .high + conversation.isPlanning = false + skills.setEnabled(false, for: oldSkill.id) + skills.upsert(AgentSkill(name: "new-skill", summary: "New procedure", body: "Do the new procedure")) + ai.setSecret(nil, for: provider.credentialRef) + ai.setWebSearch(.init(isEnabled: false, prefersBuiltIn: false)) + + let captured = try #require(conversation.transcript.last?.contextSnapshot) + #expect(captured.sessionContext?.contains("captured-host") == true) + #expect(captured.planContract == TermindAgentPolicy.planContract) + #expect(captured.toolAvailability?["terminal_suggest"] == true) + #expect(captured.skillCatalog?.contains("old-skill") == true) + #expect(captured.skillCatalog?.contains("new-skill") != true) + #expect(registryCapture.modelID == provider.models[0].id) + #expect(registryCapture.providerID == provider.id) + #expect(registryCapture.catalog?.catalogBlock.contains("old-skill") == true) + + conversation.stop() + await memoryStore.releaseSnapshot() + try await waitUntil { !conversation.isPreparingAgentRun } + #expect(conversation.runState == .interrupted) + let interrupted = try #require(await repository.snapshots.last) + #expect(interrupted.state == .interrupted) + #expect(interrupted.permissionMode == .askForApproval) + } + + @Test + func credentialAddedAfterCaptureDoesNotReviveTheTurn() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let (ai, provider) = configuredAI(secret: nil) + let controller = makeController(ai: ai, repository: repository, memory: memory) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await memoryStore.waitUntilSnapshotEntered() + ai.setSecret("late-secret", for: provider.credentialRef) + await memoryStore.releaseSnapshot() + + try await waitUntil { !conversation.isPreparingAgentRun } + #expect(conversation.runState == .failed) + #expect(await repository.snapshots.last?.state == .failed) + } + + @Test + func immediateStopInvalidatesPreparationBeforeTheMemoryBarrierIsReleased() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let (ai, _) = configuredAI(secret: nil) + let controller = makeController(ai: ai, repository: repository, memory: memory) + let conversation = ConversationModel() + conversation.permissionMode = .askForApproval + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await memoryStore.waitUntilSnapshotEntered() + conversation.permissionMode = .fullAccess + conversation.stop() + conversation.stop() + + await memoryStore.releaseSnapshot() + try await waitUntil { !conversation.isPreparingAgentRun } + #expect(conversation.runState == .interrupted) + #expect(!conversation.isStreaming) + let snapshots = await repository.snapshots + #expect(snapshots.count == 1) + #expect(snapshots[0].state == .interrupted) + #expect(snapshots[0].permissionMode == .askForApproval) + } + + @Test + func globalCancellationInterruptsAPreparedEditThatHasNoPreparationTask() async throws { + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + + #expect(controller.prepareAgentEdit(conversation, messageID: message.id, text: "after") != nil) + #expect(conversation.isPreparingAgentRun) + await controller.cancelPendingRuns().value + + #expect(!conversation.isPreparingAgentRun) + #expect(conversation.transcript == [message]) + #expect(conversation.runState == .interrupted) + #expect(await repository.snapshots.last?.state == .interrupted) + } + + @Test + func cancelledPreparedEditCannotEnterHistoryMutation() async throws { + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + let prepared = try #require( + controller.prepareAgentEdit(conversation, messageID: message.id, text: "after")) + + await controller.cancelPendingRuns().value + + #expect( + try await !controller.performAgentEdit( + prepared, in: conversation, operations: operations)) + #expect(await repository.truncationReplacement == nil) + #expect(conversation.transcript == [message]) + } + + @Test + func supersededPreparedEditCannotEnterHistoryMutation() async throws { + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + let stale = try #require( + controller.prepareAgentEdit(conversation, messageID: message.id, text: "stale")) + #expect(controller.prepareAgentEdit(conversation, messageID: message.id, text: "current") != nil) + + #expect( + try await !controller.performAgentEdit( + stale, in: conversation, operations: operations)) + #expect(await repository.truncationReplacement == nil) + #expect(conversation.transcript == [message]) + + await controller.cancelPendingRuns().value + } + + @Test + func cancellationDuringEditJoinsHistoryWorkBeforeRecordingInterruption() async throws { + let repository = RecordingAgentRunRepository(blocksTruncation: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + let prepared = try #require( + controller.prepareAgentEdit(conversation, messageID: message.id, text: "after")) + let edit = Task { + try await controller.performAgentEdit( + prepared, in: conversation, operations: operations) + } + + await repository.waitUntilTruncationEntered() + let cancellation = controller.cancelPendingRuns() + await Task.yield() + + #expect(await repository.snapshots.isEmpty) + #expect(conversation.isPreparingAgentRun) + await repository.releaseTruncation() + + #expect(try await !edit.value) + await cancellation.value + #expect(await repository.truncationReplacement == nil) + #expect(await repository.snapshots.map(\.state) == [.interrupted]) + #expect(conversation.transcript == [message]) + #expect(conversation.runState == .interrupted) + #expect(!conversation.isPreparingAgentRun) + #expect(!conversation.isStreaming) + } + + @Test + func supersededPreparationIgnoresAnOlderCancellationCompletion() async throws { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let controller = makeController(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + + #expect(controller.prepareAgentEdit(conversation, messageID: message.id, text: "first") != nil) + let oldCancellation = controller.cancelPendingRuns() + await repository.waitUntilSaveEntered() + #expect(controller.prepareAgentEdit(conversation, messageID: message.id, text: "second") != nil) + + await repository.releaseSave() + await oldCancellation.value + + #expect(conversation.isPreparingAgentRun) + #expect(conversation.runState != .interrupted) + + await controller.cancelPendingRuns().value + #expect(!conversation.isPreparingAgentRun) + } + + @Test + func selectingAnotherConversationDoesNotCancelPreparation() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository, memory: memory) + let preparing = ConversationModel() + preparing.composer.draft = "inspect" + let visible = ConversationModel() + let library = AssistantLibraryModel( + store: AssistantConversationStore(conversations: [preparing, visible])) + + #expect(controller.sendAgentDraft(preparing)) + await memoryStore.waitUntilSnapshotEntered() + library.select(visible.id) + #expect(library.selectedConversation === visible) + #expect(preparing.isPreparingAgentRun) + + await memoryStore.releaseSnapshot() + try await waitUntil { !preparing.isPreparingAgentRun } + #expect(preparing.runState == .failed) + #expect(await repository.snapshots.last?.state == .failed) + } + + @Test + func firstSendGuardsDoNotPromoteOrConsumeTheDraftConversation() { + let library = AssistantLibraryModel() + let unauthorized = makeController(authorize: { false }) + library.draftConversation.composer.draft = "keep" + #expect(unauthorized.sendDraftConversation(in: library) == nil) + #expect(library.conversations.isEmpty) + #expect(library.draftConversation.composer.draft == "keep") + + let authorized = makeController() + library.draftConversation.composer.draft = " " + #expect(authorized.sendDraftConversation(in: library) == nil) + #expect(library.conversations.isEmpty) + + library.draftConversation.composer.draft = "wait for attachment" + let pending = library.draftConversation.beginAttachmentImport(label: "notes.txt") + #expect(pending != nil) + #expect(authorized.sendDraftConversation(in: library) == nil) + #expect(library.conversations.isEmpty) + #expect(library.draftConversation.composer.draft == "wait for attachment") + #expect(library.draftConversation.composer.pendingAttachments.count == 1) + } + + @Test + func firstSendCapturesAttachmentsAndArmedSkillExactlyOnce() throws { + let library = AssistantLibraryModel() + let controller = makeController() + let conversation = library.draftConversation + conversation.composer.draft = "inspect" + conversation.composer.armedSkillName = "disk-triage" + conversation.attach(ContextChip(kind: .file, label: "notes.txt", content: "captured body")) + + #expect(controller.sendDraftConversation(in: library) === conversation) + #expect(library.conversations.map(\.id) == [conversation.id]) + #expect(library.draftConversation !== conversation) + #expect(conversation.composer.attachments.isEmpty) + #expect(conversation.composer.armedSkillName == nil) + let turn = try #require(conversation.transcript.first) + #expect(turn.authoredText == "inspect") + #expect(turn.contextAttachments.map(\.label) == ["notes.txt"]) + #expect(turn.text.components(separatedBy: "").count == 2) + #expect(turn.text.components(separatedBy: "captured body").count == 2) + } + + @Test + func textSendCapturesComposerPayloadsInsteadOfConsumingThemInvisibly() throws { + let controller = makeController() + let conversation = ConversationModel() + let image = AgentImageAttachment( + label: "graph.png", mimeType: "image/png", data: Data([7, 8, 9]), + pixelWidth: 20, pixelHeight: 20) + conversation.composer.draft = "keep this draft" + conversation.composer.armedSkillName = "disk-triage" + conversation.attach(ContextChip(kind: .file, label: "notes.txt", content: "captured body")) + conversation.attach(ContextChip(kind: .image, label: image.label, image: image)) + + #expect(controller.sendAgentText("Implement the fix.", in: conversation)) + + let turn = try #require(conversation.transcript.first) + #expect(turn.authoredText == "Implement the fix.") + #expect(turn.text.contains("captured body")) + #expect(turn.text.contains("")) + #expect(turn.images == [image]) + #expect(turn.contextAttachments.map(\.label) == ["notes.txt"]) + #expect(conversation.composer.attachments.isEmpty) + #expect(conversation.composer.armedSkillName == nil) + #expect(conversation.composer.draft == "keep this draft") + } + + @Test + func planImplementationAtomicallyCapturesActingModeAndComposerPayloads() throws { + let controller = makeController() + let conversation = ConversationModel() + let plan = AgentPlan( + title: "Implementation plan", body: "Apply the requested change.", + scratchPath: "/tmp/plan.md") + conversation.isPlanning = true + conversation.handle(.planPresented(plan)) + conversation.composer.armedSkillName = "disk-triage" + conversation.attach(ContextChip(kind: .file, label: "notes.txt", content: "captured body")) + let card = try #require(conversation.plans.first) + + #expect(controller.sendPlanImplementation(card, in: conversation)) + + #expect(conversation.plans.first?.outcome == .implemented) + #expect(!conversation.isPlanning) + let turn = try #require(conversation.transcript.last) + #expect(turn.authoredText == ConversationModel.implementationPrompt(for: plan)) + #expect(turn.contextSnapshot?.planContract == nil) + #expect(turn.text.contains("captured body")) + #expect(turn.text.contains("")) + #expect(conversation.composer.attachments.isEmpty) + #expect(conversation.composer.armedSkillName == nil) + } + + @Test + func refusedPlanImplementationPreservesPlanModeAndComposerPayloads() throws { + let controller = makeController(authorize: { false }) + let conversation = ConversationModel() + let plan = AgentPlan( + title: "Implementation plan", body: "Apply the requested change.", + scratchPath: "/tmp/plan.md") + conversation.isPlanning = true + conversation.handle(.planPresented(plan)) + conversation.composer.armedSkillName = "disk-triage" + conversation.attach(ContextChip(kind: .file, label: "notes.txt", content: "keep me")) + let card = try #require(conversation.plans.first) + + #expect(!controller.sendPlanImplementation(card, in: conversation)) + + #expect(conversation.plans.first?.outcome == .pending) + #expect(conversation.isPlanning) + #expect(conversation.transcript.isEmpty) + #expect(conversation.composer.attachments.map(\.label) == ["notes.txt"]) + #expect(conversation.composer.armedSkillName == "disk-triage") + } + + @Test + func manualCompactionIsRefusedWhileRunPreparationIsActive() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let controller = makeController(memory: memory) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await memoryStore.waitUntilSnapshotEntered() + #expect(conversation.isPreparingAgentRun) + + let didCompact = await controller.compact(conversation) + #expect(!didCompact) + #expect(conversation.compactionActivity == nil) + #expect(conversation.isPreparingAgentRun) + + conversation.stop() + await memoryStore.releaseSnapshot() + try await waitUntil { !conversation.isPreparingAgentRun } + } + + @Test + func memoryDisabledDuringPreparationRevokesTheCapturedSession() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository, memory: memory) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await memoryStore.waitUntilSnapshotEntered() + await memory.setEnabled(false) + await memoryStore.releaseSnapshot() + + try await waitUntil { !conversation.isPreparingAgentRun } + #expect(!memory.isEnabled) + #expect(conversation.runState == .failed) + #expect(await memoryStore.snapshotCallCount == 1) + } + + @Test + func memoryEnabledAfterCaptureIsNotAddedToTheTurn() async { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + let controller = makeController(memory: memory) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + #expect(conversation.transcript.last?.contextSnapshot?.memorySessionID == nil) + await memory.setEnabled(true) + #expect(await memoryStore.snapshotCallCount == 0) + } + + @Test + func legacyExplicitStartUsesANeutralUnknownSnapshot() async throws { + let repository = RecordingAgentRunRepository() + let (ai, _) = configuredAI(secret: "captured-secret") + let controller = makeController(ai: ai, repository: repository) + let legacy = AgentTranscriptMessage(role: .user, text: "legacy", authoredText: "legacy") + let conversation = ConversationModel( + messages: [ChatMessage(id: legacy.id, role: .user, text: "legacy")], transcript: [legacy]) + conversation.isPlanning = true + + controller.startAgentRun(conversation) + + let snapshot = try #require(conversation.activeContextSnapshot) + #expect(snapshot == AgentTurnContextSnapshot()) + conversation.stop() + #expect(!conversation.isStreaming) + } + + @Test + func editFailurePreservesHistoryButUsesTheSynchronouslyCapturedReplacement() async throws { + let repository = RecordingAgentRunRepository(failTruncation: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + conversation.isPlanning = true + let prepared = try #require( + controller.prepareAgentEdit( + conversation, messageID: message.id, text: "after", + context: SessionContext(hostName: "captured-host"))) + conversation.isPlanning = false + + await #expect(throws: CocoaError.self) { + try await controller.performAgentEdit(prepared, in: conversation, operations: operations) + } + #expect(conversation.transcript == [message]) + #expect(!conversation.isPreparingAgentRun) + let replacement = try #require(await repository.truncationReplacement) + #expect(replacement.authoredText == "after") + #expect(replacement.contextSnapshot?.sessionContext?.contains("captured-host") == true) + #expect(replacement.contextSnapshot?.planContract == TermindAgentPolicy.planContract) + } + + @Test + func successfulEditStartsFromTheCapturedReplacementSnapshot() async throws { + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + conversation.isPlanning = true + let prepared = try #require( + controller.prepareAgentEdit( + conversation, messageID: message.id, text: "after", + context: SessionContext(hostName: "captured-host"))) + conversation.isPlanning = false + + #expect(try await controller.performAgentEdit(prepared, in: conversation, operations: operations)) + let edited = try #require(conversation.transcript.first) + #expect(edited.authoredText == "after") + #expect(edited.contextSnapshot?.sessionContext?.contains("captured-host") == true) + #expect(edited.contextSnapshot?.planContract == TermindAgentPolicy.planContract) + #expect(conversation.runState == .failed) + } + + private func makeController( + ai: AIConfigurationModel = AIConfigurationModel(), + skills: SkillLibraryModel = SkillLibraryModel(), + repository: any AgentRunPersisting = RecordingAgentRunRepository(), + memory: MemoryModel? = nil, + authorize: @escaping @MainActor () -> Bool = { true }, + makeRegistry: @escaping AssistantExecutionController.RegistryFactory = { _, _, _, _, _ in + AgentToolRegistry([]) + } + ) -> AssistantExecutionController { + AssistantExecutionController( + ai: ai, skills: skills, repository: repository, memory: memory, + authorize: authorize, makeRegistry: makeRegistry, + title: { _ in "Title" }) + } + + private func configuredAI(secret: String?) -> (AIConfigurationModel, ModelProvider) { + let model = AIModel(id: "captured-model", abilities: [.toolCall]) + let provider = ModelProvider( + name: "Captured", inferenceURL: "https://example.invalid/v1/responses", + credentialRef: "captured-key", models: [model]) + let ai = AIConfigurationModel(modelProviders: [provider], persistsSecrets: false) + ai.setSecret(secret, for: provider.credentialRef) + return (ai, provider) + } + + private func waitUntil( + timeout: Duration = .seconds(2), _ condition: @escaping @MainActor () -> Bool + ) async throws { + let clock = ContinuousClock() + let deadline = clock.now.advanced(by: timeout) + while !condition() { + guard clock.now < deadline else { throw TestTimeoutError() } + try await Task.sleep(for: .milliseconds(1)) + } + } +} + +private struct TestTimeoutError: Error {} + +@MainActor +private final class RegistryCapture { + var modelID: String? + var providerID: UUID? + var catalog: AgentSkillCatalog? + + func record(model: AIModel, provider: ModelProvider, catalog: AgentSkillCatalog) { + modelID = model.id + providerID = provider.id + self.catalog = catalog + } +} + +private actor SnapshotBarrierMemoryStore: MemoryPersisting { + private var entered = false + private var released = false + private var entryWaiters: [CheckedContinuation] = [] + private var releaseWaiters: [CheckedContinuation] = [] + private(set) var snapshotCallCount = 0 + + func waitUntilSnapshotEntered() async { + if entered { return } + await withCheckedContinuation { entryWaiters.append($0) } + } + + func releaseSnapshot() { + released = true + let waiters = releaseWaiters + releaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func snapshot(for conversationID: UUID) async throws -> String { + snapshotCallCount += 1 + entered = true + let waiters = entryWaiters + entryWaiters.removeAll() + for waiter in waiters { waiter.resume() } + if !released { + await withCheckedContinuation { releaseWaiters.append($0) } + } + return "captured memory" + } + + func memoryState() -> AgentMemoryState { AgentMemoryState() } + func applyMemory(_ operations: [AgentMemoryOperation]) -> AgentMemoryState { AgentMemoryState() } + func replaceHistory(_ conversations: [StoredAgentConversation]) {} + func rebuildHistoryIndex() {} + func searchSessions(_ request: AgentSessionSearchRequest) -> AgentJSONValue { .array([]) } + func lastReviewedMessage(_ conversationID: UUID) -> UUID? { nil } + func markReviewed(_ messageID: UUID, conversationID: UUID) {} + func applyReview( + _ operations: [AgentMemoryOperation], expected: AgentMemoryState, + messageID: UUID, conversationID: UUID + ) -> AgentMemoryState? { expected } + func replaceTarget(_ target: AgentMemoryTarget, text: String) -> AgentMemoryState { AgentMemoryState() } + func close() {} + func destroy() {} +} + +private actor RecordingAgentRunRepository: AgentRunPersisting { + private(set) var snapshots: [AgentRunSnapshot] = [] + private(set) var truncationReplacement: AgentTranscriptMessage? + private let failTruncation: Bool + private let blocksFirstSave: Bool + private let blocksTruncation: Bool + private var saveEntered = false + private var saveReleased = false + private var saveEntryWaiters: [CheckedContinuation] = [] + private var saveReleaseWaiters: [CheckedContinuation] = [] + private var truncationEntered = false + private var truncationReleased = false + private var truncationEntryWaiters: [CheckedContinuation] = [] + private var truncationReleaseWaiters: [CheckedContinuation] = [] + + init( + failTruncation: Bool = false, blocksFirstSave: Bool = false, + blocksTruncation: Bool = false + ) { + self.failTruncation = failTruncation + self.blocksFirstSave = blocksFirstSave + self.blocksTruncation = blocksTruncation + } + + func save(_ snapshot: AgentRunSnapshot) async { + snapshots.append(snapshot) + guard blocksFirstSave, snapshots.count == 1 else { return } + saveEntered = true + let entryWaiters = saveEntryWaiters + saveEntryWaiters.removeAll() + for waiter in entryWaiters { waiter.resume() } + if !saveReleased { + await withCheckedContinuation { saveReleaseWaiters.append($0) } + } + } + + func waitUntilSaveEntered() async { + if saveEntered { return } + await withCheckedContinuation { saveEntryWaiters.append($0) } + } + + func releaseSave() { + saveReleased = true + let waiters = saveReleaseWaiters + saveReleaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func waitUntilTruncationEntered() async { + if truncationEntered { return } + await withCheckedContinuation { truncationEntryWaiters.append($0) } + } + + func releaseTruncation() { + truncationReleased = true + let waiters = truncationReleaseWaiters + truncationReleaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func record(_ event: AgentEvent, runID: UUID) {} + func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} + func loadConversations() -> [StoredAgentConversation] { [] } + func deleteConversation(_ id: UUID) {} + func setTitle(_ title: String, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} + func truncateConversation( + _ id: UUID, fromUserMessageID messageID: UUID, + replacingWith message: AgentTranscriptMessage + ) async throws { + if blocksTruncation { + truncationEntered = true + let waiters = truncationEntryWaiters + truncationEntryWaiters.removeAll() + for waiter in waiters { waiter.resume() } + if !truncationReleased { + await withCheckedContinuation { truncationReleaseWaiters.append($0) } + } + } + try Task.checkCancellation() + truncationReplacement = message + if failTruncation { throw CocoaError(.fileWriteNoPermission) } + } + func createConversation(_ conversation: StoredAgentConversation) {} +} diff --git a/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift b/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift index b30c86b7..39d82313 100644 --- a/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift +++ b/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift @@ -10,6 +10,7 @@ public final class AssistantConversationOperations { public init(repository: any AgentRunPersisting) { self.repository = repository } public func setArchived(_ value: Bool, conversation: ConversationModel) async throws { + await conversation.cancelPendingAgentPreparationAndWait() guard !conversation.isStreaming, !conversation.isCompacting, pending.insert(conversation.id).inserted else { return } @@ -60,6 +61,7 @@ public final class AssistantConversationOperations { @discardableResult public func delete(_ conversation: ConversationModel) async throws -> (deleted: Bool, cleanupError: String?) { + await conversation.cancelPendingAgentPreparationAndWait() guard !conversation.isStreaming, !conversation.isCompacting, pending.insert(conversation.id).inserted else { return (false, nil) } @@ -119,15 +121,25 @@ public final class AssistantConversationOperations { ) async throws -> ConversationModel .ComposedUserTurn? { - guard var replacement = conversation.editedUserMessage(messageID, authoredText: text), + guard var replacement = conversation.editedUserMessage(messageID, authoredText: text) else { return nil } + replacement.contextSnapshot = contextSnapshot + return try await edit(conversation, messageID: messageID, replacement: replacement) + } + + public func edit( + _ conversation: ConversationModel, messageID: UUID, + replacement: AgentTranscriptMessage + ) async throws -> ConversationModel.ComposedUserTurn? { + try Task.checkCancellation() + guard pending.insert(conversation.id).inserted else { return nil } - replacement.contextSnapshot = contextSnapshot conversation.isHistoryChanging = true defer { pending.remove(conversation.id) conversation.isHistoryChanging = false } + try Task.checkCancellation() try await repository.truncateConversation( conversation.id, fromUserMessageID: messageID, replacingWith: replacement) conversation.isHistoryChanging = false diff --git a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift index 6b2505c0..94d38bcb 100644 --- a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift +++ b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift @@ -12,7 +12,6 @@ public final class AssistantExecutionController { private let ai: AIConfigurationModel private let skills: SkillLibraryModel private let memory: MemoryModel? - private var preparingMemory = Set() private let agentRunRepository: any AgentRunPersisting private let authorize: @MainActor () -> Bool private let makeRegistry: RegistryFactory @@ -21,10 +20,68 @@ public final class AssistantExecutionController { private let agentCompaction = AgentCompactionService() private let guardianSessions = GuardianSessionStore() private var titledConversationIDs = Set() - private var preparationGeneration: UInt64 = 0 - private var preparing = Set() + private var preparations: [UUID: PreparationState] = [:] - public func cancelPendingRuns() { preparationGeneration &+= 1 } + fileprivate struct GuardianCapture { + var provider: ModelProvider + var model: AIModel + var secret: String + var reasoning: ReasoningEffort + } + + fileprivate struct RunCapture { + var id: UUID + var prompt: ConversationModel.ComposedUserTurn + var contextSnapshot: AgentTurnContextSnapshot + var permissionMode: AgentPermissionMode + var reasoning: ReasoningEffort + var chat: (provider: ModelProvider, model: AIModel)? + var secret: String? + var nativeSearch: Bool + var skillCatalog: AgentSkillCatalog + var skillInventory: [AgentSkill] + var channel: AgentEventChannel? + var baseRegistry: AgentToolRegistry? + var guardian: GuardianCapture? + var memorySessionID: UUID? + var shouldCompact: Bool + var compactionModel: (any AgentModelCompleting)? + var memoryReviewModel: (any AgentModelCompleting)? + } + + private struct PreparationState { + var id: UUID + var conversation: ConversationModel + var permissionMode: AgentPermissionMode + var task: Task? + var historyTask: Task? + var cancellationTask: Task? + var recordsInterruption = false + var cancellationRequested = false + } + + public struct PreparedAgentEdit { + fileprivate var capture: RunCapture + fileprivate var replacement: AgentTranscriptMessage + fileprivate var messageID: UUID + } + + @discardableResult + public func cancelPendingRuns() -> Task { + var cancellationTasks: [Task] = [] + for conversationID in Array(preparations.keys) { + guard let state = preparations[conversationID] else { continue } + cancellationTasks.append( + requestCancellation( + for: state.conversation, expectedID: state.id, + recordsInterruption: true)) + } + return Task { @MainActor in + for task in cancellationTasks { + await task.value + } + } + } public init( ai: AIConfigurationModel, skills: SkillLibraryModel, repository: any AgentRunPersisting, @@ -43,56 +100,207 @@ public final class AssistantExecutionController { self.logger = logger } + @discardableResult public func sendAgentDraft( _ conversation: ConversationModel, context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false - ) { - guard authorize() else { return } - let draft = conversation.composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) - guard conversation.canSendDraft, !conversation.isArchived, !conversation.isStreaming, - !conversation.isCompacting, !conversation.isHistoryChanging, - conversation.composer.pendingAttachments.isEmpty, - !preparingMemory.contains(conversation.id), preparing.insert(conversation.id).inserted - else { return } - let generation = preparationGeneration - // The *returned* text, not `draft`: attachments are folded in there, and - // running on the pre-fold string would show the model a turn that does - // not match the one the conversation recorded. - let prompt = conversation.appendUserTurnContent( - draft, - contextSnapshot: contextSnapshot( - for: conversation, context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable)) - conversation.composer.draft = "" - // Compaction happens *after* the turn is recorded and before the run, - // so the message the user just typed is inside the kept window rather - // than being the first thing summarized away. When nothing needs - // compacting this is a synchronous check that starts the run in the same - // tick, which is what keeps the composer feeling immediate. - Task { [weak self, weak conversation] in - guard let self, let conversation else { return } - defer { preparing.remove(conversation.id) } - await compactIfNeeded(conversation) - guard generation == preparationGeneration, !Task.isCancelled else { - do { - try await agentRunRepository.save( - AgentRunSnapshot( - conversationID: conversation.id, state: .interrupted, - permissionMode: ai.resolve(conversation: conversation.permissionMode), - messages: conversation.transcript, finishedAt: .now)) - conversation.handle(.runState(.interrupted)) - } catch { - conversation.messages.append(ChatMessage(role: .assistant, text: error.localizedDescription)) - } - return - } - startAgentRun( - conversation, promptID: prompt.id, prompt: prompt.text, - authoredPrompt: prompt.authoredText, promptImages: prompt.images, - promptContextAttachments: prompt.contextAttachments, context: context, - terminalSuggestionsAvailable: terminalSuggestionsAvailable - ) + ) -> Bool { + guard + let capture = captureDraft( + conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + else { return false } + startPreparation(capture, for: conversation, compactBeforeRun: true) + return true + } + + @discardableResult + public func sendNewConversation( + _ conversation: ConversationModel, in library: AssistantLibraryModel, + context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> ConversationModel? { + guard library.canPromote(conversation), + let capture = captureDraft( + conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + else { return nil } + guard library.promoteCapturedConversation(conversation) else { + cancelCapturedTurn(capture, in: conversation) + return nil } + startPreparation(capture, for: conversation, compactBeforeRun: true) + return conversation + } + + @discardableResult + public func sendDraftConversation( + in library: AssistantLibraryModel, + context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> ConversationModel? { + let conversation = library.draftConversation + guard library.canPromote(conversation), + let capture = captureDraft( + conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + else { return nil } + guard library.promoteCapturedDraftConversation(conversation) else { + cancelCapturedTurn(capture, in: conversation) + return nil + } + startPreparation(capture, for: conversation, compactBeforeRun: true) + return conversation + } + + @discardableResult + public func sendAgentText( + _ text: String, in conversation: ConversationModel, + context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> Bool { + guard authorize(), canSubmit(to: conversation), + !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + conversation.composer.pendingAttachments.isEmpty + else { return false } + let prompt = conversation.composeUserTurn(text) + let snapshot = contextSnapshot( + for: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + guard + let capture = captureRun( + conversation, prompt: prompt, contextSnapshot: snapshot, + beginPreparation: true) + else { return false } + let submittedPrompt = conversation.appendUserTurnContent( + capture.prompt, contextSnapshot: capture.contextSnapshot) + var submitted = capture + submitted.prompt = submittedPrompt + startPreparation(submitted, for: conversation, compactBeforeRun: true) + return true + } + + @discardableResult + public func sendPlanImplementation( + _ card: AgentPlanCard, in conversation: ConversationModel, + context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> Bool { + guard authorize(), canSubmit(to: conversation), + conversation.composer.pendingAttachments.isEmpty, + let pending = conversation.plans.first(where: { + $0.id == card.id && $0.outcome == .pending + }) + else { return false } + let prompt = conversation.composeUserTurn( + ConversationModel.implementationPrompt(for: pending.plan)) + var snapshot = contextSnapshot( + for: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + snapshot.planContract = nil + guard + let capture = captureRun( + conversation, prompt: prompt, contextSnapshot: snapshot, + beginPreparation: true) + else { return false } + let submittedPrompt = conversation.appendUserTurnContent( + capture.prompt, contextSnapshot: capture.contextSnapshot) + conversation.resolvePlan(card.id, as: .implemented) + var submitted = capture + submitted.prompt = submittedPrompt + startPreparation(submitted, for: conversation, compactBeforeRun: true) + return true + } + + public func prepareAgentEdit( + _ conversation: ConversationModel, messageID: UUID, text: String, + context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> PreparedAgentEdit? { + guard authorize(), !conversation.isStreaming, !conversation.isArchived, + !conversation.isCompacting, !conversation.isHistoryChanging, + var replacement = conversation.editedUserMessage(messageID, authoredText: text) + else { return nil } + let snapshot = contextSnapshot( + for: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + replacement.contextSnapshot = snapshot + let prompt = ConversationModel.ComposedUserTurn( + id: replacement.id, text: replacement.text, + authoredText: replacement.authoredText ?? text, images: replacement.images, + contextAttachments: replacement.contextAttachments) + guard + let capture = captureRun( + conversation, prompt: prompt, contextSnapshot: snapshot, + beginPreparation: true, supersedePreparation: true) + else { return nil } + return PreparedAgentEdit(capture: capture, replacement: replacement, messageID: messageID) + } + + public func performAgentEdit( + _ prepared: PreparedAgentEdit, in conversation: ConversationModel, + operations: AssistantConversationOperations + ) async throws -> Bool { + let capture = prepared.capture + guard isCurrent(capture, for: conversation) else { return false } + let historyTask = Task { @MainActor in + try Task.checkCancellation() + return try await operations.edit( + conversation, messageID: prepared.messageID, replacement: prepared.replacement) + } + guard var state = preparations[conversation.id], state.id == capture.id, + !state.cancellationRequested + else { + historyTask.cancel() + return false + } + state.historyTask = historyTask + preparations[conversation.id] = state + do { + guard let edited = try await historyTask.value else { + guard isCurrent(capture, for: conversation) else { return false } + retirePreparation(capture.id, for: conversation) + return false + } + guard isCurrent(capture, for: conversation) else { return false } + if var state = preparations[conversation.id], state.id == capture.id { + state.historyTask = nil + preparations[conversation.id] = state + } + var submitted = capture + submitted.prompt = edited + startPreparation(submitted, for: conversation, compactBeforeRun: false) + return true + } catch is CancellationError { + return false + } catch { + guard isCurrent(capture, for: conversation) else { return false } + retirePreparation(capture.id, for: conversation) + throw error + } + } + + private func captureDraft( + _ conversation: ConversationModel, context: SessionContext, + terminalSuggestionsAvailable: Bool + ) -> RunCapture? { + guard authorize(), canSubmit(to: conversation), conversation.canSendDraft, + conversation.composer.pendingAttachments.isEmpty + else { return nil } + let draft = conversation.composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) + let snapshot = contextSnapshot( + for: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + let preview = conversation.composeUserTurn(draft) + guard + var capture = captureRun( + conversation, prompt: preview, contextSnapshot: snapshot, + beginPreparation: true) + else { return nil } + capture.prompt = conversation.appendUserTurnContent(preview, contextSnapshot: snapshot) + conversation.composer.draft = "" + return capture } public func startAgentRun( @@ -105,64 +313,39 @@ public final class AssistantExecutionController { context: SessionContext? = nil, terminalSuggestionsAvailable: Bool = false ) { - guard authorize(), !conversation.isStreaming, !conversation.isArchived else { return } - guard preparingMemory.insert(conversation.id).inserted else { return } - let generation = preparationGeneration - if let memory, memory.isEnabled { - Task { [weak self, weak conversation] in - guard let self, let conversation else { return } - defer { preparingMemory.remove(conversation.id) } - let id = - conversation.transcript.first(where: { $0.contextSnapshot?.memorySessionID != nil })? - .contextSnapshot?.memorySessionID ?? conversation.id - let session = await memory.session(id) - guard generation == preparationGeneration, !Task.isCancelled else { return } - performStartAgentRun( - conversation, promptID: promptID, prompt: prompt, authoredPrompt: authoredPrompt, - promptImages: promptImages, promptContextAttachments: promptContextAttachments, - context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable, memorySession: session - ) - } - } else { - preparingMemory.remove(conversation.id) - performStartAgentRun( - conversation, promptID: promptID, prompt: prompt, authoredPrompt: authoredPrompt, - promptImages: promptImages, promptContextAttachments: promptContextAttachments, - context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) - } + guard authorize(), canSubmit(to: conversation) else { return } + let lastUserTurn = conversation.transcript.last(where: { $0.role == .user }) + let resolvedPrompt = ConversationModel.ComposedUserTurn( + id: promptID ?? lastUserTurn?.id ?? UUID(), + text: prompt ?? lastUserTurn?.text ?? "", + authoredText: authoredPrompt ?? lastUserTurn?.authoredText ?? "", + images: promptImages ?? lastUserTurn?.images ?? [], + contextAttachments: promptContextAttachments ?? lastUserTurn?.contextAttachments ?? []) + guard + !resolvedPrompt.text.isEmpty || !resolvedPrompt.images.isEmpty + || !resolvedPrompt.contextAttachments.isEmpty + else { return } + // A legacy absent snapshot is unknown. Do not manufacture historical + // session, skill, plan, memory, or surface facts from today's UI. + let snapshot = lastUserTurn?.contextSnapshot ?? AgentTurnContextSnapshot() + guard + let capture = captureRun( + conversation, prompt: resolvedPrompt, contextSnapshot: snapshot, + beginPreparation: true) + else { return } + startPreparation(capture, for: conversation, compactBeforeRun: false) } private func performStartAgentRun( - _ conversation: ConversationModel, - promptID: UUID? = nil, - prompt: String? = nil, - authoredPrompt: String? = nil, - promptImages: [AgentImageAttachment]? = nil, - promptContextAttachments: [AgentContextAttachment]? = nil, - context: SessionContext? = nil, - terminalSuggestionsAvailable: Bool = false, + _ conversation: ConversationModel, capture: RunCapture, memorySession: (context: AgentMemoryContext, access: any AgentMemoryAccessing)? = nil ) { - guard authorize() else { return } - guard !conversation.isStreaming, !conversation.isArchived, !conversation.isHistoryChanging else { return } - let lastUserTurn = conversation.transcript.last(where: { $0.role == .user }) - let userPrompt = - prompt - ?? lastUserTurn?.text - ?? "" - let userImages = - promptImages - ?? lastUserTurn?.images - ?? [] - let userContextAttachments = - promptContextAttachments - ?? lastUserTurn?.contextAttachments - ?? [] - guard !userPrompt.isEmpty || !userImages.isEmpty || !userContextAttachments.isEmpty + guard isCurrent(capture, for: conversation), !conversation.isStreaming, + !conversation.isArchived, !conversation.isHistoryChanging else { return } let operationID = UUID().uuidString.lowercased() let conversationID = conversation.id.uuidString.lowercased() - guard let chat = ai.resolve(conversation: conversation.model) else { + guard let chat = capture.chat else { logger.error( "agent.run.failed", metadata: [ @@ -171,16 +354,15 @@ public final class AssistantExecutionController { ]) recordFailedRun( conversation, - promptID: promptID ?? lastUserTurn?.id ?? UUID(), - prompt: userPrompt, - authoredPrompt: authoredPrompt ?? lastUserTurn?.authoredText, - images: userImages, - contextAttachments: userContextAttachments, + promptID: capture.prompt.id, prompt: capture.prompt.text, + authoredPrompt: capture.prompt.authoredText, images: capture.prompt.images, + contextAttachments: capture.prompt.contextAttachments, + permissionMode: capture.permissionMode, reason: String(localized: "No Chat model is configured.") ) return } - guard let secret = ai.modelSecret(for: chat.provider) else { + guard let secret = capture.secret else { logger.error( "agent.run.failed", metadata: [ @@ -189,24 +371,19 @@ public final class AssistantExecutionController { ]) recordFailedRun( conversation, - promptID: promptID ?? lastUserTurn?.id ?? UUID(), - prompt: userPrompt, - authoredPrompt: authoredPrompt ?? lastUserTurn?.authoredText, - images: userImages, - contextAttachments: userContextAttachments, + promptID: capture.prompt.id, prompt: capture.prompt.text, + authoredPrompt: capture.prompt.authoredText, images: capture.prompt.images, + contextAttachments: capture.prompt.contextAttachments, + permissionMode: capture.permissionMode, reason: String(localized: "The Chat model has no available credential.") ) return } - let mode = ai.resolve(conversation: conversation.permissionMode) - let nativeSearch = - ai.webSearch.isEnabled && ai.webSearch.prefersBuiltIn - && chat.provider.supportsNativeWebSearch(model: chat.model) let model = AgentProviderClient( provider: chat.provider, model: chat.model, secret: secret, - reasoning: conversation.reasoningEffort, - webSearch: nativeSearch, promptCacheKey: conversation.id.uuidString + reasoning: capture.reasoning, + webSearch: capture.nativeSearch, promptCacheKey: conversation.id.uuidString ) // One channel for the whole run, shared by everything that publishes // into it. The broker used to raise its review events through a closure @@ -214,22 +391,11 @@ public final class AssistantExecutionController { // events had no defined order against the tool events they belong // between, and its durable writes had no defined order against // anything. See `AgentEventChannel`. - let channel = AgentEventChannel(repository: agentRunRepository) - - // Read once, here, for the reason `isPlanning` below is: a run's posture - // is fixed when it starts, so enabling a skill while the agent is - // thinking must not change what this run was told it had. Empty when the - // model cannot call tools, which is what keeps the catalog block from - // naming a tool this run was never given. - let skillCatalog = - chat.model.abilities.contains(.toolCall) - ? skills.catalog : AgentSkillCatalog() - - let baseRegistry = makeRegistry( - conversation, chat.model, chat.provider, skillCatalog, channel) + guard let channel = capture.channel, let baseRegistry = capture.baseRegistry else { return } + let skillCatalog = capture.skillCatalog let skillRegistry = AgentToolCatalog.registry( builtIn: .init( - groups: [.skills, .memory], skills: skillCatalog, skillInventory: skills.inventory, + groups: [.skills, .memory], skills: skillCatalog, skillInventory: capture.skillInventory, readOnlySkillNames: AgentSkillLibrary.reservedNames, skillPackageResolver: GitHubSkillPackageResolver(), skillLibrary: skills, memory: memorySession?.access)) @@ -242,19 +408,14 @@ public final class AssistantExecutionController { let guardianServices = AgentToolServices() .setting(AgentSSHInteraction.unavailable, for: .sshInteraction) let reviewer: (any GuardianReviewing)? = { - guard mode == .approveForMe, let review = ai.resolve(.guardian) else { return nil } - guard let reviewSecret = ai.modelSecret(for: review.provider) - else { return nil } - let reasoning = GuardianClient.preferredReasoning( - model: review.model, provider: review.provider - ) + guard capture.permissionMode == .approveForMe, let review = capture.guardian else { return nil } let reviewTools = review.model.abilities.contains(.toolCall) ? registry : AgentToolRegistry([]) return GuardianClient( model: AgentProviderClient( - provider: review.provider, model: review.model, secret: reviewSecret, - reasoning: reasoning, + provider: review.provider, model: review.model, secret: review.secret, + reasoning: review.reasoning, promptCacheKey: TermindAgentPolicy.guardianCacheKey(conversationID: conversation.id) ), sessions: guardianSessions, @@ -286,11 +447,7 @@ public final class AssistantExecutionController { conversation.noteToolSchemas(registry.descriptors) // Read once, here: the posture a run enforces is the one it started // under, and the user is free to leave plan mode while it is thinking. - let frozenContext = - lastUserTurn?.contextSnapshot - ?? contextSnapshot( - for: conversation, context: context ?? .none, - terminalSuggestionsAvailable: terminalSuggestionsAvailable) + let frozenContext = capture.contextSnapshot let isPlanning = frozenContext.planContract != nil // The catalog reaches the model boundary through the pipeline rather // than through the system prompt, so the prompt stays byte-identical for @@ -304,11 +461,7 @@ public final class AssistantExecutionController { configuration.outputProjection = AgentToolOutputProjection( workspace: AgentScratchWorkspace( conversationID: conversation.id, applicationName: TermindAgentPolicy.scratchDirectoryName)) - configuration.contextSnapshot = { session, planning in - AgentTurnContextSnapshot( - sessionContext: session, skillCatalog: skillCatalog.isEmpty ? nil : skillCatalog.catalogBlock, - planContract: planning ? TermindAgentPolicy.planContract : nil) - } + configuration.contextSnapshot = { _, _ in frozenContext } configuration.contextPipeline = { sessionContext, promptID, planning in var pipeline = AgentContextPipeline.chat( sessionContext: sessionContext, before: promptID, @@ -336,11 +489,11 @@ public final class AssistantExecutionController { runtime: runtime, request: AgentRunRequest( conversationID: conversation.id, - promptID: promptID ?? lastUserTurn?.id ?? UUID(), prompt: userPrompt, - authoredPrompt: authoredPrompt ?? lastUserTurn?.authoredText, - promptImages: userImages, - promptContextAttachments: userContextAttachments, - permissionMode: mode, isPlanning: isPlanning, + promptID: capture.prompt.id, prompt: capture.prompt.text, + authoredPrompt: capture.prompt.authoredText, + promptImages: capture.prompt.images, + promptContextAttachments: capture.prompt.contextAttachments, + permissionMode: capture.permissionMode, isPlanning: isPlanning, systemPrompt: TermindAgentPolicy.systemPrompt, priorMessages: conversation.pendingPriorMessages, authorizationEvidence: GuardianEvidence.collect(from: conversation.transcript), @@ -355,6 +508,258 @@ public final class AssistantExecutionController { scheduleTitle(for: conversation, isPlanning: isPlanning, operationID: operationID) } + private func canSubmit(to conversation: ConversationModel) -> Bool { + !conversation.isArchived && !conversation.isStreaming && !conversation.isCompacting + && !conversation.isHistoryChanging && !conversation.isPreparingAgentRun + } + + private func captureRun( + _ conversation: ConversationModel, + prompt: ConversationModel.ComposedUserTurn, + contextSnapshot: AgentTurnContextSnapshot, + beginPreparation: Bool, + supersedePreparation: Bool = false + ) -> RunCapture? { + let id = UUID() + let chat = ai.resolve(conversation: conversation.model) + let secret = chat.flatMap { ai.modelSecret(for: $0.provider) } + let permissionMode = ai.resolve(conversation: conversation.permissionMode) + let nativeSearch = + chat.map { + ai.webSearch.isEnabled && ai.webSearch.prefersBuiltIn + && $0.provider.supportsNativeWebSearch(model: $0.model) + } ?? false + let skillCatalog = + chat?.model.abilities.contains(.toolCall) == true + ? skills.catalog : AgentSkillCatalog() + let skillInventory = skills.inventory + let channel = chat != nil && secret != nil ? AgentEventChannel(repository: agentRunRepository) : nil + let baseRegistry = channel.flatMap { channel in + chat.map { + makeRegistry(conversation, $0.model, $0.provider, skillCatalog, channel) + } + } + let guardian: GuardianCapture? = { + guard permissionMode == .approveForMe, let review = ai.resolve(.guardian), + let secret = ai.modelSecret(for: review.provider) + else { return nil } + return GuardianCapture( + provider: review.provider, model: review.model, secret: secret, + reasoning: GuardianClient.preferredReasoning( + model: review.model, provider: review.provider)) + }() + let contextWindow = chat?.model.contextLength + let shouldCompact = + conversation.usage.map { + AgentCompaction.shouldCompact( + contextTokens: $0.contextTokens, contextWindow: contextWindow, + reserveTokens: compactionBudget.reserveTokens) + } ?? false + let capture = RunCapture( + id: id, prompt: prompt, contextSnapshot: contextSnapshot, + permissionMode: permissionMode, reasoning: conversation.reasoningEffort, + chat: chat, secret: secret, nativeSearch: nativeSearch, + skillCatalog: skillCatalog, skillInventory: skillInventory, + channel: channel, baseRegistry: baseRegistry, guardian: guardian, + memorySessionID: contextSnapshot.memorySessionID, + shouldCompact: shouldCompact, compactionModel: compactionModel(), + memoryReviewModel: memoryReviewModel(conversation)) + guard beginPreparation else { return capture } + let cancellation: @MainActor () -> Task = { [weak self, weak conversation] in + guard let self, let conversation else { return Task {} } + return self.requestCancellation( + for: conversation, expectedID: id, recordsInterruption: true) + } + if supersedePreparation { + if let current = preparations[conversation.id] { + _ = requestCancellation( + for: conversation, expectedID: current.id, + recordsInterruption: false) + } + preparations[conversation.id] = PreparationState( + id: id, conversation: conversation, permissionMode: permissionMode) + conversation.supersedeAgentPreparation(id: id, cancellation: cancellation) + return capture + } + let installed = conversation.beginAgentPreparation(id: id, cancellation: cancellation) + guard installed else { return nil } + preparations[conversation.id] = PreparationState( + id: id, conversation: conversation, permissionMode: permissionMode) + return capture + } + + private func startPreparation( + _ capture: RunCapture, for conversation: ConversationModel, + compactBeforeRun: Bool + ) { + guard isCurrent(capture, for: conversation) else { return } + let needsAsync = (compactBeforeRun && capture.shouldCompact) || capture.memorySessionID != nil + guard needsAsync else { + performStartAgentRun(conversation, capture: capture) + retirePreparation(capture.id, for: conversation) + return + } + let task = Task { [weak self, weak conversation] in + guard let self, let conversation else { return } + if compactBeforeRun, capture.shouldCompact { + await compactCaptured(conversation, capture: capture) + } + guard !Task.isCancelled, isCurrent(capture, for: conversation) else { + await finishCancelledPreparation(capture, for: conversation) + return + } + let memorySession: (context: AgentMemoryContext, access: any AgentMemoryAccessing)? + if let memorySessionID = capture.memorySessionID, let memory { + memorySession = await memory.session(memorySessionID) + } else { + memorySession = nil + } + guard !Task.isCancelled, isCurrent(capture, for: conversation) else { + await finishCancelledPreparation(capture, for: conversation) + return + } + performStartAgentRun(conversation, capture: capture, memorySession: memorySession) + retirePreparation(capture.id, for: conversation) + } + guard var state = preparations[conversation.id], state.id == capture.id else { + task.cancel() + return + } + state.task = task + preparations[conversation.id] = state + } + + private func compactCaptured(_ conversation: ConversationModel, capture: RunCapture) async { + guard let model = capture.compactionModel, !conversation.isStreaming, !conversation.isCompacting else { return } + conversation.beginCompaction() + guard let span = conversation.compactionSpan(keepRecentTokens: compactionBudget.keepRecentTokens) else { + conversation.endCompaction() + return + } + do { + if capture.contextSnapshot.planContract == nil, let memory, + let review = capture.memoryReviewModel + { + await memory.review( + conversationID: conversation.id, messages: conversation.transcript, + model: review, wait: true) + } + try Task.checkCancellation() + let summary = try await agentCompaction.summarize( + span.summarize, instructions: "", + toolResultLimit: compactionBudget.toolResultLimit, model: model) + try Task.checkCancellation() + guard isCurrent(capture, for: conversation), + let record = conversation.prepareCompaction(summary: summary, cut: span.cut) + else { throw CancellationError() } + try await persistAndApplyCompaction(record, to: conversation, cut: span.cut) + conversation.endCompaction() + } catch is CancellationError { + conversation.endCompaction() + } catch { + conversation.failCompaction(error.localizedDescription) + } + } + + private func isCurrent(_ capture: RunCapture, for conversation: ConversationModel) -> Bool { + guard let state = preparations[conversation.id] else { return false } + return state.id == capture.id && !state.cancellationRequested + } + + private func retirePreparation(_ id: UUID, for conversation: ConversationModel) { + guard preparations[conversation.id]?.id == id else { return } + preparations[conversation.id] = nil + conversation.finishAgentPreparation(id: id) + } + + private func cancelCapturedTurn(_ capture: RunCapture, in conversation: ConversationModel) { + guard isCurrent(capture, for: conversation) else { return } + preparations[conversation.id]?.task?.cancel() + retirePreparation(capture.id, for: conversation) + } + + /// Invalidates a captured run before returning to the caller. The task only + /// joins asynchronous work and persists the terminal snapshot; it never + /// decides whether the runtime may still start. + private func requestCancellation( + for conversation: ConversationModel, expectedID: UUID? = nil, + recordsInterruption: Bool + ) -> Task { + guard var state = preparations[conversation.id], + expectedID == nil || state.id == expectedID + else { return Task {} } + state.recordsInterruption = state.recordsInterruption || recordsInterruption + state.cancellationRequested = true + state.task?.cancel() + state.historyTask?.cancel() + if let cancellationTask = state.cancellationTask { + preparations[conversation.id] = state + return cancellationTask + } + preparations[conversation.id] = state + let preparationTask = state.task + let historyTask = state.historyTask + let preparationID = state.id + let cancellationTask = Task { @MainActor [weak self, weak conversation] in + guard let self, let conversation else { return } + if let historyTask { + _ = try? await historyTask.value + } + if let preparationTask { + await preparationTask.value + } + await finishCancelledPreparation( + preparationID, for: conversation) + } + state.cancellationTask = cancellationTask + preparations[conversation.id] = state + return cancellationTask + } + + private func finishCancelledPreparation( + _ capture: RunCapture, for conversation: ConversationModel + ) async { + guard let state = preparations[conversation.id], state.id == capture.id else { return } + if state.recordsInterruption { + let now = Date.now + do { + try await agentRunRepository.save( + AgentRunSnapshot( + conversationID: conversation.id, state: .interrupted, + permissionMode: capture.permissionMode, + messages: conversation.transcript, startedAt: now, finishedAt: now)) + guard preparations[conversation.id]?.id == capture.id else { return } + conversation.handle(.runState(.interrupted)) + } catch { + guard preparations[conversation.id]?.id == capture.id else { return } + conversation.messages.append(ChatMessage(role: .assistant, text: error.localizedDescription)) + } + } + retirePreparation(capture.id, for: conversation) + } + + private func finishCancelledPreparation( + _ preparationID: UUID, for conversation: ConversationModel + ) async { + guard let state = preparations[conversation.id], state.id == preparationID else { return } + if state.recordsInterruption { + let now = Date.now + do { + try await agentRunRepository.save( + AgentRunSnapshot( + conversationID: conversation.id, state: .interrupted, + permissionMode: state.permissionMode, + messages: conversation.transcript, startedAt: now, finishedAt: now)) + guard preparations[conversation.id]?.id == preparationID else { return } + conversation.handle(.runState(.interrupted)) + } catch { + guard preparations[conversation.id]?.id == preparationID else { return } + conversation.messages.append(ChatMessage(role: .assistant, text: error.localizedDescription)) + } + } + retirePreparation(preparationID, for: conversation) + } + private func recordFailedRun( _ conversation: ConversationModel, promptID: UUID, @@ -362,6 +767,7 @@ public final class AssistantExecutionController { authoredPrompt: String?, images: [AgentImageAttachment], contextAttachments: [AgentContextAttachment], + permissionMode: AgentPermissionMode, reason: String ) { var messages = conversation.pendingPriorMessages @@ -377,7 +783,7 @@ public final class AssistantExecutionController { let now = Date.now let snapshot = AgentRunSnapshot( conversationID: conversation.id, state: .failed, - permissionMode: ai.resolve(conversation: conversation.permissionMode), + permissionMode: permissionMode, messages: messages, startedAt: now, finishedAt: now, failure: reason ) @@ -426,7 +832,7 @@ public final class AssistantExecutionController { isManual: Bool = true ) async -> Bool { guard authorize() else { return false } - guard !conversation.isStreaming, !conversation.isCompacting else { return false } + guard !conversation.isAgentRunActive, !conversation.isCompacting else { return false } conversation.beginCompaction() guard let span = conversation.compactionSpan( @@ -483,23 +889,6 @@ public final class AssistantExecutionController { conversation.applyCompaction(record, cut: cut) } - /// Compacts before a run when the conversation is close to the window. - /// - /// Threshold only. Recovering a turn that has *already* overflowed means - /// recognising four providers' distinct context-length errors, which is its - /// own piece of work — this is what keeps a conversation from getting there. - private func compactIfNeeded(_ conversation: ConversationModel) async { - guard let usage = conversation.usage else { return } - let window = ai.resolve(conversation: conversation.model)?.model.contextLength - guard - AgentCompaction.shouldCompact( - contextTokens: usage.contextTokens, contextWindow: window, - reserveTokens: compactionBudget.reserveTokens - ) - else { return } - await compact(conversation, isManual: false) - } - /// Resolves the Chat model with reasoning clamped off, like titling does. /// /// Summarizing is not a task that improves with a thinking budget, and this diff --git a/Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift b/Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift index ace55860..b109fea5 100644 --- a/Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift +++ b/Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift @@ -291,6 +291,29 @@ public final class AssistantLibraryModel { return started } + public func canPromote(_ conversation: ConversationModel) -> Bool { + !conversations.contains(where: { $0.id == conversation.id }) + } + + /// Publishes a conversation only after its first user turn and immutable run + /// capture have already succeeded. + @discardableResult + public func promoteCapturedConversation(_ conversation: ConversationModel) -> Bool { + guard canPromote(conversation), !conversation.transcript.isEmpty else { return false } + store.insert(conversation) + selectedConversationID = conversation.id + return true + } + + @discardableResult + public func promoteCapturedDraftConversation(_ conversation: ConversationModel) -> Bool { + guard conversation === draftConversation, + promoteCapturedConversation(conversation) + else { return false } + draftConversation = ConversationModel() + return true + } + /// Promotes the sidebar's scratch conversation into history on its first /// real send. Empty drafts remain ephemeral and never create blank rows. @discardableResult diff --git a/Frameworks/TermindApplication/Agent/ConversationModel.swift b/Frameworks/TermindApplication/Agent/ConversationModel.swift index cf5ec485..4a680a0e 100644 --- a/Frameworks/TermindApplication/Agent/ConversationModel.swift +++ b/Frameworks/TermindApplication/Agent/ConversationModel.swift @@ -800,6 +800,8 @@ public final class ConversationModel: Identifiable { private var runtime: AgentRuntime? private var eventTask: Task? private var runtimeGeneration: UInt64 = 0 + private var agentPreparationID: UUID? + private var cancelAgentPreparation: (() -> Task)? private var approvalWaiters: [UUID: CheckedContinuation] = [:] private var inputWaiters: [UUID: CheckedContinuation] = [:] private var userInputTimeoutTask: Task? @@ -838,6 +840,13 @@ public final class ConversationModel: Identifiable { /// start a second one. public private(set) var isCompacting = false + /// A submitted user turn whose immutable run assembly is still waiting for + /// compaction or memory. This is deliberately separate from `isStreaming`: + /// there is no runtime to accept steering until preparation completes. + public private(set) var isPreparingAgentRun = false + + public var isAgentRunActive: Bool { isStreaming || isPreparingAgentRun } + /// The current task list, whole. Replaced rather than patched — see /// `AgentTask`. public private(set) var tasks: [AgentTask] = [] @@ -1765,8 +1774,14 @@ public final class ConversationModel: Identifiable { public func appendUserTurnContent(_ text: String, contextSnapshot: AgentTurnContextSnapshot? = nil) -> ComposedUserTurn { - let composed = composed(text) - let messageID = UUID() + appendUserTurnContent(composeUserTurn(text), contextSnapshot: contextSnapshot) + } + + @discardableResult + public func appendUserTurnContent( + _ composed: ComposedUserTurn, contextSnapshot: AgentTurnContextSnapshot? = nil + ) -> ComposedUserTurn { + let messageID = composed.id let contextChips = composer.attachments.map { ContextChipReference( id: $0.id, kind: $0.kind, label: $0.label, @@ -1778,14 +1793,14 @@ public final class ConversationModel: Identifiable { refreshAttachedContextEstimate() messages.append( ChatMessage( - id: messageID, role: .user, text: text, + id: messageID, role: .user, text: composed.authoredText, contextChips: contextChips, images: composed.images ) ) transcript.append( AgentTranscriptMessage( id: messageID, role: .user, text: composed.text, - authoredText: text, images: composed.images, + authoredText: composed.authoredText, images: composed.images, contextAttachments: composed.contextAttachments, contextSnapshot: contextSnapshot ) ) @@ -1794,17 +1809,14 @@ public final class ConversationModel: Identifiable { publishContextEstimateImmediately() } updatedAt = .now - return ComposedUserTurn( - id: messageID, text: composed.text, authoredText: text, images: composed.images, - contextAttachments: composed.contextAttachments - ) + return composed } /// Renders attachments into the model-facing message body. The visible /// `ChatMessage` deliberately keeps only the prompt: extracted documents /// and pasted text can be hundreds of thousands of characters and do not /// belong in a Markdown bubble. - private func composed(_ text: String) -> ComposedUserTurn { + public func composeUserTurn(_ text: String) -> ComposedUserTurn { let skillDirective = composer.armedSkillName.map { name in """ @@ -1857,6 +1869,41 @@ public final class ConversationModel: Identifiable { /// provider response from racing the UI attachment path. public private(set) var activeContextSnapshot: AgentTurnContextSnapshot? + @discardableResult + func beginAgentPreparation( + id: UUID, cancellation: @escaping @MainActor () -> Task + ) -> Bool { + guard agentPreparationID == nil else { return false } + agentPreparationID = id + cancelAgentPreparation = cancellation + isPreparingAgentRun = true + return true + } + + func supersedeAgentPreparation( + id: UUID, cancellation: @escaping @MainActor () -> Task + ) { + agentPreparationID = id + cancelAgentPreparation = cancellation + isPreparingAgentRun = true + } + + func finishAgentPreparation(id: UUID) { + guard agentPreparationID == id else { return } + agentPreparationID = nil + cancelAgentPreparation = nil + isPreparingAgentRun = false + } + + @discardableResult + func cancelPendingAgentPreparation() -> Task? { + cancelAgentPreparation?() + } + + func cancelPendingAgentPreparationAndWait() async { + await cancelPendingAgentPreparation()?.value + } + public func start(runtime: AgentRuntime, request: AgentRunRequest) { activeContextSnapshot = request.contextSnapshot if let index = transcript.firstIndex(where: { $0.id == request.promptID }) { @@ -1908,6 +1955,7 @@ public final class ConversationModel: Identifiable { /// Retains the ordered event consumer until the cancellation snapshot is durable. public func stopAndWait() async { + await cancelPendingAgentPreparationAndWait() guard let runtime else { stop() return @@ -1921,6 +1969,7 @@ public final class ConversationModel: Identifiable { } public func stop() { + cancelPendingAgentPreparation() runtimeGeneration &+= 1 settleStreamingPresentation(for: activeRunID) publishAllStagedTools() @@ -1961,7 +2010,7 @@ public final class ConversationModel: Identifiable { public func submitSteeringDraft(context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false) { let text = composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) guard canSendDraft, let runtime else { return } - let composed = composed(text) + let composed = composeUserTurn(text) var snapshot = activeContextSnapshot?.withSessionContext(context.modelContext) if snapshot != nil { var availability = snapshot?.toolAvailability ?? [:] diff --git a/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift b/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift index c008f934..31e7aae1 100644 --- a/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift +++ b/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift @@ -354,6 +354,7 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { _ id: UUID, fromUserMessageID messageID: UUID, replacingWith replacement: AgentTranscriptMessage ) async throws { + try Task.checkCancellation() let allRuns = try context.fetch(FetchDescriptor()) let runs = allRuns.filter { $0.conversationID == id } .sorted { $0.startedAt < $1.startedAt } diff --git a/TFI/Application/Agent/MobileAssistantModel.swift b/TFI/Application/Agent/MobileAssistantModel.swift index c5295634..d44764d4 100644 --- a/TFI/Application/Agent/MobileAssistantModel.swift +++ b/TFI/Application/Agent/MobileAssistantModel.swift @@ -124,10 +124,12 @@ final class MobileAssistantModel { } guard conversation.canSendDraft else { return } if library.selectedConversation == nil { - guard let started = library.startDraftConversation() else { return } + guard + execution.sendDraftConversation( + in: library, + terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) != nil + else { return } configure(library.draftConversation) - execution.startAgentRun( - started, terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) } else { execution.sendAgentDraft( conversation, terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) @@ -210,20 +212,17 @@ final class MobileAssistantModel { } } - func edit(_ conversation: ConversationModel, messageID: UUID, text: String) async { - await perform { - guard - let prompt = try await operations.edit( - conversation, messageID: messageID, text: text, - contextSnapshot: execution.contextSnapshot( - for: conversation, context: .none, - terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable)) - else { return } - execution.startAgentRun( - conversation, promptID: prompt.id, prompt: prompt.text, - authoredPrompt: prompt.authoredText, promptImages: prompt.images, - promptContextAttachments: prompt.contextAttachments, + func edit(_ conversation: ConversationModel, messageID: UUID, text: String) { + guard + let prepared = execution.prepareAgentEdit( + conversation, messageID: messageID, text: text, terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) + else { return } + Task { + await perform { + _ = try await execution.performAgentEdit( + prepared, in: conversation, operations: operations) + } } } @@ -234,12 +233,12 @@ final class MobileAssistantModel { fileprivate func implement( _ card: AgentPlanCard, in conversation: ConversationModel, library: AssistantLibraryModel ) { - guard !conversation.isArchived, !conversation.isStreaming else { return } - conversation.resolvePlan(card.id, as: .implemented) - conversation.isPlanning = false - conversation.composer.draft = String(localized: "Implement this plan.") + guard + execution.sendPlanImplementation( + card, in: conversation, + terminalSuggestionsAvailable: Self.terminalSuggestionsAvailable) + else { return } select(conversation, in: library) - send(in: library) } private func perform(_ operation: () async throws -> Void) async { @@ -299,8 +298,8 @@ final class MobileAssistantSceneModel { await runtime.fork(conversation, at: messageID, in: library) } - func edit(_ conversation: ConversationModel, messageID: UUID, text: String) async { - await runtime.edit(conversation, messageID: messageID, text: text) + func edit(_ conversation: ConversationModel, messageID: UUID, text: String) { + runtime.edit(conversation, messageID: messageID, text: text) } func compact(_ conversation: ConversationModel, instructions: String = "") async { diff --git a/TFI/Features/AI/MobileAssistantComposer.swift b/TFI/Features/AI/MobileAssistantComposer.swift index adc327e8..b1d4c69e 100644 --- a/TFI/Features/AI/MobileAssistantComposer.swift +++ b/TFI/Features/AI/MobileAssistantComposer.swift @@ -171,7 +171,7 @@ struct MobileAssistantComposer: View { .disabled(!canSend) Button("Stop", action: conversation.stop) .keyboardShortcut(MobileShortcut.stop) - .disabled(!conversation.isStreaming) + .disabled(!conversation.isAgentRunActive) } .frame(width: 0, height: 0) .opacity(0) @@ -374,7 +374,7 @@ struct MobileAssistantComposer: View { } @ViewBuilder private var primaryButton: some View { - if conversation.isStreaming && !canSend { + if conversation.isPreparingAgentRun || (conversation.isStreaming && !canSend) { Button(action: conversation.stop) { Label("Stop", systemImage: "stop.circle.fill").labelStyle(.iconOnly) .font(.system(size: 34, weight: .semibold)).foregroundStyle(.red) diff --git a/TFI/Features/AI/MobileAssistantConversation.swift b/TFI/Features/AI/MobileAssistantConversation.swift index 54b88dde..88eed372 100644 --- a/TFI/Features/AI/MobileAssistantConversation.swift +++ b/TFI/Features/AI/MobileAssistantConversation.swift @@ -526,7 +526,7 @@ struct MobileAssistantConversation: View { } ToolbarItem(placement: .confirmationAction) { Button("Send", systemImage: "arrow.up") { - Task { await model.edit(conversation, messageID: message.id, text: editedText) } + model.edit(conversation, messageID: message.id, text: editedText) editing = nil }.labelStyle(.iconOnly).disabled( editedText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 262124eb..42a66c19 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -212,6 +212,33 @@ struct MobileAssistantTests { try expectMissingModelFailure(in: draft) } + @Test func planImplementationStartsAnActingTurnWithPendingComposerInputs() throws { + let app = try MobileAppModel(inMemory: true) + let assistant = app.assistant.makeSceneModel() + let conversation = ConversationModel() + _ = assistant.library.startConversation(conversation) + let plan = AgentPlan( + title: "Implementation plan", body: "Apply the requested change.", + scratchPath: "/tmp/plan.md") + conversation.isPlanning = true + conversation.handle(.planPresented(plan)) + conversation.composer.armedSkillName = "disk-triage" + conversation.attach(ContextChip(kind: .file, label: "service.log", content: "connection refused")) + let card = try #require(conversation.plans.first) + + assistant.implement(card, in: conversation) + + #expect(conversation.plans.first?.outcome == .implemented) + #expect(!conversation.isPlanning) + let turn = try #require(conversation.transcript.last) + #expect(turn.contextSnapshot?.planContract == nil) + #expect(turn.text.contains("connection refused")) + #expect(turn.text.contains("")) + #expect(conversation.composer.attachments.isEmpty) + #expect(conversation.composer.armedSkillName == nil) + try expectMissingModelFailure(in: conversation) + } + @Test func existingConversationSendStillUsesTheDraftExecutionPath() async throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() diff --git a/TFM/App/AppModel.swift b/TFM/App/AppModel.swift index 2b3e42af..99a6cde3 100644 --- a/TFM/App/AppModel.swift +++ b/TFM/App/AppModel.swift @@ -416,6 +416,46 @@ final class AppModel { conversation, context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) } + @discardableResult + func sendNewAgentConversation( + _ conversation: ConversationModel, in library: AssistantLibraryModel, + context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false + ) -> ConversationModel? { + assistantExecution.sendNewConversation( + conversation, in: library, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + } + + @discardableResult + func sendNewAgentDraft( + in library: AssistantLibraryModel, context: SessionContext = .none, + terminalSuggestionsAvailable: Bool = false + ) -> ConversationModel? { + assistantExecution.sendDraftConversation( + in: library, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + } + + @discardableResult + func sendAgentText( + _ text: String, in conversation: ConversationModel, + context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false + ) -> Bool { + assistantExecution.sendAgentText( + text, in: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + } + + @discardableResult + func sendPlanImplementation( + _ card: AgentPlanCard, in conversation: ConversationModel, + context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false + ) -> Bool { + assistantExecution.sendPlanImplementation( + card, in: conversation, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + } + func startAgentRun( _ conversation: ConversationModel, promptID: UUID? = nil, prompt: String? = nil, authoredPrompt: String? = nil, promptImages: [AgentImageAttachment]? = nil, @@ -600,20 +640,15 @@ final class AppModel { context: SessionContext, terminalSuggestionsAvailable: Bool, reportingTo window: WindowModel? ) { guard let conversation = conversation(id) else { return } + guard + let prepared = assistantExecution.prepareAgentEdit( + conversation, messageID: messageID, text: value, context: context, + terminalSuggestionsAvailable: terminalSuggestionsAvailable) + else { return } Task { [weak window] in do { - guard - let prompt = try await assistantOperations.edit( - conversation, messageID: messageID, text: value, - contextSnapshot: assistantExecution.contextSnapshot( - for: conversation, context: context, - terminalSuggestionsAvailable: terminalSuggestionsAvailable)) - else { return } - startAgentRun( - conversation, promptID: prompt.id, prompt: prompt.text, - authoredPrompt: prompt.authoredText, promptImages: prompt.images, - promptContextAttachments: prompt.contextAttachments, - context: context, terminalSuggestionsAvailable: terminalSuggestionsAvailable) + _ = try await assistantExecution.performAgentEdit( + prepared, in: conversation, operations: assistantOperations) } catch { window?.assistantOperationError = error.localizedDescription } } } diff --git a/TFM/Features/AI/ConversationTranscriptView.swift b/TFM/Features/AI/ConversationTranscriptView.swift index cce59ecd..013a84ad 100644 --- a/TFM/Features/AI/ConversationTranscriptView.swift +++ b/TFM/Features/AI/ConversationTranscriptView.swift @@ -336,11 +336,8 @@ private struct ConversationTranscriptSurface: View { }, implementPlan: { card in guard app.proAccess.require(.assistant) else { return } - conversation.resolvePlan(card.id, as: .implemented) - let prompt = ConversationModel.implementationPrompt(for: card.plan) - conversation.appendUserTurn(prompt) - app.startAgentRun( - conversation, prompt: prompt, context: sessionContext, + app.sendPlanImplementation( + card, in: conversation, context: sessionContext, terminalSuggestionsAvailable: showsTerminalActions) }, canDeliverCommand: { diff --git a/TFM/Features/AI/ConversationViews.swift b/TFM/Features/AI/ConversationViews.swift index ac2dae56..804a102b 100644 --- a/TFM/Features/AI/ConversationViews.swift +++ b/TFM/Features/AI/ConversationViews.swift @@ -738,7 +738,7 @@ struct ComposerView: View { // focus. Without this, typing would make a run unstoppable // until the draft was cleared again. .onKeyPress(KeyEquivalent("."), phases: .down) { press in - guard conversation.isStreaming, press.modifiers.contains(.command) else { + guard conversation.isAgentRunActive, press.modifiers.contains(.command) else { return .ignored } conversation.stop() @@ -776,7 +776,9 @@ struct ComposerView: View { /// moved under the pointer the moment the draft gained a character. With a /// draft to send, Send is what the button is for; with nothing to send, /// stopping is the only thing left to do. - private var showsStopButton: Bool { conversation.isStreaming && !conversation.canSendDraft } + private var showsStopButton: Bool { + conversation.isPreparingAgentRun || (conversation.isStreaming && !conversation.canSendDraft) + } private func standardControls(contextEstimate: AgentContextEstimate) -> some View { HStack(spacing: Metrics.Space.s) { diff --git a/TFM/Features/Assistant/AssistantView.swift b/TFM/Features/Assistant/AssistantView.swift index fe78a78b..635d968d 100644 --- a/TFM/Features/Assistant/AssistantView.swift +++ b/TFM/Features/Assistant/AssistantView.swift @@ -215,11 +215,12 @@ struct AssistantPanelView: View { sessionContext: window.sidebar.sessionContext, terminalSuggestionsAvailable: true, onSend: { - if let conversation = window.sendAssistantSidebarDraft() { - app.startAgentRun( - conversation, context: window.sidebar.sessionContext, - terminalSuggestionsAvailable: true - ) + if let conversation = app.sendNewAgentConversation( + window.sidebar.assistantDraft, in: window.assistantLibrary, + context: window.sidebar.sessionContext, + terminalSuggestionsAvailable: true) + { + window.assistantSidebarConversationDidStart(conversation) } } ) @@ -1407,9 +1408,6 @@ struct AssistantView: View { } private func sendNewConversation() { - guard app.proAccess.require(.assistant) else { return } - guard let conversation = window.assistantLibrary.startDraftConversation() - else { return } - app.startAgentRun(conversation) + _ = app.sendNewAgentDraft(in: window.assistantLibrary) } } diff --git a/TFM/Features/Workspaces/WindowModel.swift b/TFM/Features/Workspaces/WindowModel.swift index 5d005c17..58177476 100644 --- a/TFM/Features/Workspaces/WindowModel.swift +++ b/TFM/Features/Workspaces/WindowModel.swift @@ -757,6 +757,11 @@ final class WindowModel { return conversation } + func assistantSidebarConversationDidStart(_ conversation: ConversationModel) { + sidebar.assistantConversationID = conversation.id + sidebar.assistantDraft = ConversationModel() + } + /// Republishes the focused Workspace's host, directory, and OS facts for the /// sidebar composer. /// From d4e972f04d2d5be0395fe0fcab078d8122248d6a Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 10:49:57 +0800 Subject: [PATCH 06/30] Document Assistant capture and cancellation lifecycle --- openwiki/.claims/architecture/desktop-ui.json | 8 +- .../architecture/engineering-invariants.json | 52 ++- openwiki/.claims/architecture/mobile-ui.json | 20 +- openwiki/.claims/architecture/module-map.json | 10 +- .../architecture/native-app-directories.json | 8 +- .../architecture/platform-runtime.json | 10 +- .../shared-module-directories.json | 14 +- .../concepts/agent-runtime-and-assistant.json | 372 ++++++++++++++++-- openwiki/.claims/quickstart.json | 6 +- .../workflows/ssh-session-lifecycle.json | 6 +- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 104 ++--- openwiki/architecture/desktop-ui.md | 2 +- .../architecture/engineering-invariants.md | 18 +- openwiki/architecture/mobile-ui.md | 2 +- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 2 +- .../architecture/shared-module-directories.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 80 +++- openwiki/quickstart.md | 2 +- openwiki/workflows/ssh-session-lifecycle.md | 2 +- 22 files changed, 568 insertions(+), 160 deletions(-) diff --git a/openwiki/.claims/architecture/desktop-ui.json b/openwiki/.claims/architecture/desktop-ui.json index c5570b0b..5a793119 100644 --- a/openwiki/.claims/architecture/desktop-ui.json +++ b/openwiki/.claims/architecture/desktop-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:9e4dcfe15396af0c8e9ed77ff7347eac829aeff9d111ad37757a2e3f1e63b2c1", + "pageVersion": "sha256:aacd9e53709881d4edac532fd23dfffe4e8ba8cf7a5b611a79bfd846f1128d8a", "claims": [ { "id": "claim_5a53c5a47b7445f7a7b6f8865297a805", @@ -105,8 +105,8 @@ "statement": "The workspace compact Assistant has routing separate from the full Assistant destination and composes transcript/interaction regions with explicit terminal context; Escape cannot fall through from a blocking interaction into closing the conversation.", "evidence": [ { - "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L152-L225", - "version": "repo-lines-v1:sha256:d47d11a81fc91f016d97ec23e87c1a89e608cb049d364bda5cc33966ce5e6a5e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0YmFkZWYzNjBmZDM2NTYwNGE2ODg4NGU5NDJiYzA4M2RjZDQ0ZTJhNTBlYzYyMjkzOTExZDYxOTg1MmVhNDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MDMyZTk2NGQwOTMyMzUxZTVkMTg1MjY5MjgxYjE3MWEzYTA5YjA4YjI4NmU2NjFlYTQ3MDMyM2M4MmViNGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEyZjUyZjI3YTJiNzI0ZTlmYTM4NjA0ZGJkZGZlODczYTlkMTJhOTNiZWI0YWNiYWU5ZjFmNzk3ZDZkOTg1OTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L142-L227", + "version": "repo-lines-v1:sha256:e96c4ac72148f4e34e9ccbf9fe17ed3b7df5343281431eeba19e4ceb1b85eaf2:eyJzZWxlY3RlZExpbmVDb3VudCI6ODYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMyY2NmOTY5ODZiYTMxNzJlNzA4YmQzZWY1YTMyMzBmN2Y1N2QzNzg1ZmU2YzhmNzdjNGQ4MzdkZjc1OWMzOTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkMWMxYWE3ZmY5NDI4MTg1ZGFiMjIyMmU3NWI1N2U4Zjg2ZGFmMTJkZjZkNWI5MzMwYTI0YzQ1ODU0NzdjMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkY2E2ZmY0MTYzODU2MjUyOTU1NzNiYjQ0ZGRjZGM5ZDZiYmZkOWQ2OGRlYjVkZDRkNjA5OGNjZWRiNTFhNjUifQ" }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L7-L14", @@ -207,6 +207,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index e415c36d..f8aaf4e8 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:75fe464fb65e761e5eeaab3f31f1a2d852175cc5f78f5b099ddffee4a8655971", + "pageVersion": "sha256:564964f95e8176182ff915ce6d8834e7717b6a3a3be946666c5b1d1d749906cc", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -52,11 +52,11 @@ }, { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:ba89018766f45b430033efc2969c0307ba30c0d298bd8b55a51e17a08915c767" + "version": "repo-file-v1:sha256:8efd1aa2da741e633993416bf4f88eff4df978ee3768a13788a1ac0419bdf837" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -321,10 +321,54 @@ "version": "repo-lines-v1:sha256:a228288ef4e8a002f6ea687e2fadd9646d999992d73665e548aec331284a7d4e:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmMxNDAxZmI0YzIwNTE3YTEyYWUxOWZkMDNlYjE4Y2UwMDg3ODZlMGFmOGQ0OTJjZTNiNjIwZGI3YWJhMmFlNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" } ] + }, + { + "id": "claim_e9d194c36350453093030ee401cf1f18", + "statement": "AssistantExecutionController synchronously captures composed user-turn content, context, model/provider/credential, reasoning/permissions, search, skills/registry and Guardian/preparation configuration before memory or automatic compaction waits; first-send promotion and edit persistence follow capture.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L103-L340", + "version": "repo-lines-v1:sha256:a36381cd69fbf5b2b411a7a7a409a011e0e654507ad45760c7b36bb648354caa:eyJzZWxlY3RlZExpbmVDb3VudCI6MjM4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5YWQ1M2JhYWMxODc4NTJiNDMxYzIzMmUwNTE2MDYxZDQ5NmY3YTIxNTZlZTRiZjdkZWE2NDdmYzY2NDk4NjZkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI5NTZkZWYzNmJmNjVmNmJkNjM4NThiMTkwN2U3ZDNiOGQ4N2I2MjVmMzk5M2Q5YzM0ODQ1YzAwMTYwNGVkYzY3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI4M2FlZTJjY2Q2MDIwNWM0ZjU2NDVjNDYxNzM2ZWU2ZmI5MmU1NWRkM2YxNjJkNjRkNGNlMzhiMGNiNTYxMWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNjhlMWNiOWEwY2Y5YTE2NjZhODRmOGZkZjhmZjUzNGM1NjljYThkYTYxOGNmYWZhZjdiZWE1ZDY0ODU5ZTBhIn0" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L630", + "version": "repo-lines-v1:sha256:b1882f48ba2a0c4f0faaa010c3ad73fbbfed243a2f0f51c0d883ed8058e5a3d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" + } + ] + }, + { + "id": "claim_bbae8e4110c5473b83d5d2803745fea5", + "statement": "Preparation has a separate identity from streaming: cancellation invalidates it synchronously and joins tracked history/preparation work before interrupted persistence; stale edits are rejected, archive/delete cancel preparation, manual compaction refuses it, and changing selection alone does not cancel it.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L236-L259", + "version": "repo-lines-v1:sha256:fb96479831645e246050471c6bfb603e36dfa4c433cea61b917671f897948614:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2YmY3NzkyYmMwMzMxOWEyNThmMDMxMzZlNTg4MWE0YjdmNTI3ZWVkNTQ4ZDQ4MGRlZWE5Zjg0ZGE2NTczZWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwNTRiZjkyODIzYmI3NmNlYjU1YmNkZGI1Y2UwNTk0MTUzNjJjZGNhZjk3MTQ1MzhmMDg0NzQ1ZmQ4Njg2OWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MDZhNjU1YWM2ZmRkYzgzZGYwOGUyMmQyNjc1MzM3ZjZjYWYzN2E0NGZhMDdjOGEzNWViZGIwZWNlYTVjMWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxYWI2OTA5MDBkNWZjZmRjMmMxNjEzYjRlNDA0ODc3YTU2MjZjMjM2MWZiNGQ0MTJjMGIyMDlkYjBlNzJlMzgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L21", + "version": "repo-lines-v1:sha256:e2ba6b40bc2c5a1843a0a570f28c471ba3a74682cf31c79865a8472029912a21:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNWFkOTQ4ZjVhN2JlOWJlMTVkNTRhY2EwNzlmZWI4MDYwMjNlMjVjNjIzNmVmMWI0NTM0YzQ0NDA1ZTk1NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDU0MDY3MDk1MTM2ZmQyMDViNmNhM2JkMGFjYjk5N2M0MDg5M2JjNGI4OWFjMzMxN2YxZmNhYzFiMzc5ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4ZGJjMzU4MWFjZTk4MTNjMmE3ZmQ5OWI2YjNmN2I2ZTBlNmE5ZjJiMGJlZDUzNmUwOTJiZjI2NWMzOWNmNGIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L63-L75", + "version": "repo-lines-v1:sha256:c49e78dca59df1fa504084f70e5870b13c95199d99512f713215e4d963559c1c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlZGZjY2UzODdkY2Y5MTEzNWQ4YjcyMzViYjIzZGU4ZDM4OGVkOWY0YjU5MzhkODdlMTM1ZjA3OGU5MzBlNjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmNTU2NmQyMjg3Mjk1MmM0NDc3MmQ1NDEyZTgwZjE2MzI4ZWM1ZGM5YmIxM2YyNjVkODFmNTg0MGFhYWM1ZjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZjZjMxNzMzMTFlZjYxMGRlNTllOTE2MjM5ZDBiZDQ3YjA5OTU3MTNjYThiYWY1NGZmYWVkY2Y2ZmJmZTQxZjUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L241-L280", + "version": "repo-lines-v1:sha256:10dde8f96383fc4e7c331b4e927c1c5e61a7173356ef4b5b49d67c68ba29e2e3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExMGUxZDhkNTQ2OGFiNjQ4MzUxNzE3MWM1ZDQ0NzZiNjUzNjIwNDJlYzVlNzZhYjNlZWQ2NTM4YjhmZmRiN2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiZWNkMDQ0NWJmNGEzYTg5NTcwNTczNGE0YmEyYjFjYzQxMjRiNzM1NzYyYWY0ZWYxYzQyODcyNTkyMThlNzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiNDkxY2E1OGFjYmFiYWNhOTBhZDBkYmQyNjgxODk3NzFhMjY2YTYyM2FhZmE3NzRhYzY1MTg5OTk3YjU1YWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L770", + "version": "repo-lines-v1:sha256:405ec2eca133a1dd12f58eb7f2099860c2da18f294af59b286e2f1f935847c4c:eyJzZWxlY3RlZExpbmVDb3VudCI6ODcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0OWVhNzVmZmFiZjU2OTUxMTk2YTQ4ODljNDE1OGZmYjM3NDY5NThhMmI3NDIxODE1NjVkYTU3NDkxZDI2YmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkNjUzOWRjYTQ0MjY0ZTViYjAwZTU2MzRmY2M2NGI4YmMwOThmNTViNDAxZDBjNDIzZTYzNTlmNDJhOGE0ZjEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L829-L839", + "version": "repo-lines-v1:sha256:1cfe225932062439ff267e988637e685c28e2ecefa0b0e93398784b85cb79032:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhMmRhZDQzODJjOWExZTA4YjBkMDE4YjZmM2E5ZTE3ZDJlODU3YTA0ZjdjOWUyN2RhODQxOGI3YjUzN2U2MzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwMjczNmZkMDQ2ZjFlNWQ4Y2ExZTYyNjRhNWJjZjhlNTc2YmY4YzFmM2RmNmM4OTk2MTIxZDI1ZWIyMDAzMzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYyZDRiYThiMzc0ODExZjYxZDdmOTQ4NWYwNzMzMzFiMDczYTNmNTI5ZDdmM2ZiNDU2MDlmMTRhMTMxMjc1M2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJiMDY3MDk2YzhiOTcxNzZhODc3ZGQ3YzIyNjRmYjU3MTljMGRlYWRkMWIxMGI4YjcxYzljYWZjZDk2OWIyNDAifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/mobile-ui.json b/openwiki/.claims/architecture/mobile-ui.json index 1da889e3..4af9086a 100644 --- a/openwiki/.claims/architecture/mobile-ui.json +++ b/openwiki/.claims/architecture/mobile-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:a1e240ca495ddf76ace3e9861b2f459a2fa61e2d53179352e87b15452e6828ee", + "pageVersion": "sha256:5a57c9f6cd1214629ea2a75c3ba60386a70d5c2170cdb552f4dea473f0dde193", "claims": [ { "id": "claim_fbb919f62a2d4ab88aafcc4c1186731b", @@ -22,15 +22,15 @@ "evidence": [ { "resource": "repo://TFI/App/MobileSceneModel.swift#L83-L128", - "version": "repo-lines-v1:sha256:3fd379cb52f15ff04e2e6b6aac563325062772680b18c9d641b60eea728707a4:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwOTA1MzRlNGFlMDdmZThiMGM5MTk4MWE3MGY1ZDFjMmM4ZWE1YzMzZmY5OWEzZGQ2YTBjMWQwNWM3NzBmNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0OTBmZjExZjVjZTJiYzA2ZTFkODlhZTNjZmEyN2Y4NzhiMzZhNzE4Yjg0NjU4ZDFlNjI3OGExNjdmNjFiMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhjYzk0ZjdiZjEyMjBmOTFjNDQxODMzYjYxMGRjZGQ1MGM1N2ViNjc1YzNiYzY0YmY3Y2EyZThlYzkxNzBhYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNmMDZjZDBkMzM2MDE5NDAwZDMzYmMwYTA3ZDBhMTE5ZTViZTAxZTFlYjhiY2M1MWM1Nzg4NTM1ZTQ5NjJmNjEifQ" + "version": "repo-lines-v1:sha256:baaf1de27b2108824132566f55bab94ce04c9b4fad0238b88828b018d29b08ae:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwOTA1MzRlNGFlMDdmZThiMGM5MTk4MWE3MGY1ZDFjMmM4ZWE1YzMzZmY5OWEzZGQ2YTBjMWQwNWM3NzBmNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlN2JjZDM2ZWY1M2I0NDUyM2U2NWU5OGQxOTQ0OTkzZTBmZjE3NDZmNTUzNmMxYTUxMTc0NmYzZTAxZTc1NmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhjYzk0ZjdiZjEyMjBmOTFjNDQxODMzYjYxMGRjZGQ1MGM1N2ViNjc1YzNiYzY0YmY3Y2EyZThlYzkxNzBhYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVjMjAzNzJkZDgxNWJmNjRjNzVlM2ZkZTAxYzhiNmUxYTU2YjQ3OGZlZjg0ZWJhMDJmYTJiODBlMGQ4MzRlNWMifQ" }, { "resource": "repo://TFI/App/MobileSceneModel.swift#L9-L42", "version": "repo-lines-v1:sha256:af2dd6be5697ab15086a02db87d9de341a4f444011bb354a124cf50d39eb909a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdlMzBiMWNmYzNlMDdhNWQzNDFjZTY5NjdjZDYzMTI4MWQzYzQwYmYzMWQzNTI1MTM2M2MyNzYxN2NiZmIwMzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY0Zjc3ZWUyMTVmMDIxOWU5NjFjNzM3NGFhNTQ2MzA4ZjNjMGVlZDI3MzQzZmEyYjY0Y2EwYjZhZmVkMmQwYzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNkYTMxMmZlMTU1NzA0MzkzMzRiYzkwMGRiNDQyOTUzNjJmMzg5ZjVjNmVkOGU0MTViODIyYzBmN2YwNDJjNDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQwMmY3OGRkOTFmY2NiMDNkNTg4MDU0YTRkNjhkMmRlYTU2NTgzODI3ODU5ODNlMTEwOGZiN2IzOTY5MTdkMWUifQ" }, { - "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L243-L306", - "version": "repo-lines-v1:sha256:49f1bce4a65005b1a5ad1f81083f3d10026f66f89e587a688a8625139aadec14:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMzhmZDI5OTQ0ZGQyMDFhMDBlYWM2MjRhZGRiMzM1Yjg0ZTcwOWQ3YmViZmUyMTZjNTE2MTE2ZGQ3NmJiMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L249-L311", + "version": "repo-lines-v1:sha256:459e342245648f5c1c01e2c7ee708b89b10efb36155c2f7ac11950dd0ff38c38:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMzhmZDI5OTQ0ZGQyMDFhMDBlYWM2MjRhZGRiMzM1Yjg0ZTcwOWQ3YmViZmUyMTZjNTE2MTE2ZGQ3NmJiMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] }, @@ -137,16 +137,16 @@ "statement": "Mobile Assistant shares application-owned execution/store services while scene libraries select conversations; focus restoration after replacement revalidates conversation identity, and the view composes an incremental native transcript with a measured scrolling interaction region.", "evidence": [ { - "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L243-L306", - "version": "repo-lines-v1:sha256:49f1bce4a65005b1a5ad1f81083f3d10026f66f89e587a688a8625139aadec14:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMzhmZDI5OTQ0ZGQyMDFhMDBlYWM2MjRhZGRiMzM1Yjg0ZTcwOWQ3YmViZmUyMTZjNTE2MTE2ZGQ3NmJiMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L249-L311", + "version": "repo-lines-v1:sha256:459e342245648f5c1c01e2c7ee708b89b10efb36155c2f7ac11950dd0ff38c38:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMzhmZDI5OTQ0ZGQyMDFhMDBlYWM2MjRhZGRiMzM1Yjg0ZTcwOWQ3YmViZmUyMTZjNTE2MTE2ZGQ3NmJiMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L9-L39", - "version": "repo-lines-v1:sha256:f6690fa9486d7a196714cdb9b22349dae117807c62649feffd2126f03dba2f3f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4ZDUwMzc5NDJmNThhMWQwZWRlN2QxNDYwNmMwZTJlOTE2OGUwMzRkNDFiYTU2ZThkMTA5YTRlNzY5NTgxZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwYWZkMDliMDljYTY2Y2Y0YmIyN2QwZWMxOWQ2ODUyMzA1OTYxNWE4OGQ2MzRjYTIzYjMxYjdmYmM1OGY5MDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ4NjlhNmI3MWE1OTA2NzY4MWZkOTE5ZWJlNThkZmMzODZkMzI3YTRhNmJlMDFiMGI0YWYwYmFkMmViYWY3MzcifQ" + "version": "repo-lines-v1:sha256:c084579028b11a36904131ea1df17faca1a3f7151461ae2a7095176f3d10287a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4ZDUwMzc5NDJmNThhMWQwZWRlN2QxNDYwNmMwZTJlOTE2OGUwMzRkNDFiYTU2ZThkMTA5YTRlNzY5NTgxZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2NzdhNTkwZjI0OGRmYWU0MTYzMzc3OGFjZmM4MjQ3MTAwZjIxNWE2NGQ3ZmNjNzRmOTg1MDQ4ZWQ1ZTNhNGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwYWZkMDliMDljYTY2Y2Y0YmIyN2QwZWMxOWQ2ODUyMzA1OTYxNWE4OGQ2MzRjYTIzYjMxYjdmYmM1OGY5MDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiYTc0NzkxZjM3YWU3NzE5NmU5M2UyMDBkYTQ5MTNlMzQ0NWVjMDNiOTYxMjMyMjdmMzQ4MTNjODNhOTZjYjcifQ" }, { - "resource": "repo://TFI/Features/AI/MobileAssistantConversation.swift#L447-L544", - "version": "repo-lines-v1:sha256:faa0f3af974bcc94ad86beb910bbf1472ed30b41c38daeff0ce7879220b8ba03:eyJzZWxlY3RlZExpbmVDb3VudCI6OTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlNjUyYzFiYmFkYjBkNmExNmM5MzI1MmQyM2FjMzljNjlhOTE2ZGFlN2UxZWNhZTM4M2I1ODIxMGY5OGFhZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiYjRiNzI3ZmE2ZGQ3OWEzNzlhMzIxMjU1Zjg3Y2MwODFmODg5Nzk2MDc1ZDQ4YzM0NDhhMjcxNzQzMmFjYTAifQ" + "resource": "repo://TFI/Features/AI/MobileAssistantConversation.swift#L447-L512", + "version": "repo-lines-v1:sha256:1b6561c03d144fe9e01c25caeda779e06bb6998f61fbd8af3f8f44541832f7e6:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlNjUyYzFiYmFkYjBkNmExNmM5MzI1MmQyM2FjMzljNjlhOTE2ZGFlN2UxZWNhZTM4M2I1ODIxMGY5OGFhZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM5MThlNjU1YmQ2ZjQ1MTc5MzZhYjg3YWI1ZjM1MjM2NzkwZTdlMTdhZDI5MDJkZDczYzUzZDhhOGZmOTZhOTkifQ" }, { "resource": "repo://TFI/Features/Assistant/MobileAssistantView.swift#L201-L235", @@ -263,6 +263,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 09535e72..16c6dfcd 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:cd736475053bed4751ff7b4f06ee0433c8d3ac3e74f3866d585bc958dd6b25d5", + "pageVersion": "sha256:4139c19be34c8fc47c74c1b05a07851c8e18bdfafe33237f7507fbc542e4b360", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -44,7 +44,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -101,8 +101,8 @@ "statement": "Cross-feature responsibility is split among Application policy, concrete shared adapters, Persistence stores and native owners: remote files use a filesystem port plus a transfer coordinator; Assistant execution uses an injected tool-registry factory; Vault sync uses persistence journals; speech uses an injected native audio input.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L6-L44", - "version": "repo-lines-v1:sha256:acf89cdfba502ef9c32f962dc43493dd515971c9c8b203533c43044e3ebca670:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L85-L101", + "version": "repo-lines-v1:sha256:577dce93a8dc638405f6c56f3765a8c249566b31e706d49b62be67594175b5e8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmM2UzN2Y2NzljZDY2NzAyYjcxZjNmOThkN2IyYjYyZTAwMGEzM2FkYjQ4OTE1NDdjNjZhOTJkNDEyNGIxNzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4OTM5MWFkMjEyZWVlODE0ZWE3ODFhNWJkOTlkM2YxZDcwY2JiODhlZmE0OTc0ZGZmMmJlZGE5NzVhOWNjODYifQ" }, { "resource": "repo://Frameworks/TermindApplication/Transfers/RemoteFileSystem.swift#L4-L14", @@ -151,6 +151,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index 156bf01c..def4e55e 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:daaecfb65b33663c617a5627c7c65fc6e45cd05b85017d53e6a48c818a1e15b5", + "pageVersion": "sha256:f1dc10b8e646cf711a546c2b764e9d898ad017b4ef0f3bdc7f1932e54e746ae5", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -20,7 +20,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -112,7 +112,7 @@ }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift", - "version": "repo-file-v1:sha256:3d9babddce2d3da3e2f1787094a40692a2b7a4a2d2c22eed61f5372b4221b205" + "version": "repo-file-v1:sha256:d1b592e226f8467b8070139529d17882dcf327a3fe643392025f1ae1205d22dc" }, { "resource": "repo://TFM/Features/Shell/WindowRoute.swift", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index d38210aa..0949b4df 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:f6b1eae907640a27121b4c2f41cc040490003e2944d38858bc7a4719581bd0b3", + "pageVersion": "sha256:0e61cbe711eed5c27ef6466c5ce521193641c61d25abf03298b9098834235f2f", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:ba89018766f45b430033efc2969c0307ba30c0d298bd8b55a51e17a08915c767" + "version": "repo-file-v1:sha256:8efd1aa2da741e633993416bf4f88eff4df978ee3768a13788a1ac0419bdf837" }, { "resource": "repo://TFM/App/RootView.swift", @@ -48,7 +48,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -224,7 +224,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -257,6 +257,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/architecture/shared-module-directories.json b/openwiki/.claims/architecture/shared-module-directories.json index 9d33af90..12ea54de 100644 --- a/openwiki/.claims/architecture/shared-module-directories.json +++ b/openwiki/.claims/architecture/shared-module-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:42206a596cfd036cd1cf7bd7bc2f0e8ec5f96a4577648a864cebd3f8db5d0a3f", + "pageVersion": "sha256:afb1bab3e4c91081f597fea301a3472ef878abc9493b4e6bcc9014ba1aabe4e9", "claims": [ { "id": "claim_6e30bc8cde134c23815d274543d0786c", @@ -59,16 +59,16 @@ "statement": "Application Agent coordinates conversation execution through an injected registry and AgentKit services; Remote Infrastructure Agent registers host resolution, SSH/SFTP, metrics and scratch-transfer capabilities alongside runtime-owned tools.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L6-L44", - "version": "repo-lines-v1:sha256:acf89cdfba502ef9c32f962dc43493dd515971c9c8b203533c43044e3ebca670:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3YzgyYWM5Yzk2MGJhNjIyNDBlYWZkZDE0ZTY2NTI1NjQ2MTMzOTE5NjU2MzNiODhlMWIwNjk5MjY2MGI2NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFhZDFiYmYwZjdkYWQyYTAyNGQxMzBmZjQzOWQ5M2Y0ODZhZDQwMmQ5Y2U5N2Q3ZDVhMDZhYTUxNjFlMmIyZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4MjgyYmRhZjE5OTYzODdkODg5MzczMjM0Njc1ZjQ2YTI5N2UxN2RhZjAxMGJjMWI5OGMzNjA0Zjk5OGEwNDMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L85-L101", + "version": "repo-lines-v1:sha256:577dce93a8dc638405f6c56f3765a8c249566b31e706d49b62be67594175b5e8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmM2UzN2Y2NzljZDY2NzAyYjcxZjNmOThkN2IyYjYyZTAwMGEzM2FkYjQ4OTE1NDdjNjZhOTJkNDEyNGIxNzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4OTM5MWFkMjEyZWVlODE0ZWE3ODFhNWJkOTlkM2YxZDcwY2JiODhlZmE0OTc0ZGZmMmJlZGE5NzVhOWNjODYifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L68", - "version": "repo-lines-v1:sha256:8ec2078dc1ab7a5f6087ab497ecb18aa1b47942cd6213e8f611f6ee6b14c1d26:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" + "version": "repo-lines-v1:sha256:7dcbf34ac1f3456f7329f03d3b3806e5d477bb215f9c9ec4061fa764fdea5b50:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0YjdlYTY2ZmJmMGQyOWM5MTFmYzIzMDY4MTRjNWRjNDQ5ZTY2NDlmM2FkYWE0ZjRlYjI5NWJmZDk2MzM3MzcifQ" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L92-L162", - "version": "repo-lines-v1:sha256:09fca2c0116fdebff0ef1b1a8a2dca8ab7fb0923bc8c7d9bab717fd87297bee5:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4YWJiMjc3OTAyNGI2NzIxNWQ5ODVjYzIwODdjZDRmMzU2YWFiZTJiNWNmMTNmNDA2ZDA3YWFhMjE1ZTBlZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkzNWVhY2VkYTg4Yzg0MTA1MTk1ZjI1ODQ0MGQwNTYzMjMwNjM1YWJlZDA2YjhlOTM2NzYwZDM1MDk5OGU5MTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L81-L147", + "version": "repo-lines-v1:sha256:1b353b5d91d4db4eac25ad310d824af21d84f7525bbb1dfe291e956a7c1306f0:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJmMWY5ZGIzYzBkNGZjYzA4YjgxOWIxZmNkNzE5YjMzYzc5ZDljZGVjNDdkMWJhOWYxZGRhMmRmM2MwOWZhZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkZDI0NDU4MmI2ZmIwM2QyM2I4NDE1YWJlY2QzNDZiMGM5ZjQ2NmI2Zjc1MmIwNjgxODljZTdlOWEyZGMzNTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiNzJhMmNhNDdjMTY2YWY3M2MzZDdiZjQxMzQwYTdjMmVjYWE1ZTYwMWEzYTlhOTFiMjZhNTUzMGYwM2NkZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjNjEzNmJiYzlhOWUzYjVmMDNmYjg1ZTNhYjgzMGIwNWY4N2E4NTdiNTc2ZjdiY2JhN2NjY2ExZTAwOWQwZjEifQ" } ] }, @@ -291,6 +291,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index f6181567..c5ff0753 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:47fa6bbaa4a43b7bac039520e0bd6d4cf3f003b8969821fecaebe1cd9bdeac6b", + "pageVersion": "sha256:1dba6f651d9536e5ea63b9bd551b7e20cc973f0816613aec3593304b42c8cbfb", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -8,21 +8,21 @@ "evidence": [ { "resource": "repo://AGENTS.md#L16-L18", - "version": "repo-lines-v1:sha256:79d9222951afd1cde2967f4987b4aa7b4347f1510f1fc4d1141010690b9c8bff:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGI2ZWFiNGFjNWIwNmRiMjY5NmQ0ZDUzZTJlN2YwZDE3MzhmMWI3NTAyY2UyZWY1YjcwMzIzZjg3NjdjYTllNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRkZTk1MTMwNGUxYWVlNjRiM2IyYmY3NWZkMzI2YjAyZGM0OTE5YjlhODM4MGZjZTY1YTAyY2VjOTE3MzdjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWM1NjRmZjMwNzY2N2QwZTgzMGJkYjkxYmY1MDYwY2FlODNhMjc2NzRkNTU5NWNiYTU0ZGMwNmQzNmE2NWMzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWYxZjNmMTg5YTBiZTIyMGIzYjA0YmIwYzc0MTI2OGE5MzE4OGU1YzY0OTMzZGUyNzU0YzI3ZTZkYzRmZTE0NSJ9" + "version": "repo-lines-v1:sha256:79d9222951afd1cde2967f4987b4aa7b4347f1510f1fc4d1141010690b9c8bff:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGI2ZWFiNGFjNWIwNmRiMjY5NmQ0ZDUzZTJlN2YwZDE3MzhmMWI3NTAyY2UyZWY1YjcwMzIzZjg3NjdjYTllNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRkZTk1MTMwNGUxYWVlNjRiM2IyYmY3NWZkMzI2YjAyZGM0OTE5YjlhODM4MGZjZTY1YTAyY2VjOTE3MzdjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWM1NjRmZjMwNzY2N2QwZTgzMGJkYjkxYmY1MDYwY2FlODNhMjc2NzRkNTU5NWNiYTU0ZGMwNmQzNmE2NWMzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTVkOTdmMmUwNzM5MTAzZjc1MTA1MjljNjA1Zjg0OTIzMTdkNDJmNDA5NzEwNGFmMmRkMjBiOGUyNmZhMmNmNyJ9" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L201-L328", - "version": "repo-lines-v1:sha256:2744f8cd1de07ff0f99433383a73d1424c77a6dee3edce41f125c652bfe7e1a7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI5LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2NTVlMmVkYTc3Yzc3ODBiNjg2ODJiMmZiYTExNGQ4YWE5YmIwOTBkMDYzN2ZmY2RmMzgwNWViODkzNjZlYWZkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1OTA5YTUzYTYxOGQwMjhlMWQzMDAwYzBhN2FiY2VlY2JlNWZiMzdmNGU1MTFlZmU0NjY0M2ZhZDZlNjY4NzJmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YmE4NGEwNzc5OGYyNzk4NjliNDM1ZTdiZGU2MmZkYzg2ZDRhZjkzNzJkYTcyMmY5NWM2MjY5YTJhYjkwM2U3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNGJkNzA2YzdkNTlkMjVmM2MxNTUzNDM5MDAxZmYyOWQ2ZGEzZjVlNjQ1YWIyZTE5ZjM2N2MwZmM3MDQ0MGU0In0" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L385-L501", + "version": "repo-lines-v1:sha256:261028a999073e8a0cb935c25c5226d7e3ccbb616cdf003ce8ba9d54ffb92c31:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJlYzFiOGExODllNmEyYWM4ZTM1MmJlMDMxYmIzYWY0NTI2MjM2NDU0MDI2NzIwODY5NzZkNWVhNTQyYjk5MDQ0IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIyZDVmMTIyNGM2ZDQyZTY1YWVjMzZmNmRjYjBiMTc2Y2FkMGJjYjU0M2U1ZjM3MDMzOGQ4ZjYzZjk0ZGEwZWM3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYzE1ZWYyMzVmYjkyZDFkYmQwNTRjZTlmMTFiZTRmMzA0NTVkYmRjMmU3MzkwNGY0MDYzYzA2M2M5ZjM5MzI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlZWMwMmIwMGE3NjI5NDE0ODA2ZDEyMjI3NjliODBlMGY2YjFjNTFmZmE0MGM1OWMxMTI5YjJmNDE4Y2EzZWM2In0" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", - "version": "repo-lines-v1:sha256:5e034e35b4291fbd3ada9dbf3613ec9e3f6865bf8b3945545efb078073d866f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YjBhZGQ3NDg1ZGY0OTA5MTkwNzI5ZGEzYTVkZWY5MjVkZmFkMTQ2YTJkMTA1MDk5YmFjZTJiYmJhM2ViMDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTBhODk2YTNjYjAzNmM0NDZiNDgyMTA3MDA4ZDZkOTZmNTdkNzA2MGRiNDk4ZjQ5YTVhNjhkNDI1ZWI4NGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlYzQ4ZTU4YzVlMTBhZWJkMDdkOGQ5OTljOWJhYjg0MDBlNWRlMDA3NzE4YWQwNzA5OTkxOGRjOGMxMDg3ZjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + "version": "repo-lines-v1:sha256:1be121a99f843d9d127884deb2bf2d9aeb076f899bc7dd89017d85db3359e991:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdkOGI0OTE4MTMwMjdmMDQxMDIyZDAyZWNmZjI4YzIxYjYyZWMwMjZlYzE0MmJiZDE2MTU5MTYzYmY0YjFjYmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhOWIyYjIzNmEwNjJlM2M5NDU0NDlhNjdjMGE4ZWZiNGU4NGM4Y2UwNDI0Y2YzNzZjZmY4MWE1NGRiMjIwYjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" } ] }, { "id": "claim_28c4b1e064e94a798fe0220965a3951e", - "statement": "A submitted Termind turn records the user prompt and its context snapshot, resolves the catalog and run mode, constructs a fresh AgentRuntime, and starts it through ConversationModel; the runtime persists a running snapshot before requesting the model and persists transcript snapshots around each tool-result round.", + "statement": "An accepted Termind submission synchronously captures composed content, context, model/provider/credential, reasoning, permissions, search, skills/registry, Guardian and preparation settings before compaction or memory awaits; a fresh runtime consumes that capture and persists a running snapshot before model work and transcript snapshots around tool-result rounds.", "evidence": [ { "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L213-L274", @@ -33,12 +33,16 @@ "version": "repo-lines-v1:sha256:fd811671f5fdc47d264e5c3d26b3fb815385a39f5747a7d460403896bb8c0081:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlMmZjMTRlMmFjYmU5MjgxYmI5OTRmZjQ1NGUyNDAyMTE3MDQ1OWI5ODJjM2ExNTlkOGJlODM5NWI2NmJkNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkN2RhMWFmYjFkMTc3MmUwNTY5YmU0MWZkOGQ0YTA4OTczNWNlMzczNDExNGU3OWZkYjJmMGJiMDdmN2ZmODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGI3YjgwZDA1MzcwODYxOGIzZTUzNTJmYWM1YWE3NTUxN2M5OGE4OWVjNGNkYzU0MWUzZjE4YTgxMjY1ZGEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L286-L351", - "version": "repo-lines-v1:sha256:d7a10bdad07278a40279af680e0f3630aa1febdcb7851cb87e6a48a0509e7759:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI4YjA1OGQwZDE5MTQ0NzUxZmFhNDYzZmM5OTk3NjQyYjQ0OGNjMDVhZmFjZmMxYzY3YTg5NjhhZTA4MTgzMGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQzOThhZDkxNzk1ZTc5MjQwYjEzY2Q0MTU4NDY3NjI3Y2U3OGRjY2FiNzdkNmM3ZWRhZjczMTBlMTZhYjU3ODgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlYThlMTdlZjdhMzg5MDQ1NjczYTQyYWZkOGFlYWFiMjk5OTg3NGIwYjdmNmEyMGY1YzEyNDc2OTU2ZGI1NzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmNjk4YzQ5MTQ3ZGQzNjlmZDIwOTBmYzRkYmI2MGY1ODhiMzU3ODY1MmFjZTVkOWQwYjg2NzU0MjE0YWY3OTEifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L284-L304", + "version": "repo-lines-v1:sha256:de29dc974cbd2c72ac1a59847fd02fc322550b4b15d0016d4ac1a76e768f07f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZhNWZmOTFkNGU1OWMxMDhlZWFkYjMzYzhiYzk3NTk1NGM4MjM1MDQ2YTcxNDRhNDFmMTBlMmI5NzgyYTc2ZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1MGE4MTlhNjgyMGMwMzcxODE2OWM0YjllYmQ3ZThkODI1NDlmMTgzZDE2ZjkxYjM4YWY5MGZkNTM4ZmQ5YTkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L46-L95", - "version": "repo-lines-v1:sha256:42d8c99afe5dceebcaaabd0abd43c95d68ee3d1bacc4f900e60db34dfc667958:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzNTMzM2MzZTA4M2NlNjk4MjRiZTAyZTMyZWFlMzcxNjc0ZDhmOGZhZDMyYzNlOGU1MDViMmJmNTE0M2VmMTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzYWVlMmNjZDYwMjA1YzRmNTY0NWM0NjE3MzZlZTZmYjkyZTU1ZGQzZjE2MmQ2NGQ0Y2UzOGIwY2I1NjExZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiNWI5MWZmNzRmYmZjZWY0OGZjOTZhMzNmMGFlNzk2NzkzODE3N2U1YTJlN2Q1OWE1YTg0MWJiZDU5OTY0NWMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L474-L501", + "version": "repo-lines-v1:sha256:810bdc77e6681b23d6f9936b515073efa39a5f809eefcc66b0f6c79e571bec35:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MzNjZDg0MjZlZTc3ZDg3ZTk4YTlkYjY4YmMyNDVhYmEwYjYxYzY4NGNkZjJiZDhlNTJlY2ViYTA4YTliNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNWYxMjI0YzZkNDJlNjVhZWMzNmY2ZGNiMGIxNzZjYWQwYmNiNTQzZTVmMzcwMzM4ZDhmNjNmOTRkYTBlYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzODdlODkwYzgzZWE5ZTZkY2U1MzZhMTBhM2Q5MjBiODQzNTk3ZDMwMTQ4YjY1ZjYyYzYyMzk2OGMyM2RhYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYzAyYjAwYTc2Mjk0MTQ4MDZkMTIyMjc2OWI4MGUwZjZiMWM1MWZmYTQwYzU5YzExMjliMmY0MThjYTNlYzYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L630", + "version": "repo-lines-v1:sha256:b1882f48ba2a0c4f0faaa010c3ad73fbbfed243a2f0f51c0d883ed8058e5a3d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" } ] }, @@ -47,12 +51,12 @@ "statement": "ConversationModel keeps a display projection separate from the authoritative transcript because replaying display messages would lose tool calls and results; it owns the active runtime and consumes its ordered event stream.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1855-L1887", - "version": "repo-lines-v1:sha256:fe3484016f99762359b65be4ea1f12a76f0bcd358bb9df3739219263b7201002:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4MzU3NDc3MTIzNGY2N2ExYzRjOWU1MDEyZjcwMzUxYTUxNWI4ZmVmMTcwYTRkYjQyM2M1YmFhNDQxOTJkNzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAxMWE4MTg2YjMyNGI2MmY1NzZkYjA1MDc5YWJlYzNlYjU3YjU0YmVmYjgxMzc4NTgyZmY1ODg1NGUwZGFiM2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjYjFmYTljNzA3NDFlNzgzNWY2ZGY3YWY1NmQzYWUyNjUyYmNiMjYzZTFlOTFkZjE5MjYyNmM2ZWU2MWE0ODAifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1935", + "version": "repo-lines-v1:sha256:29086746228df689c5ee73c74a8f470141cf46d41005fdc2804d4ee9d81f9759:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlY2VjZjZjNTcwM2QwMzM4NThjMTkzNjQyMTc5NGFhNmExYzY1MjAyMWM1ZjQzNTI4YTk1MTlkMDE3NzliYzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjU4ZTRmZmFjNDQ4ZTc3NTVkYTFlNTM3ZjU5ODgxMWFjYzY1MTAyODBjMmUyMjA1YTk5M2JhNjI2MWEwOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L785-L803", - "version": "repo-lines-v1:sha256:fbfbc47722c6c98fb8ef1707713ef10376782045b660ce93864fef69a4b22c85:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljZjIxYjFiNzJjNDQxMzg5ZmYyMzE1NjcxOTNiOTgyNWY3ZjQ0YWY1MTI4YmVjNjlkNjVjNmUzOGI5NWQ0YTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlZDJhZGU3YmY0OGRkZWUwNGVjOWIxNTIxMjZhODBkNDBlYmRjYjAyNTc3MTE1ZDg1OTY2NDAwMjI1YzBkNWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdhZjhmZjY1NDM5MTMxODVjM2UyNWNhNWQ5M2U3NjhlY2NjZTI4ZmRmNzZmN2Q1NTA2ZjRkMmViNzJiMzYwMDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyNTczNGI5ZTA2ZjkwMGU3ZWY0NTY2ZWYzMjQzZmM2OWYyYTc1MTY4ZDE4OWFjNDJkNjJlM2Y5NDg4OTM5NmYifQ" + "version": "repo-lines-v1:sha256:6f128436b4d34e1d8dff80a345e8abc178fb1737285d7cbee881bece263cf02a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljZjIxYjFiNzJjNDQxMzg5ZmYyMzE1NjcxOTNiOTgyNWY3ZjQ0YWY1MTI4YmVjNjlkNjVjNmUzOGI5NWQ0YTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA1ZjhjNjMxNWJmY2Y3MzE1OTUzYTJkYTBkZDM0ODVhNTkwMjk0OTExMzVlMGViZmFjYmI0MTkxZmE1OWVjYWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdhZjhmZjY1NDM5MTMxODVjM2UyNWNhNWQ5M2U3NjhlY2NjZTI4ZmRmNzZmN2Q1NTA2ZjRkMmViNzJiMzYwMDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdhZjMzNTQyNTM4N2U2ZjRiZTIxOTVkODMxM2YxMzhjMTM4MDk2YmYzOWIwNzE0Y2NiZDA2ZWI3NzhmZGFkOWUifQ" } ] }, @@ -79,8 +83,12 @@ "statement": "Guardian review is used only for approve-for-me runs when a configured reviewer credential is available; unavailable, timed-out, malformed, or otherwise failed review denies execution rather than allowing it.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L241-L281", - "version": "repo-lines-v1:sha256:7898f2adaa92869c8bd82511e787865dac90da5721cc4fc9ab2721c4f3b0044b:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI0ZDFmYTZhZWEyMmE1YzExY2JkODNlN2Q5NmZlZTQxZmRiYWFhNGZkMDZkODNhYTMyYTk4N2JmNDFhZmY4NTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRjNGM2YzQwNjcxOGIzOWFjYzE3Zjk5ZGE2NDhjZWJkNmRmODZiZWUwMmFkZjAzNDUxZTQ5YTRkZDExYmIwZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYThlMTdlZjdhMzg5MDQ1NjczYTQyYWZkOGFlYWFiMjk5OTg3NGIwYjdmNmEyMGY1YzEyNDc2OTU2ZGI1NzUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L408-L443", + "version": "repo-lines-v1:sha256:be68979a9e136d3a8f883e2cb2def49dc9335df3a94bcaf022b041570ab2158a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI0ZDFmYTZhZWEyMmE1YzExY2JkODNlN2Q5NmZlZTQxZmRiYWFhNGZkMDZkODNhYTMyYTk4N2JmNDFhZmY4NTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRjNGM2YzQwNjcxOGIzOWFjYzE3Zjk5ZGE2NDhjZWJkNmRmODZiZWUwMmFkZjAzNDUxZTQ5YTRkZDExYmIwZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYThlMTdlZjdhMzg5MDQ1NjczYTQyYWZkOGFlYWFiMjk5OTg3NGIwYjdmNmEyMGY1YzEyNDc2OTU2ZGI1NzUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L542-L550", + "version": "repo-lines-v1:sha256:4408fc03e436bfce7a4d7222263680c2120979d6fb3de14470044c9dadf3ca72:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGEwZmNlYjEzNTZjZTA4YTNjNzAwOTgwYTcyZjE1M2E4NmRjMjI0MTM0NjEyY2JjNDQyMzM1Y2YzZWU4OGI0NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDQyODcxNmM5MWY4MTQyMDI2ZmQzNjY4N2Y5MjY1ZTk0ZTgxZjYwNDc4ZTFmOGRjNTI1MTU5MTdmYWEwNjVhMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTcxNGVkZGVhMGYyOTAxNTlhMGRhNTdiNjU5NDE4ZjYwNTBhODY0YzQ3MjljYjU1MzNiNjFjZWFhMjQ2MmNmMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmYyNDAxNTU5OWFkNjZkZTI1YzVlYWZlNTNiN2RiYWNmYTUyMWMwMGRhZDEwNjliNDgzNTViYjk4YWY1NTRlNiJ9" }, { "resource": "repo://TFMTests/AgentRuntimeTests.swift#L709-L774", @@ -108,7 +116,7 @@ }, { "id": "claim_10f2f03c85434fd3b22cbe8e51895b0c", - "statement": "Steering messages are queued while a run is active and only become transcript history at the next model boundary; cancellation stops the runtime, cancels its task, discards run secrets, and returns undeliverable steering to the composer.", + "statement": "Steering messages queue for an active runtime and become transcript history at its next model boundary; stopping requests runtime/task cancellation and secret disposal and returns undeliverable steering to the composer.", "evidence": [ { "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L139-L165", @@ -119,12 +127,12 @@ "version": "repo-lines-v1:sha256:dd7262281b0133a9a56570381c1b52df5dededbbb751d9e81871e8fd08bb10b4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjMjU4YjdiNGY1YjRhZjg4NjE4ZDI4NGJkODg1Y2VmYzNiMmQxOWRhMTk5OTlmN2ExNDI0YmQxMDliMjNjZjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmMGE5Mjg5N2Q0YzU4ODhhNzJmMDY5NDJjNmZkYmViM2NiNDAyMzBlY2U1ZmZjMThiNWJhYzU2ZTM0Mjk1YzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWQ2MDRlNmM4MjFhMGNkZDdjNjhjMGQ5MjM2ZGYzNGQzYWY1NjM5ZDI2YzAxNDRiMDQ5MDQzZTgxMjBmZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1N2YwZDhiZTM1MGE0NGY1YjRhYjlmZmE1MDUyMjc5NTBjODk3YmI4MGVhZjI3NzNkMTAxODNkZTljNjE1ZWQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1995", - "version": "repo-lines-v1:sha256:6cda8b5a391dcf8703619fb4c97e81ac032fb3dc591c2369a20d2b9e8ba28f97:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY0NDkzM2JlYmY3OGIxOGFkNjc4Mjk0MzM1NjdjYzk4MWNlYjdiM2FlOGIyZTRlNjA5NjRlOWYwNDQ4MjJmNTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyZjFlOGM4MDY5ZDRmY2Q0OTQyZDIyYzQ1MGZmZmJmYjJmODE4NjAxYjE5ZDE3Njk0YWM0M2FjNDVmMTc0YTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5NjI4ZWNhNWJkNzUyYzJmOThkZGFkY2VjOTU5YWNkNmFiMWZhMTA1MmVhYTY3MzZiZWFjODRmNDc4NjEwMDcifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2040", + "version": "repo-lines-v1:sha256:f0190ce17ccd16ba2db4de8b4ee133516fd1cbf030f89f914b0643ab0477deb4:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmMThmMGI1OGIyNjAwNzUwYzBkMjdlZDlmMDA2ZGRhNjZiMjBmYWM4NDg1NzFlYTVlODUwOWFlZDQ3NWJmNjkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2507-L2534", - "version": "repo-lines-v1:sha256:fa1732f1c428eebb9fe5c6720dee5116ca6a8436ed3f480122180630d6cbb740:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5MGQ2NzM0OTMwMjgxMjBkMGRhN2IyZGI0MmRmMjU0NGU3YjI0YTNjNGY0MjVhZjkzMDI4NGI0ZGE2NTg2OGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjA1NGIyODExNGMxNmJlMWM4MmQ5NDFjNzc1OGM1NzFhNzcxNzBkYzM0ZTA1ZDUwZDllMDk3NGEyMjRlODQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", + "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" } ] }, @@ -173,8 +181,8 @@ "statement": "Compaction persists a record before the live conversation applies it and replaces only the model-facing projection with a synthetic summary; the original display history remains available after restore.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L473-L482", - "version": "repo-lines-v1:sha256:7c9756ce11f5a802355fdf037aff24ef90f81e71df715249370eef766c0a4ab0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MTZmMWZiZWQwMTYzZTEyMTU4OGUxOTZkMjA3Zjk1ZWYwNTFmMGZiMDEwN2YyNDI4OTA1MzAyOTEyYTVhNmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVhZjdjMDM3Nzg0ZTJjNDQ1NmE3Y2MwZWUwNTFiZGU2ZjA0YjZjNmU0OGJiYzA1Y2Y1OWQ0Y2Q5NGE0OGI0NjEifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L881-L890", + "version": "repo-lines-v1:sha256:7c9756ce11f5a802355fdf037aff24ef90f81e71df715249370eef766c0a4ab0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MTZmMWZiZWQwMTYzZTEyMTU4OGUxOTZkMjA3Zjk1ZWYwNTFmMGZiMDEwN2YyNDI4OTA1MzAyOTEyYTVhNmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlMGJhYWZiMDE3NmFmY2M1Yzg3NzI0MTkzYmY4ZmM2NWU4MzgwNGUyYWUwYzk5YjZkODViZjViMmY2ZWFmODEifQ" }, { "resource": "repo://TFMTests/AgentPersistenceTests.swift#L961-L1010", @@ -184,11 +192,15 @@ }, { "id": "claim_5adad6caaaf1482baf6e5d4afcebc008", - "statement": "Skills are app-managed packages whose catalog is fixed per run; only SKILL.md is procedural instruction, and a skill does not authorize commands, writes, or transfers outside the normal approval policy.", + "statement": "Skills are app-managed packages whose enabled catalog and inventory are captured at submission before preparation; only SKILL.md is procedural instruction, and a skill does not authorize commands, writes, or transfers outside the normal approval policy.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L218-L234", - "version": "repo-lines-v1:sha256:65ddf8be8b7a5adeab788e5df286c4169da771dbfd9f2a9919a9ee789c4ee562:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMDg3ZmZlODE0NTk2NGYwZWIwOTlhM2FiMzQxOTRjMzlkM2E5YTY2YTM2NWJhYTQ3OWQ0OGIyOWJhY2ZiNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyZGFjYzM0NjMzZGIxZjRlY2ZjYjYzOGI5OTNjYWY1ZWE1MGU3ZThmZTk5MTFjNjBmODBhZjIxNWM4ODk5ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhkZjJlZWYyYmU0YTM1MGU3ZjI3MmVmZjExNDc2MWJlM2I1Y2MyYzMyYmI1NzllZTc3NjIyY2JkN2MxOWIxNDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNjZiMzQxNmZhZmQ5NWY2NjY2OTQ5MzJmMjc5ZmVjNWM4ZTliODY4N2I0NDk2NGM4MTU4Y2UzZDJmYWYxZjcifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L394-L404", + "version": "repo-lines-v1:sha256:3bcf15a3ce14b9ed88dd81ab4acccf0d8910eee98b7e5e350a7647991cea8ba8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4YjYyNmIxMGUzNGMyYmYxMTE1OTg5NGYyYjA1MTU4Y2I0NzVjYjJmODYyMjRiNjg2NjA2Nzk5M2JjNGEyZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5MDJhM2QzZDU4YjYwMmY0YjZlNjhkMTAxMjRjZjE1YThmMTMwOGYyYThlMzdhY2RhNzQ1NDkzOWEyOTllOWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0MDU4ZjhjMjAyYjNmM2Q5YTEzOGY2ZTMxOTAwZjc1YTVlZmM4N2Y3OWM1OGZmMzQ4Mzc2ODJkNzkyMTVkYmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjNGM2YzQwNjcxOGIzOWFjYzE3Zjk5ZGE2NDhjZWJkNmRmODZiZWUwMmFkZjAzNDUxZTQ5YTRkZDExYmIwZjUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L532-L566", + "version": "repo-lines-v1:sha256:1436f7c04f746958de27f6bda7b04d27675582b1bb906c9d464b4d2c9b7494ec:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2ZTY3YmE5OTE0YTMzNWE1NTMwYTQwZWE1NDBjMzc2Y2E0MjM5MGNkZmFmMmVkMzgyYzM3YTFkZmQ3MzcyYWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMjIwYzhiMTI3ODQ1YTBiNDdlOGI4ZDY5OWQ0YTZhZmQ1OTVjNmQwOWFjYjVmODliNzI2N2Y4MmQzMDc2ZTkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI1ZmNhMDUzYmQ4NzkwZTZmOGI0N2ZiZTAzMWZhNzk4OWFhOGNkY2EzYWZiYzI1MTRjOWI5MjkwNWJjOGJjMmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzZjQ3NzRkYmY1NmE0MTNhNmY3ZTUzNWRlYzdhYTU1NjRjZjcxNDIyZTJhMGI4ZjU5YWQ2NWMzYWZlYzE5MTUifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/TermindAgentPolicy.swift#L46-L49", @@ -285,7 +297,7 @@ "statement": "The terminal_suggest schema remains registered across Assistant surfaces, while its execution gate is captured in toolAvailability: TFM Workspace sidebar sends true, TFM standalone and TFI send false; this partial gate does not disable independent SSH/SFTP execution.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L388-L403", + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L794-L809", "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" }, { @@ -293,8 +305,8 @@ "version": "repo-lines-v1:sha256:a43583fa28f32662ba3edc9845073bfd7bfacb29b7c16c21536c56a4fe75a32e:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDQ3NTE0MGUyNmU5YzVhMTcxMWQ2NDQ1YTE4YjdmNGFlMjAyMmY5OTNlZjYyODQwNzU1NDMwMWI5NmUwZGMyMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMmI2ODNiYzA0Njk2MWVmOTJkOWI3Y2JkNmQ5YWNhZjJlOTk5Yzg2Zjg2ZjM1MzM1MTFiNWIyZTNmNjZhYjZmMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTJlNWI4YTJmMzJlNTczMDY2NDkyZTk4ZTUyNjFmZDQ1ZGQxMGYzZGQ1MDk3NjY2MzYwMzQzZjVjMWRmYzZhNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzlhM2ZjZTUzZjAwNjc0MmEyNjdhOThkZTNhZTI5Mjg2MzgzNmUwNzA3YTlmODgyZDllZGFhMzdmNjU5MTU1MiJ9" }, { - "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L134", - "version": "repo-lines-v1:sha256:05f9c81933d87f71280921366aef45ca6b7595c2a37bbc09748f17358ab5a4ad:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiNmZkYjFiZmUzOGYyNzNlMzRkZjZjYzA4YTViMmRlMWNkN2IwM2M0N2FhZDE5Zjg5YjM1ZWU2ZjUwMzdjOTAifQ" + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L137", + "version": "repo-lines-v1:sha256:d884494a35ff53875a54b449f399714b9593269893ee6b39224ad026690f757c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQifQ" }, { "resource": "repo://TFM/Features/AI/ConversationTranscriptView.swift#L103-L110", @@ -302,7 +314,7 @@ }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L201-L227", - "version": "repo-lines-v1:sha256:3cd1bb860992a27cf2aee06e68ee551298ee0464e891894fee9736399936f21f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI3ZjNmZmEwM2YxMTJmOWYzOWMwOWNjYjQ4YmIxYzY4MmUzODBmNmY0YTI2ZmQzOWU2NDJmMmI5ZTllMGJhYjAifQ" + "version": "repo-lines-v1:sha256:1d54119a61269212f65c8230f6bbdbe4878a2feb36bcce0a3e87f802545152a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkY2E2ZmY0MTYzODU2MjUyOTU1NzNiYjQ0ZGRjZGM5ZDZiYmZkOWQ2OGRlYjVkZDRkNjA5OGNjZWRiNTFhNjUifQ" }, { "resource": "repo://TFMTests/AgentBuiltInToolAvailabilityTests.swift#L155-L186", @@ -319,12 +331,12 @@ "version": "repo-lines-v1:sha256:c063ee9ee073185632ec59778a870a9595ae3c988770a96bd2dfee10f2ba5338:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyNTlmOTQwOWQzOTg3ODRhY2EzOWEzMGU1M2VmY2E0MjlkZDdhNzExZWY1NTZhMzhmYTZlNGRlY2VmYzIwNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZjljNDg3Y2RiYjcyYTAyZDg2OWMyNmNmMGM5ZDYwYTdmZDg4ZTBkMjcxYzM1MGM5MTgyNDM4YjYzY2E3YmIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1961-L1981", - "version": "repo-lines-v1:sha256:2f0d3e9bf56ee4865528a8d17aa69083d4c03d3719d26929e40b6b7fe5c80a70:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5MzFhZDdmZGZiNjJkOTVjY2U1NTA2NDE1NmIxYmM1Nzg5ZTU3ZjhiZDc5MDQ4OGM3MzU1ZWM4MDhkZTJlNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg1MjU4NmJjYTA5MDczZjY4Y2EyNDQxMWJiNTUzYTk2MjNiOWEwNTk4ZWNjM2QxNWE0ZmEzOWI2ODNjYzhkYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YjgzYmUzYzE2ZmJiMzE3MTQ1NzRmYjUyZDFhYjU4MWI3YTFiZmI2MTI2ZTI5YmI5ZjkxMWUxMzNiN2QzZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE1N2RkZTZiYTE3NTBhYTU3ZjU2MzdlZTdjM2JiYjU4YzU3NWJjMmU0YTA1ZWQyNGJjMTBmNWMyYTM2YjM0YzYifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2010-L2027", + "version": "repo-lines-v1:sha256:458a3fd6f7bd7ffcce08a4a412975c949076d429ce17f65d44c3c2ccb774bcb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5MzFhZDdmZGZiNjJkOTVjY2U1NTA2NDE1NmIxYmM1Nzg5ZTU3ZjhiZDc5MDQ4OGM3MzU1ZWM4MDhkZTJlNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YjgzYmUzYzE2ZmJiMzE3MTQ1NzRmYjUyZDFhYjU4MWI3YTFiZmI2MTI2ZTI5YmI5ZjkxMWUxMzNiN2QzZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlhMjk1ZTVhYjdlZTNmODg1ZDE5YjdlYzE0ODJlOTI2NjJmZDcxZDdjZWM0MWZlMzUyYzE4MGFlNWRmZjFlZmUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2507-L2534", - "version": "repo-lines-v1:sha256:fa1732f1c428eebb9fe5c6720dee5116ca6a8436ed3f480122180630d6cbb740:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5MGQ2NzM0OTMwMjgxMjBkMGRhN2IyZGI0MmRmMjU0NGU3YjI0YTNjNGY0MjVhZjkzMDI4NGI0ZGE2NTg2OGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjA1NGIyODExNGMxNmJlMWM4MmQ5NDFjNzc1OGM1NzFhNzcxNzBkYzM0ZTA1ZDUwZDllMDk3NGEyMjRlODQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", + "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" } ] }, @@ -355,7 +367,7 @@ "statement": "Historical tool cards resolve persisted presenter IDs through the immutable combined presenter registry or a closed legacy tool-name list; the terminal_suggest context key and presenter identities are independent of surface-registration arguments.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L388-L403", + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L794-L809", "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" }, { @@ -397,10 +409,300 @@ "version": "repo-lines-v1:sha256:ecccf30acbd284a663e31c1aa7f40e224b2264fb038003a81dceaa30c6449866:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjZmJkYmI1MDE2YWM3ZGVlYTU2ZTg5MGFlOTNhZDc3MzY1Y2I0ZDhhMGQ4NjhiMmU2YzliZTNjYzExYzQ2NGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3M2Y1YTY5MGEwZjAwODY0Y2RmNDU3MjY3OTAxZjNkNzg5ZmRjYzgwNzQ5NzlmNWUzMDQ2ODVlMjFiMzNlNTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE0ODcxYWY5NDg1Yzg3ZThjMGM0ZGNhN2IyYTI3MThiOWY5YTJmN2QxM2RlYWM1Y2MyYTY5YzgyNWVhY2ViZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" } ] + }, + { + "id": "claim_032fd183864d488f9d67331798a1d1b3", + "statement": "Desktop sidebar, standalone and mobile first sends use controller capture before library promotion; refused sends do not promote or consume drafts, while accepted composer/text sends fold attachments and armed skills once and direct text sends preserve unrelated draft text.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L255-L322", + "version": "repo-lines-v1:sha256:2889ab3b98df5cb7ad3761be5369f5ce30608dd0545a113ba32e762084fcf0f4:eyJzZWxlY3RlZExpbmVDb3VudCI6NjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5N2Y5MTA0Y2I3MTFhZmVjOWQwNDhkNmU3MDg5ZThjMWQxMjMzNGI3ZjRiMDY4NDMzYmM2OTFkNmZlNTFiYjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjczY2YyYmQ5MTEwMmVhZTlkOTJiZjA2MzA3M2UyMjA1ZmY2MDE1MTNjNTU0MTM1OWIxMDkzOTUxMTAyMTdlMmEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L103-L183", + "version": "repo-lines-v1:sha256:178f860b9b18486bb117281733065309aea6c20a833151433972af61dfb4856a:eyJzZWxlY3RlZExpbmVDb3VudCI6ODEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzYWVlMmNjZDYwMjA1YzRmNTY0NWM0NjE3MzZlZTZmYjkyZTU1ZGQzZjE2MmQ2NGQ0Y2UzOGIwY2I1NjExZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1OTNiMzkxMWVkYTE0NWU1MTk0NDFkMzI3ZTc2NWU4OTk5YmIzYTA1NjViMTI4NzRmOWM2MGRkODNjYTBkZjIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L284-L304", + "version": "repo-lines-v1:sha256:de29dc974cbd2c72ac1a59847fd02fc322550b4b15d0016d4ac1a76e768f07f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZhNWZmOTFkNGU1OWMxMDhlZWFkYjMzYzhiYzk3NTk1NGM4MjM1MDQ2YTcxNDRhNDFmMTBlMmI5NzgyYTc2ZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1MGE4MTlhNjgyMGMwMzcxODE2OWM0YjllYmQ3ZThkODI1NDlmMTgzZDE2ZjkxYjM4YWY5MGZkNTM4ZmQ5YTkifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift#L294-L314", + "version": "repo-lines-v1:sha256:a64014c34c15ee7ab0d8a7a5a10328e7bd3404bf8421df377ce5286051a2d32d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE0NjE2ZjIwOWNhYjQ2NDQ1MTQ4ZjU3NzBlZmQwM2Y2ZmU1YTA5NGIwOTJhYWM5M2Q1ZTE0MTE2YWNiMWVkNjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyMTg5MDk5ZWU0MjAyMWJlMjA2MDFkNTBkNGY4YTc5ZDI0YWEzOWE3MGUwNjgwZTA4MDgxZmZhMzk1MWEyOTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI5ZjE4NTg0N2IwMWZkZjhmNzliZDMxMGI3NzJjYzI4OTlkMGFmOGQ1YTM2ODAzYzZlZmY4ZTNkNDc0NWE5N2MifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1781-L1857", + "version": "repo-lines-v1:sha256:137b934f370a293436a575b71fa2064f196fde99b552ad6b4722cc26c7d1715f:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwNWFkZWY4YmJmMTk1N2E4N2RkNjVkZGNkNWUxZmZkNzU1NzQwNzM2MTY4Yzg4MTUyOTU1OTljMTk2Y2ZlMWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNjYmNmYmVjMzcyYTExZGI3NzM0MGI4NDNmYzI5N2NmNDc4MmM2NTA3NzMwZWQ3YzMwYjAzYzZjNTU4NWQxOTUifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L137", + "version": "repo-lines-v1:sha256:d884494a35ff53875a54b449f399714b9593269893ee6b39224ad026690f757c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQifQ" + }, + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L1410-L1412", + "version": "repo-lines-v1:sha256:31c1f04a072f685c2062916ea2f92b78d852768850456d8452bffe97923d3bf6:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTA0Yzc0ODEwODVjNGRkZGFjYmEyYTcwMDc2NDhhZWU1NWMwZDRkM2ZjNzkzZjJhMTJiMDM2NzU3YTIzMGIwZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSJ9" + }, + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L201-L227", + "version": "repo-lines-v1:sha256:1d54119a61269212f65c8230f6bbdbe4878a2feb36bcce0a3e87f802545152a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkY2E2ZmY0MTYzODU2MjUyOTU1NzNiYjQ0ZGRjZGM5ZDZiYmZkOWQ2OGRlYjVkZDRkNjA5OGNjZWRiNTFhNjUifQ" + } + ] + }, + { + "id": "claim_ee0f3773cc6349719ef3197c3a24b62b", + "statement": "Desktop and mobile prepare edits synchronously before creating a UI task, preserving original attachment payload/suffix but capturing fresh submission context; performAgentEdit checks identity before tracked persistence and again before runtime preparation, and failed persistence does not apply the live edit.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L448-L495", + "version": "repo-lines-v1:sha256:838ceb32878a6eeab928ca75de90ff336039a453f732420b741e243c655d60e7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", + "version": "repo-lines-v1:sha256:cd90c6361276a4a76ce8f418f2c1c6e84412990ab662ed6d79c7aedaf37f184e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L216-L281", + "version": "repo-lines-v1:sha256:0b8c25408aec78da9722af3b9358ea9a949a5bf115e9d69ec12834362b0fbdee:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3MTI1MTA3NjE5NmE2NjI3OWU2MDFjYjExNTRjYjNkYTQyNmM0NmUxYzVkMjBmZDJiYmYyMWM3Yjg2MzE5MDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAwNDYxNmUzZjI2ZjhhZWRkMzU4YmNjODQ3OTZkZDA1MThhOTIzOWYxODRhMTAyMTY2ZDFkZTRmOGRmOTRmODIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1054-L1067", + "version": "repo-lines-v1:sha256:4f484d7cfc22662766e35da9dbd2bbae1e661c48fb02f3e454ed67e39e0efe50:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNDEwYjZlNzRkZThiNjdmYWNlOGI5MDE5M2EzYWNiMzg2Y2U2OTk1YjQzMGUwZTVlNThhZmFiZWE0NDFmYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmOWI5MjU0ZmFlZTRiMGFkZWU1YmRlMDZjYjY3MjI2ZGI3MDQ2NzUwYjU3NDVkZWQxNzI4YjQ2OTMyODEwZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1YjAwYjk5MjY2NDI4ZmU1NTI1NDYwZDE0YzNhMjZlNDI5Yzc3Yjk1ODUyZmEzNzEyYzEzYmNiMTMwM2I3MTAifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L215-L227", + "version": "repo-lines-v1:sha256:cd20a3869cf3d54e2dba2169f1e5d4a254f24780ec533eae7cebbc62715da7e2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5ZjlhOGZjNjI0YTNlM2JjNjU0ZjJiMGJjYTRhYjk4ZDgzNGJiZmY0M2Q2MTI2ZGNiYzg3OThhODE4Njg0MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmMzBhYTA1MGU1ZjU1NjUzODUyZDU3YzU5ZDBmZWFhMWUxZTJkNmJlYzc3Mjk1NTQ4ZjFlNjExZWZmNzc0OTEifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L638-L654", + "version": "repo-lines-v1:sha256:52f1ee86dd5715fa752288f82f000ad9f5df3df9a8b00a1cd9ca301d14921032:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjNDJhZWNiYjEyMjRmOWZlOGI3NjQwMDJhOTcyYzgwMzBiNDNiZjY5NGI2MDdjZmIzNTFlM2QwYzFmZmJhMzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFlZWMxYmQwNjQ0MjU2MWMzNjBkM2Q1MzgzZmQ3MTQwMjEwNzMwMWI2NWQ5NGYyZjk1YWI1MDIwYmRiYjdkOWMifQ" + } + ] + }, + { + "id": "claim_52455054e49840aca30138327ea628e3", + "statement": "Preparation is separate from streaming, with combined activity guards; stop, global cancellation and mobile background expiration synchronously invalidate preparation identity, cancel and join tracked history/preparation work, and recheck identity after the interrupted save so stale work cannot retire a newer capture.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L88-L228", + "version": "repo-lines-v1:sha256:26af95b584c6f8c1e65cebc40fade84fabaed69fc999ad156818375892763d7e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjYjA4ZDM4ZjgxOTRlMTEyMGIyOTYzN2Q4MmZhM2QzNzc3MTk5ODlhNWQwODc4MWI5ZTQyY2I4NjgwNDYyZTczIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0MTUxMDgxNzdlYjA0YjhkYjk2YTZiNGFhNGEzNzE2YjAwZGRmNjYxZmNhZDlmODcwOWI3NDdkNmI2MTBlZWM2In0" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L760", + "version": "repo-lines-v1:sha256:64ee147e1dcaafe8cc19bb2697cc20ec0c213ab4605a69f330102ab70ac0e0c7:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L69-L83", + "version": "repo-lines-v1:sha256:0d5f7cf554fd4cb9799565481fd35f87cf6deabc07509813f9f40d913426acea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3MzJiN2I3NTFlYmQ1MmM4OWQxOGYwNzU4M2EwYjQ1MDY4OWNlMjkyYTIyNzJmNDVlNGZkNTY5ZGZhYjg1YmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImExMjMyNjI1MGIyOGY3Yzg0NmRmODVkODZhMWM1MmM1MjBhNjM1Y2Y5ZGIzMTY3MjFhNTE1MDdjY2E2NjE4NGYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1872-L1905", + "version": "repo-lines-v1:sha256:6a975f7fa61da5d616fc6f10e1f9423d2116389da4c055f19e477ed0fc6ab95f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkNDg1YmY3ZTFmMTQ5NDczNzFiZDY3NGZiMzAzMTJmNmNhOGU2YjU1MThjOTliNGI1YzRhMmE1NWYyOGI2OGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0YzBjNjJkMDZjYTNlYmQ3NDRjZGU2YmE0M2M5Njc2ZmIwYzQ2ZjdmZmNhZDZkNzRjZTFjN2M5NGNjYmI4YWYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2000", + "version": "repo-lines-v1:sha256:def37b6b9d6634fc8205db0d3ab3eb671124b0e95c4e482e65b1b0ec66ce731a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNGE5Mjc1NzY2ZjZmOWRlODJkNTdmOTM5ZDg3YTk5ZDhhYmNjYWYxMWI1ZTczNDhiOTA2M2FkZmM5YmFiYWMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L843-L848", + "version": "repo-lines-v1:sha256:69c8d0eebc87d56ae3c9b142d02d0db22d37583c5461baed4ef0bcc384c91037:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjIzNTI1YTY4OWNjZTMxOGNjNWYyYmRmNzM5YmEzY2JhN2UzZjk3MDlkZGJjMmM0ZDVmZTU2M2RhZmMxNDcwZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZjE5YjZmYmIyYWMxYzk2NmFiYjNiNDcyYzQzYjI5NmEwN2JiOTU3ZDY0OWY0NTU2MDYzMzVjY2M3MDE3OTUzZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYmFiMWFiMzA0Mzk4NjMzMmYyMWM2MTUyNWUyNjI4YzE2NGU1ZWRlMzYwMzhjNGMxYmM0MjgyYTVkMGZmMGFkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNmFlNWQ4OWJmZGUxNzU1ZTZmMDE4MTkzYzdlYzQ1YzMzZDkwNTgyOTI3MWIzZmI5N2QwYzk4YTUzNzI3NjFmNCJ9" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L153", + "version": "repo-lines-v1:sha256:a447b5e91a0ff06e0586ba98bce9ea20c40d2acd56bae2b931f831e2e81f8b7c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzNjFmYmU2YTJkNzg3MDAzYjc5NDliMDY4NTFjOGJlNDczNTI3MDNiZjAwYmZkYjM5YTVlZmEwZDAxYTdkNmQifQ" + } + ] + }, + { + "id": "claim_21c0e6a726c9423098e4ca8f42b2651b", + "statement": "Archive/delete cancel and join pending preparation before their activity guards; manual compaction refuses preparation, navigation leaves it active, and automatic compaction uses captured preparation settings while preserving persistence-before-live-apply ordering.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L253", + "version": "repo-lines-v1:sha256:139ac4c69442f66025b379fc4089c53b7a0146bb84dc60c78a255b867eec9ffa:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0NDQ1ZGQ3NzZkZGY3MmE2OGI5ODIzMjUxMjgyYzFlNTI3NmIwNTQwYWVlMzQ3YmViZjEwMWQyODlkZGNmZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L372-L393", + "version": "repo-lines-v1:sha256:86b94fd10a8c9d7ca8a12fd280f314f6ed57b669f92add6b32897973ab5fbe31:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUxNDk2NDE4YTVkMGU3OTIyYmYzZjczOGMzMzJmZmNhM2UxZGJlMjkwNmFiMWVhMzBkYmE1YjVmNzdiNGYzNDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBjOGE2MGMzNzZlZGJkOWFjYTMwZGY1NDgwMjUwOGVmMDcxYWU4YmYzN2VmZWE3ZjI3ZTkwOTk0Y2VhZTBhYjAifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L28", + "version": "repo-lines-v1:sha256:8143c52c14bd43544662ced4066ff097b92b594ccbd587298eb4f222f8ac6c0a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNWFkOTQ4ZjVhN2JlOWJlMTVkNTRhY2EwNzlmZWI4MDYwMjNlMjVjNjIzNmVmMWI0NTM0YzQ0NDA1ZTk1NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDU0MDY3MDk1MTM2ZmQyMDViNmNhM2JkMGFjYjk5N2M0MDg5M2JjNGI4OWFjMzMxN2YxZmNhYzFiMzc5ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJlMjQ5NGEwNDI5YjA2NWEzYzM2ODQ2ZjNmNWVjNTljOGUxZGFmNWVjYjZiNGFjYzVjYjRlM2Q3ZWRhZTg4N2EifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L63-L81", + "version": "repo-lines-v1:sha256:da559c6fb05b1afa694d3397263759479e8be0ff76854ea7fa06fd269153b9e5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlZGZjY2UzODdkY2Y5MTEzNWQ4YjcyMzViYjIzZGU4ZDM4OGVkOWY0YjU5MzhkODdlMTM1ZjA3OGU5MzBlNjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViZDk1YzE1MTE4MTI1NDYxYzI5ZGMzMGJjNGJlOTFkZGUyOGI1NDFlYjJjM2NiZTkwNzc4NDA4NTFiZDMyZDcifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L632-L660", + "version": "repo-lines-v1:sha256:b7ea68c5df571de68ab0ae45d9c94ed876783cac80350d6c5d0b17aaf3a8683e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjMGM5OTdhNmNkYjEzZjBmMGY3NDJjNjE2YWRkMmMzYjRlOGMwZDY2OTQ2ZTlkNzQ5Nzc1YjY3YTBkNGY5Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZmNTNlNmY5NWE4YTI0ODczYjhiMTUyMjJmODMwOWNmNTE2OTY3N2NmYjgwN2JhN2YwYWQwMWQxYTAxMWJhMzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg1ZThlZjg0MGM3NWUwYTg0ZDlhYTdjN2NkMWY4MmRhMWU2ZmNlM2VjOTY4Yjk5YjIwYzYyZmU1MWYwZDg2OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L829-L835", + "version": "repo-lines-v1:sha256:26e72807f43e248f95f1c994dac14bb23d50bba63007496164e70adadf2c6e4a:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWEyZGFkNDM4MmM5YTFlMDhiMGQwMThiNmYzYTllMTdkMmU4NTdhMDRmN2M5ZTI3ZGE4NDE4YjdiNTM3ZTYzNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGY0NjJiN2UxZTkxMWYzN2VhMDM5OWU0OTk1ZWUwMWZmYjc5NzcyNzA0MzU4ZTIzYjQ5M2RhYjBmMjliNzU5NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjJkNGJhOGIzNzQ4MTFmNjFkN2Y5NDg1ZjA3MzMzMWIwNzNhM2Y1MjlkN2YzZmI0NTYwOWYxNGExMzEyNzUzZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDQ2MTIwNmJkOGQ1ZTI2MGNmOWJkYjA0MTlmZjA2MDQyM2IxODIzMWE2ZDE1ZjI4OWY4ZDZhYWI0NDYyNTAxYSJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L881-L890", + "version": "repo-lines-v1:sha256:7c9756ce11f5a802355fdf037aff24ef90f81e71df715249370eef766c0a4ab0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MTZmMWZiZWQwMTYzZTEyMTU4OGUxOTZkMjA3Zjk1ZWYwNTFmMGZiMDEwN2YyNDI4OTA1MzAyOTEyYTVhNmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlMGJhYWZiMDE3NmFmY2M1Yzg3NzI0MTkzYmY4ZmM2NWU4MzgwNGUyYWUwYzk5YjZkODViZjViMmY2ZWFmODEifQ" + } + ] + }, + { + "id": "claim_8420ecde122546a3a1bc25a77957def7", + "statement": "The SwiftData edit transaction checks cancellation before any synchronous truncation work, but still removes the entire target run and later runs; editing a steering message can therefore remove earlier same-run durable messages that the live model retains before the target message.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1070-L1095", + "version": "repo-lines-v1:sha256:52e55fbf29dbf0f6cdbb7f1e7a8649d82049f4e361faa3b2fabb517687dd0841:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1OTY0YzBmNzc2OTUyZTBkNTg5ZmJiNTBiNzMxMDg2Njg5Y2EyMDc3MzRkNmI0YTcwNmRhYTljMjQ2ZTg0ZjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkNDQ2MGM1Y2JlNjMyMzgwMmViZWI5M2EzYTkzNjY1ZTU5ZmM5ODYxZjhlYTEzZjY5MDlhYmIyNDZmMWRmOGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyZTkwOGFhOTFkNDc4NzMzMTdhNjJkYjBlNzIwZTU5MjZhNjgyYTQxMjBiZTgyZjVjZmQ3Y2ZmNDc3YjU0ZjMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", + "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L352-L412", + "version": "repo-lines-v1:sha256:3a86a7bd8ecbbb84119dfaaa13c7eede8a28a6321764a743ba61677b6dcd662f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEifQ" + } + ] + }, + { + "id": "claim_c32cd0e1ce114b36bb3a375a2f210902", + "statement": "Preparation cancellation does not guarantee durability across the separate queued-runtime handoff: ConversationModel.start defers AgentRuntime.start to its event task, and immediate stop can cancel that task before runtime snapshot persistence begins; the immediate-stop operations test checks model calls, not persistence.", + "evidence": [ + { + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L123-L136", + "version": "repo-lines-v1:sha256:4d5a3a936c8b5150faef581457b3b776c9d18f4efe621f40a45a72c60ef2a59a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3MGM4YmE2NzBjNmVkZmMzYjk4N2VhOTcyODM5OGY5ZDI5MTQ1NTUzODA1ZDQwZDVjZGNmZGE1YTk1ODk2MWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjYzFjNmE0OTAwMzg1NzI5Mzc4YTYwZDdhNWJjMjU2MDdhZGMzMDBjNDFlYjg1NTBlYmQ0MjYyMDdiYmViNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxZmIwZDU5NGEyZGZmNmM5M2FlMmNkNDZkMjg0MGJhZTRkOWFlOTJlM2JkNzMwNGQ0YzYxMjY2OTQwOWRhYWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzYzg1YTk5NjdhZTQwM2JlODJjYTg1Nzg2NTUxMTVlYTkzNjIxZDRkM2I1MTVkODcxYTljNTc1MGU2NjczMDMifQ" + }, + { + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L220-L226", + "version": "repo-lines-v1:sha256:cc952d0f1cbf33f306da48d33bcf8bf2242b55cfca2867498da7ce2a4f243d56:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjU3NDBkMDVhYjczOTc0NDVlYWU1ZWRhYWY5YTVjODk4NDQwMDk0NzUyYjRkY2Q3ZjI4MjFmNTQ2ZTliNTNjNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzllZjY4ZTg0MTUzNDc2MTZmMDcwYTU2MjhjYWQ0MmYzMDZmNTRjNjE4MjA0OGU2YmU1ZDgyNjI4YzZhYjU1YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjMxZWUwZWMyMTRhNDZmZTcyZmQzNWNjOTEzODUxNjVkODAwNGIzNzc4MDI1NDRmOWIwODY5MzE1MTQ5MmU3MiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTE2ZTU4Y2JiMDlkYmM1NTdlMTAwOGU2M2U2YTU3NjU3N2Y0MTFiYWM5NmRlMmFhYjVhODJhYzcyNzNlMzUzYyJ9" + }, + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L205-L220", + "version": "repo-lines-v1:sha256:774f5d0ae6842b8664276641f46b6f253e19709bd44872b96739c6326b567f6b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3ZmIyNGM5NGIzYjY1ZDg2Y2I5MmE2YmI3NGNhZjg3OTA1Y2U1OThjZDMwOGUxYTg0ZWQ1Y2ExMDU1MDdiYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjYWRiMjlmOGU4MGU2MDkwOGNiNTczMGFhZmVkYmIwNjI1MzkxOTYyMjYyMWVmNDI4MDM5ZDQ3MTc1ODU4NTAifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1935", + "version": "repo-lines-v1:sha256:29086746228df689c5ee73c74a8f470141cf46d41005fdc2804d4ee9d81f9759:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlY2VjZjZjNTcwM2QwMzM4NThjMTkzNjQyMTc5NGFhNmExYzY1MjAyMWM1ZjQzNTI4YTk1MTlkMDE3NzliYzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjU4ZTRmZmFjNDQ4ZTc3NTVkYTFlNTM3ZjU5ODgxMWFjYzY1MTAyODBjMmUyMjA1YTk5M2JhNjI2MWEwOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2000", + "version": "repo-lines-v1:sha256:def37b6b9d6634fc8205db0d3ab3eb671124b0e95c4e482e65b1b0ec66ce731a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNGE5Mjc1NzY2ZjZmOWRlODJkNTdmOTM5ZDg3YTk5ZDhhYmNjYWYxMWI1ZTczNDhiOTA2M2FkZmM5YmFiYWMifQ" + } + ] + }, + { + "id": "claim_f8eef38f66f149f98e83d2ba03a64db4", + "statement": "Implementing a pending plan captures a new acting submission with planContract cleared before resolving the plan, folds composer attachments and armed skill once, and leaves the pending plan and composer payloads untouched when refused.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L324-L370", + "version": "repo-lines-v1:sha256:ad9eaf7b1dc87c1e840daa96a98450465bda4c91dc8e5fb16f1c3d665084f121:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzYzdiMTYwOTA4ZTYxYWIxZThjNTM0ZTY4ZjU2Mjk0OTc0ZDNiYzI5M2EyZGFlNmU2OGY2NjJiMmU2ZWMyNjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlmOGIzMTVhNmQ3Y2JlZGQzOGVlZmUzMjBiZmRhZjdjMDI2ZGQ4YWRlMGNlNGE1NWY0OTExMjZiODA5YmJiNDYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L185-L213", + "version": "repo-lines-v1:sha256:a30ec2eddcb339c146e32da1428a8f2381c02b6515aadcf05f27b41d4b6410bf:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU0NWZhOWExYTg0YWM0MzAwODViZTIxMDNkZmRlMTA5OTQ5ZTQ4OGY0NjA3YzRmYjM1ZWQ4Yjc2MjUyOTAwYzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkM2NkNTgwOGZiNWY1MTI3NWEyY2IzNTQ3ZDcxYWJhZjJmYmYyMjlkYzFkNTkxOGU5MjNlYzNkN2FhNTRhY2QifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1613-L1632", + "version": "repo-lines-v1:sha256:1bb7fc88e5f9e73eeaea78d36f1655d94dba673f8fe9bf6b08652e7e18fd45a5:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUyNjc3YmY3OTBlMWIzMDZhMTY1MWNlOGJjMTliM2JjYmU5NGQ3NDk4MjlhYThiNDcwNGZjYjRjYjkyYTc4MTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlkNmE1NzQxNTViMDMyNWQ5ZTA3YzA5MmNhMWJhNDAwMDBhYWNmN2FlYzMzZmNkOTMwZTA2NDg2YjVjYjY5YTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5NzkxODQ1NWVkODQyNThjMThkMDBmYmM2OTkwNGYyZWNkNWJjMmFlODVlMTY4MjlmYzY4YWEzNDhmZTI0YzMifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L215-L240", + "version": "repo-lines-v1:sha256:198760cf2a134c8b7734e21e1666526e81539cda162f86a0c718c9af531b0643:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjMjY5OTU5MDM0ZmNmMTk1NzhiYTA5MTVkMTUyNTUzNzQ0Y2FjMTQ4NTIyM2M2YzI3MDY1ZTc4NzRhMzYwZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViMmY3NTYyYjc4YzhiNGIxMTdjZDY1ZDFlZTg4NmQzN2E4NGQyYTA1Yzg1YzY0MzA4Yzk4MmFiYzU4M2JlMDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmZWEwZWQ4NzRhMWQyZmU0MmUxZmUwOTBkNGYwOTFjOTdlMGYwYzRjZjJmZDdhNDFkNTg5Y2ZlZWVjMzVhMWUifQ" + } + ] + }, + { + "id": "claim_9b9c512baaff4cb68099608384f1755e", + "statement": "Explicit startAgentRun reuses the last user turn's stored context snapshot or a neutral unknown legacy snapshot, while capturing current runtime configuration rather than reconstructing historical model settings, credentials or skill-package bytes; this context path is not a manual Retry/Resume UI or automatic recovery execution, and provider retry remains AgentKit-owned.", + "evidence": [ + { + "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L228-L232", + "version": "repo-lines-v1:sha256:eed277fa50bd81bf4981cfc5ca33ee0040f9f31c8c789675787c5b21125c5a3c:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZWFjOWQ5NmYxZjNjZWJiNDM5ZWM1ZWU1OTBhNzcyMDU4YzBkZmMwY2E3NTBhMGM0MGVjNzI3NDU5MjU4ZmY0NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTkwOWE1M2E2MThkMDI4ZTFkMzAwMGMwYTdhYmNlZWNiZTVmYjM3ZjRlNTExZWZlNDY2NDNmYWQ2ZTY2ODcyZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWZkNmZkMjc0OTZiYTU4NmFjZGJiODIzYTBhNjQxYTUzMTU0NTc4OTAzOWM5MjU2NWUwYmQ5Zjc3OWFiMjhiZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYmNiODI3NzU2YjA1Y2M5Y2NmNDljNjg5ZGExNTM1ODlmNDA5MjYyNzY1Nzg2Njg0YTllMDVhNDY2ZGIwZjQ2ZiJ9" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L430-L445", + "version": "repo-lines-v1:sha256:3f7bdc58bf3927133f06eea2e8e00a3133869ed3462af5055d22c12e05480f3d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzMmE1MGMyZTM5ZDNkYmY3MWUwOGMzNmMwNDZjZDQ1YTc0NDU3ZWE1YzA0NzlmYjYzZDQwNDFmYTk2YmEwMGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM3ODk5Y2YxYzlmODQzNzQ2Mjk3MTM5YjliNDZhOTNiZjExN2IyMTE1ZWE5OGQzMDcyMmI1NzMyYjQxODc2OGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L306-L336", + "version": "repo-lines-v1:sha256:bf88b55c1f3fecac21ea3c56a99a2176e6666c75cde733f0cf4aef2414a158d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJhNzE4ZTA0YThkYjIyMWZkY2ViZjYyMzlkMzkyM2ZiZThiNzMwOTE0ZmI0ODgxYmQ0ZDcyMDNkM2ViNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjYTlmYWYyZWJmODlkNzUyZmY5ZDg0MzNhMTU1ODQ5YzIyMTA4ODA1OTA2OWU5ZGI0ODU5ODkxZDcxNDUwY2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ0OGJmNTE3NmViOWRmZDcxNmVlZjIzYjMwMTU4ZTE1ZWVjYWEwZDM4NGMzMTczOTA4YjJiZGJhMjA2MjU0ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlhMWQ3MWNlZjc4YzMxNTAyYjU3ZGVjZmIyOWNjYzFiNDY0OTZhYTdkMDBkM2M2YzZlZjkyZDc1NDM3NDgwODgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L566", + "version": "repo-lines-v1:sha256:edb46d8373b999cee1120d8ef9ef0cc3027c386590cbc8cbe77eed3d1d2d9086:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3ZTZjOTQxMjBkZTM2NTQ4YzFmNzAxOTNjMzFjODQ1NDU4MjQ4ZDQzYTg3ZTZlNTQyNjkxMGZjZWRiYmU4YmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMjIwYzhiMTI3ODQ1YTBiNDdlOGI4ZDY5OWQ0YTZhZmQ1OTVjNmQwOWFjYjVmODliNzI2N2Y4MmQzMDc2ZTkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0OGI1M2NhMjYxZmM5ZWYxNWMzODlhZWRhMDRmODYxMWM3YjQ3ZTE4ZTYyMDk1ZDExZGY4OTBhY2MxZDhmNGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzZjQ3NzRkYmY1NmE0MTNhNmY3ZTUzNWRlYzdhYTU1NjRjZjcxNDIyZTJhMGI4ZjU5YWQ2NWMzYWZlYzE5MTUifQ" + }, + { + "resource": "repo://TFI/Features/AI/MobileAssistantConversation.swift", + "version": "repo-file-v1:sha256:e61d288008796c0ae9c45ef3bb28166726d98128b950d303c6d5117f9d0d11a9" + }, + { + "resource": "repo://TFM/Features/AI/ConversationTranscriptView.swift", + "version": "repo-file-v1:sha256:e25a6400fe10738ea7881664f6f1fc2465d7696d470dce522ef5222a3bb83fe4" + } + ] + }, + { + "id": "claim_19d8c2a298ff4646bf0214684b0c3f32", + "statement": "A missing model or credential at capture stays a failed submission even if configuration is supplied during preparation.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L85", + "version": "repo-lines-v1:sha256:13e7b01f19946ab38cdef60520d4429dc12ac6ba60679734bb93decce1577a38:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L348-L379", + "version": "repo-lines-v1:sha256:6eaf43bb757978c5badc8b8bc08b8d551439733c979c24ef1a948607211463ce:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNmE0YjUxN2VkYTk2ZmVhODI1MDU2NzdjMGJlNmY0NTYzM2E1NTVjNzg3MTM4OTkxOTcxN2M4YWY2ZTY3MzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MDMyZTk2NGQwOTMyMzUxZTVkMTg1MjY5MjgxYjE3MWEzYTA5YjA4YjI4NmU2NjFlYTQ3MDMyM2M4MmViNGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3MWE2YmMwYjNlZDNlZmEzMmI4NjkzYmQxYTljOTg4NjZhYjU5YjM1NjlmMTMxYmNjOWU1YjBkNDE1MGJkYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRiYTg0YTA3Nzk4ZjI3OTg2OWI0MzVlN2JkZTYyZmRjODZkNGFmOTM3MmRhNzIyZjk1YzYyNjlhMmFiOTAzZTcifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L523-L525", + "version": "repo-lines-v1:sha256:3cad3b5cb26d6fd2806c7989d5cd8b766f0080b717490e9a6998983f8650f32b:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDcwYjdhMjk5OTAzODQzNTQ2ZDM5YmFkZDg5NzQ4YmNjMTUzZTQ0NmZkZWY5OGQwZWQ1ZGZmZWI3MWZlMjYwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjFiNmJkZjk5Njg4ZWIwNmJkNWI1YjVhOGI4ZDZhODg2NzExYjVkMzY2YTY2Mjg1YWQ2YjAwOTU1YjRlZmQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2RkZmFhMDUwMzQ3NzIxMThiMjE4ZWU1MWZjZjMyN2U5ZGMwYjUwNTRjYjE5OGFmOTcwMGI4MzM5YTJhZTkzNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTEzM2Q3NzZkZWFkNDA1ZTEyOTNiZWRiMzdkZGRmMmFjNDA0OTJhNGE1NDRjYjhjZTQxZDQ4Y2JhMmQ3ZTBlMyJ9" + } + ] + }, + { + "id": "claim_1b7003b57ebd41dfbc14412d029a7f28", + "statement": "New submissions capture memory-session eligibility before preparation; later enabling does not add a session to the turn, while disabling revokes existing contexts and rejects late loads from the prior generation.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L395-L428", + "version": "repo-lines-v1:sha256:a08af3f8158f08c746947fff72a037f53c6abcef3acc5a298d04c24e3a817d93:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFmODg2Mjk4OTI2YzJmYTAwODkxNDQ4MzdhZjIyYzFmOThlNWRiNTlhZDJiNDI3MDFlMWY4OWYwOTZlNjNiZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkZWFjMjgxZjBmYTk0OTMzODI3YzJlYzFlNGFmMThiZTk0Yjk0NjRmMzMyNDIyYzUxMzNiZDNmYThkODg0YmMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L611-L619", + "version": "repo-lines-v1:sha256:04ba197fac5b56c77ec0c0f30bbd76411d9455f58e9d396d84d2b36c9e6967c8:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzFmOWYwZmE1Mjg2ZTNjNWNjODQyMGM4OTlhNDllOWE4MDg5YzdlODFmNDM4MjhkM2ZmODE1YWZkNjhhYmU1NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZjJhYjU2YWMwYTg2OWJmODA5ZWVlMWJkZGNlMzg4YWYwNzdjOWZiMzY3MjUyMWRjN2ZjOTVlMjA5YTExNzdjYSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDY0NzlmM2RlOGUxNmNkMWJkMjcxMjk5YzlhYTY3MTBiOWFkMjU0Yzk5NTBmNGRiYjZkZjFmMDAyYzYwMjBiMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzY0ZTM0ZWNiZjkzMjgzZDEzYTVlN2UyNGY0MTAxZTg2NzQ1YzI2NjZjMmNiOGEwNGY3YmE0N2QwM2Y5MTkyMiJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L794-L809", + "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L52-L76", + "version": "repo-lines-v1:sha256:63ceffb701cb214698f1c57e2105b4dc02d7d20d26ec8b71c4eb50058a6e8aed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwYzVkNTIyNmFhZThmY2Y0MzhjMzM0OTQ3MWY0M2JlYWU3ZjkzYjAwMTlmOWY3MTIxOWQyMzhhNTY3MWU0YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmMjE1Y2QxMWU4MjNlM2M0M2IxYWY0OGI2MDBmNDQ1ZTY5MDA4OTZmNmU1MmFlN2U1ZGI3M2FmYjFkNjMwMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5NzJkNjIzZGZjOTFjMGU2YzQ4MDU1YzY3NzhlYThmNWUxZGQxOTYyZGNlYmY3YjY1NDQ4M2NlZGUzZGY0OGEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L98-L116", + "version": "repo-lines-v1:sha256:ae20e9db15b84f07e0787f36390d963dfb0425b2ba7f5a5cec258e46ddc6598f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjZmQ4ZjZmYzk3NjQ5MmIzNjQ2MmQ4NmUyNjVhODNkNGRjYzlhN2YwMDViYmZjYmI1NjYwZThkOGM0NmQ1MzkifQ" + } + ] + }, + { + "id": "claim_27a85d797b794f1ea5150b62479253c6", + "statement": "Automatic titles require a real nonempty transcript reply, which can qualify even if its run is later stopped, and resolve title configuration live; automatic post-run memory review instead requires a completed non-planning run.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L926-L999", + "version": "repo-lines-v1:sha256:c9f47a8f56faafbb74e3890456a70b21274e461a092082bfde58ea7f58fc1e2e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE5MWVjYmE5YTIzZmI0MmI5ZmU5Zjk2MDhjYThlNWM5ZjRmODNhZDBjNGJmZDRkZTVlNTdiMTI5ZTEyMDIxZDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxNzVmZGUxMTY1ODljNGUxYzlmMjRkNDdiNzExODdhNDNjMGM3NWZmNjg3NTA0NGQ3YjFjOGIzYjlkNzkwNmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgwNGY4OWZjMGVjOThjOTgyNDE4M2U3OTVkM2VkZDE5ZTkzMGY3YmM0NzFmOTAxMmFhM2Q1MDNiZTJmODk3NGIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationTitleController.swift#L20-L40", + "version": "repo-lines-v1:sha256:8c395370a5d6db13c09099290c322db66dec28cafff52ef018a409820775f53e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhNTA0ZjZiZmJkY2EwOGIwZTE4NmFlMjA4YTBjODQwNTFlYmVjYzg5YjU5NTcxOTc1M2U1MGRhNjJiMDcwNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliNGNhZGRmMjhjMGJkYmRjNmVkMWM0NzRiNWIwNTNlZDk3OWQ1ZDQyZjA0NTZlODQ4YmIwNzY5ZjViOWVjMzUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZWJkZTg4ZjUxNjFmNzkyMmRjOWY0ZjdlYzA4OTg2ZDU2MzdjMmI3NDZiNzYxMWVjOGZkY2VkMzM2YTNjZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 0e928087..1b801bc5 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:61210d26025511ef69e04ecbb09c4b5266e8a8b365fdc612edc432f355271193", + "pageVersion": "sha256:89970353463e9a930e9c659614c1e55ef4107801b0721beda98be323539ac450", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -38,7 +38,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -129,6 +129,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.claims/workflows/ssh-session-lifecycle.json b/openwiki/.claims/workflows/ssh-session-lifecycle.json index e0e246f5..a1a78494 100644 --- a/openwiki/.claims/workflows/ssh-session-lifecycle.json +++ b/openwiki/.claims/workflows/ssh-session-lifecycle.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:2440bbfacd6925e8874548c87942c4f02c29835160dde94f309fb7abc29758f5", + "pageVersion": "sha256:8ed164f2bf48e753c9305fac8b0f7e7754b6e223ed99d68ba71ed6c8ca191334", "claims": [ { "id": "claim_69a9de4ce918417c8a44c6c87c9f6564", @@ -146,7 +146,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:4c153bf5b7d97bf82683269527c35e76d9666ce459b681af16e7eed4a586e837" + "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" } ] }, @@ -175,6 +175,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T02:40:53.789Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index ce389c2c..a932dbed 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T01:40:20.785Z", + "updatedAt": "2026-09-23T02:48:46.050Z", "command": "update", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index b8200e54..b50b9778 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,74 +2,74 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { - "pageVersion": "sha256:9e4dcfe15396af0c8e9ed77ff7347eac829aeff9d111ad37757a2e3f1e63b2c1", + "pageVersion": "sha256:aacd9e53709881d4edac532fd23dfffe4e8ba8cf7a5b611a79bfd846f1128d8a", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:75fe464fb65e761e5eeaab3f31f1a2d852175cc5f78f5b099ddffee4a8655971", + "pageVersion": "sha256:564964f95e8176182ff915ce6d8834e7717b6a3a3be946666c5b1d1d749906cc", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/mobile-ui.md": { - "pageVersion": "sha256:a1e240ca495ddf76ace3e9861b2f459a2fa61e2d53179352e87b15452e6828ee", + "pageVersion": "sha256:5a57c9f6cd1214629ea2a75c3ba60386a70d5c2170cdb552f4dea473f0dde193", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/module-map.md": { - "pageVersion": "sha256:cd736475053bed4751ff7b4f06ee0433c8d3ac3e74f3866d585bc958dd6b25d5", + "pageVersion": "sha256:4139c19be34c8fc47c74c1b05a07851c8e18bdfafe33237f7507fbc542e4b360", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/native-app-directories.md": { - "pageVersion": "sha256:daaecfb65b33663c617a5627c7c65fc6e45cd05b85017d53e6a48c818a1e15b5", + "pageVersion": "sha256:f1dc10b8e646cf711a546c2b764e9d898ad017b4ef0f3bdc7f1932e54e746ae5", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/platform-runtime.md": { - "pageVersion": "sha256:f6b1eae907640a27121b4c2f41cc040490003e2944d38858bc7a4719581bd0b3", + "pageVersion": "sha256:0e61cbe711eed5c27ef6466c5ce521193641c61d25abf03298b9098834235f2f", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/shared-module-directories.md": { - "pageVersion": "sha256:42206a596cfd036cd1cf7bd7bc2f0e8ec5f96a4577648a864cebd3f8db5d0a3f", + "pageVersion": "sha256:afb1bab3e4c91081f597fea301a3472ef878abc9493b4e6bcc9014ba1aabe4e9", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/architecture/supporting-system-directories.md": { + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", "pageVersion": "sha256:2711b0e7d443185dd21a3d96c2cb80f84a5e2149a5a6fc64fda5363afcbc86be", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/architecture/system-boundaries.md": { + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", "pageVersion": "sha256:3cbf1be9eb94f0eb87d83be0a1277c9801cb4df9a3da898ef95b63b20b72a785", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "pageVersion": "sha256:47fa6bbaa4a43b7bac039520e0bd6d4cf3f003b8969821fecaebe1cd9bdeac6b", + "pageVersion": "sha256:1dba6f651d9536e5ea63b9bd551b7e20cc973f0816613aec3593304b42c8cbfb", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -86,11 +86,11 @@ "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/concepts/remote-services-and-forwarding.md": { + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", "pageVersion": "sha256:e2720aa6320a8e366bf71c3fa0c7a03da39dc3ef8d3f20aebaabdaef585258a8", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/concepts/shell-integration.md": { "gitHead": "cca15abd7682f33404485806d15c99541ef98c5e", @@ -114,11 +114,11 @@ "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" }, "/openwiki/integrations/mcp-server-and-bridge.md": { + "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", + "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", "pageVersion": "sha256:25994ac645085f10b88bf658588df753039ac6bd5c79e6cdde785a0323fb495e", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/operations/build-test-and-release.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -156,11 +156,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "pageVersion": "sha256:61210d26025511ef69e04ecbb09c4b5266e8a8b365fdc612edc432f355271193", + "pageVersion": "sha256:89970353463e9a930e9c659614c1e55ef4107801b0721beda98be323539ac450", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { - "pageVersion": "sha256:2440bbfacd6925e8874548c87942c4f02c29835160dde94f309fb7abc29758f5", + "pageVersion": "sha256:8ed164f2bf48e753c9305fac8b0f7e7754b6e223ed99d68ba71ed6c8ca191334", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d", - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" } } } diff --git a/openwiki/architecture/desktop-ui.md b/openwiki/architecture/desktop-ui.md index 0a171d66..fec366ed 100644 --- a/openwiki/architecture/desktop-ui.md +++ b/openwiki/architecture/desktop-ui.md @@ -5,7 +5,7 @@ description: "How the macOS app composes its native window shell, persistent wor tags: [macos, tfm, ui, navigation, workspaces, swiftui, appkit, design] verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z sources: - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index a7bc49e0..f7aa1ab2 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -6,6 +6,10 @@ tags: [architecture, invariants, lifecycle, security, persistence, remote-sessio sources: - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md + - id: openwiki-source-685b7f8e7dbcf91b4e2a50e2 + resource: repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift + - id: openwiki-source-5f99e8af445e073ffe9bc257 + resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift - id: openwiki-source-5c91b796f7e6d651aedf3023 resource: repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f @@ -56,6 +60,8 @@ sources: resource: repo://Frameworks/TermindRemoteInfrastructure/Transfers/SFTPEditService.swift - id: openwiki-source-c4b6e224fdbea6dcd393344d resource: repo://Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferCoordinator.swift + - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 + resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift - id: openwiki-source-bf7a64e7c819eb9279e2f9e4 resource: repo://FrameworkTests/CloudSyncLifecycleTests.swift - id: openwiki-source-40cfadc2320da9a292c64d26 @@ -104,10 +110,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } +generated: { by: "codex", at: "2026-09-23T02:40:53.789Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Cross-Cutting Engineering Invariants @@ -492,6 +498,14 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo - Immutable hidden `AgentTurnContextSnapshot` captures facts/skills/plan/surface availability at submission/steer/edit. Replay uses original baseline/deltas; absent legacy context is unknown, not current UI. Leave session/mode clears; retry retains, fork copies, compact rebases. V16 adds independent replay backing, removed on deletion/truncation while older messages remain frozen. +- `AssistantExecutionController` captures composed prompt/images/attachments, context, model/provider/credential, reasoning/permissions, + search, skills/registry, Guardian and preparation settings synchronously before memory or automatic compaction awaits. First-send promotion + follows accepted capture; edit UI captures before creating its persistence task. Plan implementation captures acting mode and consumes + attachments/armed skills once. Historical context replay does not reconstruct old model settings or skill-package bytes. +- Preparation is distinct from streaming. Stop/background expiration invalidates preparation identity synchronously and joins pending + history/preparation work before its interrupted save; stale captures cannot edit or start. Archive/delete cancel pending preparation, + manual compaction refuses it, and navigation alone leaves it running. See [Assistant Runtime](/openwiki/concepts/agent-runtime-and-assistant.md) + for the preparation boundary and existing runtime/recovery limitations. - Project tool output once before `toolFinished`: bounded preview and scratch reference, retaining crash/replay metadata and paging offsets. Later completion never shortens earlier results; `sftp_read`/`scratch_read` report complete lines/truthful offsets. - Terminal suggestions use latest delivered user snapshot, not focus. Surface availability is partial and does not restrict independent SSH/ diff --git a/openwiki/architecture/mobile-ui.md b/openwiki/architecture/mobile-ui.md index 1a48dfdc..37f136dc 100644 --- a/openwiki/architecture/mobile-ui.md +++ b/openwiki/architecture/mobile-ui.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # TFI Mobile UI Architecture diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index ebaeacaa..eabe5561 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 5509802a..b6b7b38d 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index eceeb8ba..9e0dc4c9 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Platform Runtime, App Composition, and Lifetimes diff --git a/openwiki/architecture/shared-module-directories.md b/openwiki/architecture/shared-module-directories.md index 0c7db681..988c73da 100644 --- a/openwiki/architecture/shared-module-directories.md +++ b/openwiki/architecture/shared-module-directories.md @@ -107,7 +107,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Shared Framework Directory Responsibilities diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index f1faa9af..2d337007 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -8,10 +8,16 @@ sources: resource: repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md + - id: openwiki-source-685b7f8e7dbcf91b4e2a50e2 + resource: repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift - id: openwiki-source-5f99e8af445e073ffe9bc257 resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift + - id: openwiki-source-160f180df5d3cb1457febfee + resource: repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift - id: openwiki-source-1c48f72c0c7a3164d7d5c715 resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift + - id: openwiki-source-b62626c07828480fcd2b71b7 + resource: repo://Frameworks/TermindApplication/Agent/ConversationTitleController.swift - id: openwiki-source-76220f10b4e23e3c61a811be resource: repo://Frameworks/TermindApplication/Agent/TermindAgentPolicy.swift - id: openwiki-source-7397a04f40a393c81b0ca52d @@ -34,14 +40,22 @@ sources: resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift - id: openwiki-source-6d759b4cb61f68a067dae42c resource: repo://FrameworkTests/AgentSteeringTests.swift + - id: openwiki-source-247755d873c5d94b0a364217 + resource: repo://FrameworkTests/AssistantConversationOperationsTests.swift + - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 + resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift - id: openwiki-source-a62bfbbc7de13a894444718b resource: repo://TermindMCPBridge/main.swift - id: openwiki-source-906678adb91c2fce2d3fcaed resource: repo://TFI/App/MobileAppModel.swift - id: openwiki-source-d44869be46c03fbd0ce9c64c resource: repo://TFI/Application/Agent/MobileAssistantModel.swift + - id: openwiki-source-3dfad0e95bb7d66f292c20c9 + resource: repo://TFI/Features/AI/MobileAssistantConversation.swift - id: openwiki-source-a154b5d36ec59496392d45e4 resource: repo://TFITests/MobileAssistantTests.swift + - id: openwiki-source-4dbea0ff2808163cb4ecdd8f + resource: repo://TFM/App/AppModel.swift - id: openwiki-source-76b1cdca4a02038756187f92 resource: repo://TFM/Application/MCPServer/MCPServerTools.swift - id: openwiki-source-16db5d5459897d40fe080701 @@ -62,10 +76,10 @@ sources: resource: repo://TFMTests/AgentSkillTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T01:34:03.251Z" } +generated: { by: "codex", at: "2026-09-23T02:40:53.789Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -76,11 +90,11 @@ This split is a security boundary as well as a modularity boundary. A model prop ## Responsibilities and assembly -`AssistantExecutionController` is the application entry point for a draft. It prevents overlapping starts, archived/history-changing conversations, unfinished attachments, and unauthorized use before it records the user turn. It compacts first when needed, resolves the chat provider and its credential, freezes run-level choices, assembles the tool registry, and starts a fresh `AgentRuntime` through the `ConversationModel`. +`AssistantExecutionController` is the application entry point for a submitted turn. Before recording a new turn it rejects overlapping starts, archived/history-changing conversations, active compaction, unfinished attachments, and unauthorized use. Accepted submission synchronously captures the composed prompt, images and context attachments; session, plan and surface context; chat model/provider/credential; reasoning and permission mode; search choice; skills and base registry; Guardian configuration; and memory/compaction preparation settings. Only then can asynchronous compaction or memory loading begin. Runtime assembly consumes this capture instead of re-reading mutable composer or configuration state after an await. A missing model or credential remains a captured failure even if Settings changes during preparation. A runtime is deliberately single-run. It owns one event stream and one event consumer. `ConversationModel` owns the live runtime and projects its events into visible messages, tool cards, approval and input queues, run summaries, and the authoritative model transcript. The visible message list is not replay history: tool calls and results are separately rendered as cards, so replay must use `transcript`. -The registered tool catalog and run mode are resolved at run start. A tool-capable model receives the resolved skill catalog, AgentKit built-ins, and Termind tools; a model without tool-call capability does not receive the skill/tool catalog. Session facts, plan posture, surface availability, and the memory-session identity are captured in an `AgentTurnContextSnapshot`. Surface availability follows the latest delivered user snapshot rather than mutable UI focus. +The tool catalog and plan posture are captured at submission; AgentKit selects that captured catalog's executable mode-specific subset when the runtime starts. A tool-capable model receives the captured skill catalog, AgentKit built-ins, and Termind tools; a model without tool-call capability does not receive the skill/tool catalog. `AgentTurnContextSnapshot` is the persistable context portion of the capture: session facts, skill catalog text, plan posture, surface availability, and memory-session identity. It is not a durable copy of provider credentials or all runtime configuration. Surface availability follows the latest delivered user snapshot rather than mutable UI focus. Termind composes the registry in this order: @@ -107,7 +121,11 @@ This snapshot dictionary is a partial override: disabling terminal suggestions d ## Turn lifecycle -A submitted prompt is persisted as part of the run snapshot before model work begins. AgentKit streams a provider turn, emits ordered UI and durable events through a shared `AgentEventChannel`, validates any complete tool call, then schedules execution. Tool results are appended to the model transcript and persisted before the following model round. This preserves a grammatically answerable transcript even for refusal, cancellation, or failure paths. +Desktop sidebar, standalone Assistant, and mobile first sends use the same controller-owned capture path as subsequent sends. A first conversation is promoted into the library only after capture and local turn append succeed. Refused sends leave the draft, attachments, armed skill and ephemeral conversation unconsumed. Accepted composer sends fold attachments and an armed skill directive into the model-facing turn once, clear those composer payloads, and clear the submitted draft. Direct text sends and plan implementation also fold those payloads once but leave unrelated ordinary draft text alone. + +Edits have a separate two-stage entry point: desktop and mobile UI call `prepareAgentEdit` synchronously before creating their UI `Task`. It preserves the original attachment payload and model-facing suffix while capturing fresh context and configuration for the replacement submission. `performAgentEdit` checks the capture identity before starting a tracked history task; persistence succeeds before the live edit is applied, and another identity check precedes runtime preparation. A cancelled or superseded prepared edit cannot later enter truncation or start a runtime. + +Once the runtime begins, the submitted prompt is persisted as part of its running snapshot before model work. AgentKit streams a provider turn, emits ordered UI and durable events through a shared `AgentEventChannel`, validates any complete tool call, then schedules execution. Tool results are appended to the model transcript and persisted before the following model round. This preserves a grammatically answerable transcript even for refusal, cancellation, or failure paths. ```mermaid sequenceDiagram @@ -120,9 +138,15 @@ sequenceDiagram participant Store as SwiftData Repository User->>Controller: submit draft - Controller->>Conversation: record user turn - Controller->>Controller: compact and freeze context - Controller->>Runtime: start request and registry + Controller->>Controller: validate and capture turn/configuration + Controller->>Conversation: mark preparing and append captured turn + opt first send + Controller->>Conversation: promote captured conversation + end + Controller->>Controller: await needed compaction and memory + Controller->>Controller: verify capture is still current + Controller->>Conversation: start with captured request and registry + Conversation->>Runtime: start through event consumer Runtime->>Store: save running snapshot Runtime->>Runtime: stream provider turn Runtime->>Runtime: validate tool schema @@ -142,7 +166,15 @@ sequenceDiagram Conversation-->>User: messages and tool cards ``` -This sequence shows one assistant turn, including the persistence points around tool execution. +This sequence shows an accepted draft submission and the persistence points after runtime handoff; edits first commit their history replacement before runtime preparation. + +### Preparation and cancellation ownership + +`ConversationModel.isPreparingAgentRun` is distinct from `isStreaming`; `isAgentRunActive` combines them for activity guards. Preparation has no runtime that can accept steering, so it must not masquerade as streaming. When neither compaction nor memory loading is needed, preparation can hand off synchronously without an asynchronous preparation task. + +Stop, controller-wide cancellation and mobile background expiration invalidate preparation identity synchronously. Cancellation cancels and joins tracked edit-history/preparation work before finishing its interrupted save; completion rechecks identity after the save so an older cancellation cannot retire a replacement capture. The concrete SwiftData truncation method checks cancellation before entering its synchronous transaction. These boundaries prevent a stopped or superseded preparation from later editing history or starting a runtime; they do not roll back a history transaction that already committed. + +Archive/delete operations cancel and await pending preparation before their existing activity guards and persistence work. Manual compaction refuses preparation, while selecting another conversation leaves its preparation running. Automatic compaction uses the captured model/settings and the valid transcript available during preparation, with persistence preceding the live projection change. ### Streaming, steering, and cancellation @@ -150,7 +182,9 @@ While a run is live, a further user message is queued as steering rather than st Steering captures the sending session context and updates only the `terminal_suggest` availability entry. It preserves other tool overrides, the fixed skill catalog and plan contract, and the memory-session identity. Queuing does not change `ConversationModel.activeContextSnapshot`; delivery does. If the conversation has no legacy snapshot, steering keeps that context unknown instead of filling it from current UI state. The persisted availability key remains `terminal_suggest`. -Stopping a conversation cancels the runtime and its tool work, discards run-scoped secrets, marks running cards interrupted in the presentation, and returns undelivered steering text. `stopAndWait()` waits for the runtime's cancellation/persistence path before clearing live ownership. Termind also invalidates pending preparation when its controller is cancelled, recording an interrupted snapshot rather than launching a stale run. +For an already-started runtime, stopping requests cancellation of runtime/tool work, discards run-scoped secrets, marks running cards interrupted in the presentation, and returns undelivered steering text. `stopAndWait()` joins preparation cancellation first, then requests runtime cancellation and waits for the event consumer before clearing live ownership. + +The preparation guarantees do not eliminate the separate runtime handoff edge case: `ConversationModel.start` schedules its event consumer, and an immediate stop before that task calls `AgentRuntime.start` can prevent runtime startup without persisting a runtime snapshot. The ordering between `stopAndWait()` and this queued start remains a distinct boundary; do not infer universal cancellation durability from preparation tests. ## Approval, secrets, and trust boundaries @@ -172,6 +206,8 @@ Termind tools operate through noninteractive per-command SSH channels and exclus Plan mode freezes at submission. It permits investigation, reads, and policy-allowed commands, but host-specific planning registrations refuse direct remote writes, deletes, and scratch pushes. A plan is drafted in scratch space and presented as the final action; presenting it does not itself suspend a run awaiting a decision. +Implementing a pending plan is a new captured submission. The controller clears the captured `planContract` before capturing the run so implementation is acting work, then appends the composed implementation prompt and resolves the plan as implemented. Refusal leaves the pending plan, planning state, attachments and armed skill untouched. + ## Scratch workspace containment Each tool-capable conversation has an `AgentScratchWorkspace` keyed by its conversation ID under Termind application support. It survives later runs in that conversation, but one conversation cannot read another's scope. Scratch paths are normalized and bounded; escapes, absolute paths, parent traversal, control characters, excessive depth, and symlinks are refused. Entry and byte quotas are enforced before mutation. @@ -184,12 +220,16 @@ A fork copies its scratch scope before creating the forked conversation and remo `SwiftDataAgentRunRepository` stores conversations, runs, messages, replay backing, attachments, tool invocations/results, reviews, ordering metadata, and compaction records. It writes messages by message ID rather than run ID, so replaying prior conversation history into later run snapshots does not duplicate historical rows. Tool event persistence is restricted to durable lifecycle events rather than streamed token deltas. -A saved context snapshot preserves the original session and policy baseline for replay, retry, edit, and fork behavior; legacy absent context remains unknown rather than being replaced by current UI state. Persisted text, tool arguments, titles, reasoning, and attachments go through redaction. In particular, secret handles are replaced with expired lifecycle content on restore, and sensitive header values are redacted. +A saved context snapshot preserves historical session, skill-catalog text, plan and surface facts for replay and fork history. Explicit `startAgentRun` uses the last user turn's stored snapshot, or a neutral `AgentTurnContextSnapshot()` for legacy absent context rather than inventing facts from current UI state. This is context replay, not reconstruction of historical model/provider settings, credentials or skill-package bytes: the controller still captures the current runtime configuration. An edited submission deliberately captures fresh context instead of inheriting the historical snapshot. There is no manual Retry/Resume UI or automatic recovery execution; provider retry remains AgentKit-owned. + +Persisted text, tool arguments, titles, reasoning, and attachments go through redaction. In particular, secret handles are replaced with expired lifecycle content on restore, and sensitive header values are redacted. Tool cards resolve their persisted presenter ID through the immutable presenter registry. Cards predating those IDs use the closed legacy tool-name list and its `builtin.` IDs. Registration and surface-availability changes preserve those identities and the persisted context key; they do not require a SwiftData schema change. Compaction changes the **model-facing** transcript, not the scrollable display history. Its summary record is persisted before the live conversation applies it; after recovery the model receives a synthetic compaction summary plus recent messages while the user can still read the full original conversation. Editing a user message truncates the target run and later runs atomically, creates the replacement turn, and removes compactions whose boundary was removed. +That durable edit boundary is still run-level, whereas the live model truncates from the target message. Editing a steering user message within a run can therefore leave earlier messages from that same run in the live model after persistence removed them. This existing durable/live mismatch is separate from stale-preparation cancellation and is not repaired by synchronous capture. + At repository initialization, every nonterminal persisted run becomes `interrupted`; Termind never replays unfinished execution automatically. Restore reconstructs completed tool cards and can recover a durable tool result that was recorded before its next transcript snapshot. If an outcome remains unknown, repair records an explicit unknown/refusal result rather than assuming a call was never dispatched. ```mermaid @@ -199,7 +239,10 @@ stateDiagram-v2 Restored --> Interrupted: nonterminal run on launch Interrupted --> Ready: history and cards restored Completed --> Ready: history and cards restored - Ready --> Running: new submitted turn + Ready --> Preparing: accepted capture + Preparing --> Running: current capture handed to runtime + Preparing --> Interrupted: cancellation saved + Preparing --> Failed: captured configuration unavailable Running --> WaitingApproval: Ask action pending WaitingApproval --> Running: allow WaitingApproval --> Running: denial result recorded @@ -213,11 +256,13 @@ stateDiagram-v2 This state diagram distinguishes recovery from automatic resumption: interrupted work is visible and recoverable as history, never silently rerun. -Conversation metadata mutations are persistence-first. Archive clears pinning and prevents editing; archive/delete are guarded while a run or compaction is active. Reordering commits the whole ordering update before changing the in-memory library. These rules avoid UI state claiming a history mutation that failed to save. +Conversation metadata mutations are persistence-first. Archive clears pinning and prevents editing; after cancelling pending preparation, archive/delete remain guarded while streaming or compaction is active. Reordering commits the whole ordering update before changing the in-memory library. These rules avoid UI state claiming a history mutation that failed to save. + +Automatic titles require a real nonempty assistant reply in the authoritative transcript, not a visible failure message. A completed reply can qualify even if the run is later stopped. Title generation resolves its configuration when it runs, independently of the submitted turn's frozen configuration. Automatic post-run memory review is stricter: it requires a `.completed`, non-planning run; preparation cancellation does not trigger it. ## Skills -Skills are global app-managed packages, not per-conversation data. A run snapshots its enabled catalog at startup, so enabling or changing a skill does not alter a run already in progress. Reserved skill names cannot be shadowed, and collisions remain visible but paused. +Skills are global app-managed packages, not per-conversation data. Submission snapshots the enabled catalog and inventory before preparation, so subsequent Settings changes do not replace that run's captured catalog. This is not archival replay of package bytes. Reserved skill names cannot be shadowed, and collisions remain visible but paused. A skill is procedure-bearing only when read through `skill_read_file` from its `SKILL.md`. Supporting files, package frontmatter, tool output, remote files, web pages, and MCP responses are untrusted reference data—not instructions or authorization. Skill installation is acting-only, revision-bound, and subject to normal tool approval; a skill cannot grant permission to execute a command, write a file, transfer data, or bypass other policy. @@ -229,16 +274,19 @@ The GRDB store opens lazily, so disabled memory does not create or open its data A memory session receives a frozen snapshot for its conversation. Memory reviews use a compare-and-apply base state, so a stale review cannot overwrite newer memory. Disabling memory invalidates sessions, cancels indexing and reviews, and closes the database while preserving stored data. Destroying memory first persists disabled/destroy-pending state, revokes contexts and joins ongoing work, then closes and removes the owned directory; failure remains disabled and is retried on a later launch. Memory and credentials are device-local and are not synced. +New submissions capture whether they have a memory-session identity before preparation awaits. Enabling memory afterward does not add a session to that captured turn. Disabling it remains a revocation boundary: `MemoryModel` invalidates existing contexts and rejects a late snapshot load from the old generation, so capture does not override a later disable. + ## Focused verification The focused tests establish behavior that is important when changing this area: - `TFMTests/AgentRuntimeTests.swift` covers schema-before-approval/execution, approval mode routing, Guardian timeout/failure fail-closed behavior, single-use secret handles, steering delivery, tool cancellation, registry shadowing, and durable-event filtering. - `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, deletion of the persistent graph, edit truncation, fork persistence, and compaction restoration. -- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order mutations, activity guards, and immediate stop before a queued runtime can begin. +- `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. +- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order mutations, activity guards, and that immediate stop prevents a queued runtime's model call. That last test does not establish snapshot persistence for the runtime handoff gap. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. - `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. -- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, and tool-incapable models. +- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. - `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. ## Change guidance diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index 08d1d927..dcf8b989 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -25,7 +25,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:23:14.035Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # Termind Engineering Quickstart diff --git a/openwiki/workflows/ssh-session-lifecycle.md b/openwiki/workflows/ssh-session-lifecycle.md index 49187151..2835ba62 100644 --- a/openwiki/workflows/ssh-session-lifecycle.md +++ b/openwiki/workflows/ssh-session-lifecycle.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T02:40:53.789Z --- # SSH, Telnet, Trust, and Workspace Session Lifecycle From 5ae3039f0da81c2799f6e9d5713fc8fd5f42e623 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 11:33:27 +0800 Subject: [PATCH 07/30] Fix Assistant edits at message boundaries --- .../AgentReplayPersistenceTests.swift | 52 ++- ...AssistantConversationOperationsTests.swift | 61 +++ .../AssistantExecutionControllerTests.swift | 54 +++ .../AssistantConversationOperations.swift | 4 +- .../Agent/AssistantExecutionController.swift | 22 +- .../Agent/ConversationModel.swift | 185 ++++++++- .../SwiftDataAgentRunRepository.swift | 367 +++++++++++++----- TFITests/MobileAssistantTests.swift | 48 +++ TFMTests/AgentPersistenceTests.swift | 260 +++++++++++++ TFMTests/ChatComposerStateTests.swift | 92 ++++- 10 files changed, 1019 insertions(+), 126 deletions(-) diff --git a/FrameworkTests/AgentReplayPersistenceTests.swift b/FrameworkTests/AgentReplayPersistenceTests.swift index 9c719b0e..6e7e9f2b 100644 --- a/FrameworkTests/AgentReplayPersistenceTests.swift +++ b/FrameworkTests/AgentReplayPersistenceTests.swift @@ -29,6 +29,8 @@ struct AgentReplayPersistenceTests { try await repository.save(snapshot) // A subsequent run must neither overwrite an earlier snapshot nor duplicate its backing. snapshot.id = UUID() + snapshot.startedAt = snapshot.startedAt.addingTimeInterval(1) + snapshot.finishedAt = snapshot.startedAt snapshot.messages.append(.init(role: .user, text: "continue", contextSnapshot: .init())) try await repository.save(snapshot) let restored = try #require(try await repository.loadConversations().first) @@ -58,9 +60,55 @@ struct AgentReplayPersistenceTests { var result = AgentToolResult(callID: call.id, content: "full captured result") result.modelContent = "frozen preview" try await repository.record(.toolFinished(invocation, result), runID: snapshot.id) - let restored = try #require(try await repository.loadConversations().first?.messages.last) + let conversation = try #require(try await repository.loadConversations().first) + let restored = try #require(conversation.messages.last) + let resultID = AgentTranscriptMessage.toolResultID(runID: snapshot.id, callID: call.id) #expect(restored.modelText == "frozen preview") - #expect(restored.id == AgentTranscriptMessage.toolResultID(runID: snapshot.id, callID: call.id)) + #expect(restored.id == resultID) + #expect(conversation.messageRunIDs[resultID] == snapshot.id) + } + + @Test(arguments: [AgentRunState.failed, .interrupted]) + func aRealRunAtomicallyAdoptsTheEditedStagingMessageAndReplayBacking( + state: AgentRunState + ) async throws { + let container = try TermindPersistence.makeContainer(inMemory: true) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let conversationID = UUID() + let original = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let originalRun = AgentRunSnapshot( + conversationID: conversationID, state: .completed, + permissionMode: .askForApproval, messages: [original]) + try await repository.save(originalRun) + let replacement = AgentTranscriptMessage( + role: .user, text: "after", authoredText: "after", + contextSnapshot: .init(sessionContext: "captured host")) + try await repository.truncateConversation( + conversationID, fromUserMessageID: original.id, replacingWith: replacement) + + let realRun = AgentRunSnapshot( + conversationID: conversationID, state: state, + permissionMode: .askForApproval, messages: [replacement], + failure: state == .failed ? "preparation failed" : nil) + try await repository.save(realRun) + + let messageRows = try container.mainContext.fetch(FetchDescriptor()) + let runRows = try container.mainContext.fetch(FetchDescriptor()) + let replayRows = try container.mainContext.fetch(FetchDescriptor()) + #expect(messageRows.count == 1) + #expect(messageRows.first?.id == replacement.id) + #expect(messageRows.first?.runID == realRun.id) + #expect(runRows.map(\.id) == [realRun.id]) + #expect(replayRows.map(\.messageID) == [replacement.id]) + + let freshRepository = SwiftDataAgentRunRepository(context: ModelContext(container)) + let restored = try #require(try await freshRepository.loadConversations().first) + #expect(restored.messages.map(\.id) == [replacement.id]) + #expect(restored.messages.first?.contextSnapshot == replacement.contextSnapshot) + #expect(restored.messageRunIDs[replacement.id] == realRun.id) + #expect(restored.runs.map(\.id) == [realRun.id]) + #expect(restored.runs.first?.state == state) + #expect(restored.runs.first?.failure == (state == .failed ? "preparation failed" : nil)) } @Test diff --git a/FrameworkTests/AssistantConversationOperationsTests.swift b/FrameworkTests/AssistantConversationOperationsTests.swift index f17f2714..8a1d2f85 100644 --- a/FrameworkTests/AssistantConversationOperationsTests.swift +++ b/FrameworkTests/AssistantConversationOperationsTests.swift @@ -224,6 +224,67 @@ struct AssistantConversationOperationsTests { try await operations.delete(conversation) #expect(await repository.loadConversations().isEmpty) } + + @Test func invalidReplacementNeverCallsPersistence() async throws { + let repository = TruncationSpyAgentRunRepository() + let operations = AssistantConversationOperations(repository: repository) + let original = AgentTranscriptMessage(role: .user, text: "Before", authoredText: "Before") + let conversation = ConversationModel( + messages: [ChatMessage(id: original.id, role: .user, text: original.text)], + transcript: [original]) + let invalid = AgentTranscriptMessage(role: .assistant, text: "Not a user turn") + + #expect( + try await operations.edit( + conversation, messageID: original.id, replacement: invalid) == nil) + #expect(await repository.truncationCount == 0) + #expect(conversation.transcript == [original]) + #expect(!conversation.isHistoryChanging) + } + + @Test func truncationFailureLeavesInMemoryHistoryUnchanged() async throws { + let original = AgentTranscriptMessage(role: .user, text: "Before", authoredText: "Before") + let answer = AgentTranscriptMessage(role: .assistant, text: "Existing answer") + let conversation = ConversationModel( + messages: [ + ChatMessage(id: original.id, role: .user, text: original.text), + ChatMessage(id: answer.id, role: .assistant, text: answer.text), + ], + transcript: [original, answer]) + let originalMessages = conversation.messages + let originalTranscript = conversation.transcript + let replacement = AgentTranscriptMessage(role: .user, text: "After", authoredText: "After") + let operations = AssistantConversationOperations(repository: RejectingConversationRepository()) + + await #expect(throws: CocoaError.self) { + try await operations.edit( + conversation, messageID: original.id, replacement: replacement) + } + + #expect(conversation.messages == originalMessages) + #expect(conversation.transcript == originalTranscript) + #expect(!conversation.isHistoryChanging) + } +} + +private actor TruncationSpyAgentRunRepository: AgentRunPersisting { + private(set) var truncationCount = 0 + + func save(_ snapshot: AgentRunSnapshot) {} + func record(_ event: AgentEvent, runID: UUID) {} + func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} + func loadConversations() -> [StoredAgentConversation] { [] } + func deleteConversation(_ id: UUID) {} + func setTitle(_ title: String, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} + func truncateConversation( + _ id: UUID, fromUserMessageID messageID: UUID, + replacingWith message: AgentTranscriptMessage + ) { + truncationCount += 1 + } + func createConversation(_ conversation: StoredAgentConversation) {} } private actor RejectingConversationRepository: AgentRunPersisting { diff --git a/FrameworkTests/AssistantExecutionControllerTests.swift b/FrameworkTests/AssistantExecutionControllerTests.swift index 48e42649..fef4d96e 100644 --- a/FrameworkTests/AssistantExecutionControllerTests.swift +++ b/FrameworkTests/AssistantExecutionControllerTests.swift @@ -81,6 +81,7 @@ struct AssistantExecutionControllerTests { try await waitUntil { !conversation.isPreparingAgentRun } #expect(conversation.runState == .failed) + await repository.waitUntilSnapshot(state: .failed) #expect(await repository.snapshots.last?.state == .failed) } @@ -249,6 +250,7 @@ struct AssistantExecutionControllerTests { await memoryStore.releaseSnapshot() try await waitUntil { !preparing.isPreparingAgentRun } #expect(preparing.runState == .failed) + await repository.waitUntilSnapshot(state: .failed) #expect(await repository.snapshots.last?.state == .failed) } @@ -492,6 +494,43 @@ struct AssistantExecutionControllerTests { #expect(edited.contextSnapshot?.sessionContext?.contains("captured-host") == true) #expect(edited.contextSnapshot?.planContract == TermindAgentPolicy.planContract) #expect(conversation.runState == .failed) + let failed = try #require(conversation.runSummaries.values.first) + #expect(conversation.runSummaries.count == 1) + #expect(failed.state == .failed) + #expect(failed.id != edited.id) + #expect(conversation.messageRunIDs[edited.id] == failed.id) + #expect(conversation.runSummaries[edited.id] == nil) + } + + @Test + func cancellationAfterSuccessfulEditAdoptsTheInterruptedRunProvenance() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository() + let controller = makeController(repository: repository, memory: memory) + let operations = AssistantConversationOperations(repository: repository) + let message = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: message.id, role: .user, text: "before")], transcript: [message]) + let prepared = try #require( + controller.prepareAgentEdit(conversation, messageID: message.id, text: "after")) + + #expect(try await controller.performAgentEdit(prepared, in: conversation, operations: operations)) + let edited = try #require(conversation.transcript.first) + #expect(conversation.messageRunIDs[edited.id] == edited.id) + await memoryStore.waitUntilSnapshotEntered() + + let cancellation = controller.cancelPendingRuns() + await memoryStore.releaseSnapshot() + await cancellation.value + + let interrupted = try #require(await repository.snapshots.last) + #expect(interrupted.state == .interrupted) + #expect(conversation.runState == .interrupted) + #expect(conversation.messageRunIDs[edited.id] == interrupted.id) + #expect(conversation.runSummaries[edited.id] == nil) + #expect(conversation.runSummaries[interrupted.id]?.state == .interrupted) } private func makeController( @@ -604,6 +643,7 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { private var saveReleased = false private var saveEntryWaiters: [CheckedContinuation] = [] private var saveReleaseWaiters: [CheckedContinuation] = [] + private var snapshotWaiters: [(state: AgentRunState, continuation: CheckedContinuation)] = [] private var truncationEntered = false private var truncationReleased = false private var truncationEntryWaiters: [CheckedContinuation] = [] @@ -620,6 +660,15 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { func save(_ snapshot: AgentRunSnapshot) async { snapshots.append(snapshot) + var pendingSnapshotWaiters: [(state: AgentRunState, continuation: CheckedContinuation)] = [] + for waiter in snapshotWaiters { + if waiter.state == snapshot.state { + waiter.continuation.resume() + } else { + pendingSnapshotWaiters.append(waiter) + } + } + snapshotWaiters = pendingSnapshotWaiters guard blocksFirstSave, snapshots.count == 1 else { return } saveEntered = true let entryWaiters = saveEntryWaiters @@ -635,6 +684,11 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { await withCheckedContinuation { saveEntryWaiters.append($0) } } + func waitUntilSnapshot(state: AgentRunState) async { + if snapshots.contains(where: { $0.state == state }) { return } + await withCheckedContinuation { snapshotWaiters.append((state, $0)) } + } + func releaseSave() { saveReleased = true let waiters = saveReleaseWaiters diff --git a/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift b/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift index 39d82313..a1ef8e4e 100644 --- a/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift +++ b/Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift @@ -131,7 +131,7 @@ public final class AssistantConversationOperations { replacement: AgentTranscriptMessage ) async throws -> ConversationModel.ComposedUserTurn? { try Task.checkCancellation() - guard + guard conversation.canApplyUserMessageEdit(messageID, replacement: replacement), pending.insert(conversation.id).inserted else { return nil } conversation.isHistoryChanging = true @@ -143,6 +143,6 @@ public final class AssistantConversationOperations { try await repository.truncateConversation( conversation.id, fromUserMessageID: messageID, replacingWith: replacement) conversation.isHistoryChanging = false - return conversation.editUserMessage(messageID, replacement: replacement) + return conversation.applyPersistedUserMessageEdit(messageID, replacement: replacement) } } diff --git a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift index 94d38bcb..b5432284 100644 --- a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift +++ b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift @@ -723,12 +723,13 @@ public final class AssistantExecutionController { if state.recordsInterruption { let now = Date.now do { - try await agentRunRepository.save( - AgentRunSnapshot( - conversationID: conversation.id, state: .interrupted, - permissionMode: capture.permissionMode, - messages: conversation.transcript, startedAt: now, finishedAt: now)) + let snapshot = AgentRunSnapshot( + conversationID: conversation.id, state: .interrupted, + permissionMode: capture.permissionMode, + messages: conversation.transcript, startedAt: now, finishedAt: now) + try await agentRunRepository.save(snapshot) guard preparations[conversation.id]?.id == capture.id else { return } + conversation.adoptStagedReplacementRun(snapshot) conversation.handle(.runState(.interrupted)) } catch { guard preparations[conversation.id]?.id == capture.id else { return } @@ -745,12 +746,13 @@ public final class AssistantExecutionController { if state.recordsInterruption { let now = Date.now do { - try await agentRunRepository.save( - AgentRunSnapshot( - conversationID: conversation.id, state: .interrupted, - permissionMode: state.permissionMode, - messages: conversation.transcript, startedAt: now, finishedAt: now)) + let snapshot = AgentRunSnapshot( + conversationID: conversation.id, state: .interrupted, + permissionMode: state.permissionMode, + messages: conversation.transcript, startedAt: now, finishedAt: now) + try await agentRunRepository.save(snapshot) guard preparations[conversation.id]?.id == preparationID else { return } + conversation.adoptStagedReplacementRun(snapshot) conversation.handle(.runState(.interrupted)) } catch { guard preparations[conversation.id]?.id == preparationID else { return } diff --git a/Frameworks/TermindApplication/Agent/ConversationModel.swift b/Frameworks/TermindApplication/Agent/ConversationModel.swift index 4a680a0e..74ea0208 100644 --- a/Frameworks/TermindApplication/Agent/ConversationModel.swift +++ b/Frameworks/TermindApplication/Agent/ConversationModel.swift @@ -1067,20 +1067,79 @@ public final class ConversationModel: Identifiable { ) } + public func canApplyUserMessageEdit( + _ id: UUID, replacement: AgentTranscriptMessage + ) -> Bool { + guard canEditUserMessage(id), replacement.role == .user, + !replacement.isCompaction, replacement.toolCalls.isEmpty, + replacement.toolCallID == nil, replacement.toolName == nil, + !replacement.isError, replacement.id != id, + !transcript.contains(where: { $0.id == replacement.id }) + else { return false } + return true + } + public func editUserMessage( _ id: UUID, replacement: AgentTranscriptMessage ) -> ComposedUserTurn? { - guard canEditUserMessage(id), + guard canApplyUserMessageEdit(id, replacement: replacement) else { return nil } + return applyPersistedUserMessageEdit(id, replacement: replacement) + } + + func applyPersistedUserMessageEdit( + _ id: UUID, replacement: AgentTranscriptMessage + ) -> ComposedUserTurn? { + guard replacement.role == .user, !replacement.isCompaction, + replacement.toolCalls.isEmpty, replacement.toolCallID == nil, + replacement.toolName == nil, !replacement.isError, + replacement.id != id, + !transcript.contains(where: { $0.id == replacement.id }), let target = transcript.firstIndex(where: { $0.id == id }) else { return nil } let original = transcript[target] - let removedMessageIDs = Set(transcript[target...].map(\.id)) - let removedRunIDs = Set(removedMessageIDs.compactMap { messageRunIDs[$0] }) + guard original.role == .user, !original.isCompaction else { return nil } + let prefix = Array(transcript[.. targetSummary.startedAt ? summary.id : nil + } + return summary.id.uuidString > targetSummary.id.uuidString ? summary.id : nil + }) + } + let hasEarlierVisibleUser = + visibleTarget.map { index in + messages[..= original.createdAt + if whollyRemovedRunIDs.contains($0.invocation.runID) { return true } + if let source = $0.invocation.sourceMessageID { + if removedMessageIDs.contains(source) { return true } + if keptMessageIDs.contains(source) { return false } + } + if removedCallIDs.contains($0.invocation.call.id) { return true } + if keptCallIDs.contains($0.invocation.call.id) { return false } + let resultMessageID = AgentTranscriptMessage.toolResultID( + runID: $0.invocation.runID, callID: $0.invocation.call.id) + if messageRunIDs[resultMessageID] == $0.invocation.runID, + !removedMessageIDs.contains(resultMessageID) + { + return false + } + if $0.invocation.runID != targetRunID { + if prefixRunIDs.contains($0.invocation.runID) { return false } + if removedRunIDs.contains($0.invocation.runID) { return true } + } + // Legacy rows with no recoverable source, call, or result ownership + // belong to the edited suffix at the boundary as well as after it. + return $0.invocation.createdAt >= original.createdAt } for messageID in removedMessageIDs { messageRunIDs[messageID] = nil } - for runID in removedRunIDs { runSummaries[runID] = nil } + for runID in removedRunIDs { + guard prefixRunIDs.contains(runID) else { + runSummaries[runID] = nil + continue + } + let lastPrefixDate = + visiblePrefix.last(where: { messageRunIDs[$0.id] == runID })?.createdAt + ?? prefix.last(where: { + messageRunIDs[$0.id] == runID && $0.role != .tool + })?.createdAt + ?? prefix.last(where: { messageRunIDs[$0.id] == runID })?.createdAt + ?? original.createdAt + let startedAt = runSummaries[runID]?.startedAt ?? lastPrefixDate + runSummaries[runID] = AgentRunSummary( + id: runID, state: .completed, startedAt: startedAt, + finishedAt: lastPrefixDate) + } + messageRunIDs[replacement.id] = replacement.id + runSummaries[replacement.id] = AgentRunSummary( + id: replacement.id, state: .completed, + startedAt: stagingStartedAt, finishedAt: stagingStartedAt) plans.removeAll { $0.createdAt >= original.createdAt } - compactions.removeAll { $0.createdAt >= original.createdAt } + compactionRecords.removeAll { record in + guard let boundary = record.compactedThroughMessageID else { return false } + return removedMessageIDs.contains(boundary) + } + compactions = compactionRecords.map(AgentCompactionMark.init) tasks.removeAll() usage = nil contextEstimateTracker.setMeasured(nil) publishContextEstimateImmediately() reportedRunState = .idle updatedAt = replacement.createdAt - if !transcript[.. Bool { + guard messageRunIDs[promptID] == promptID, + let staging = runSummaries[promptID], staging.state == .completed, + staging.failure == nil, + messageRunIDs.filter({ $0.value == promptID }).count == 1, + !toolCards.contains(where: { $0.invocation.runID == promptID }) + else { return false } + if summary.startedAt < staging.startedAt { + summary.startedAt = staging.startedAt + } + if let finishedAt = summary.finishedAt, finishedAt < summary.startedAt { + summary.finishedAt = summary.startedAt + } + runSummaries[promptID] = nil + messageRunIDs[promptID] = summary.id + runSummaries[summary.id] = summary + return true + } + /// Retains the ordered event consumer until the cancellation snapshot is durable. public func stopAndWait() async { await cancelPendingAgentPreparationAndWait() @@ -2484,14 +2623,23 @@ public final class ConversationModel: Identifiable { // them — but they are listed rather than swept into a `default` so // adding an event stays a compile error here. case .runStarted(let run): - activeRunID = run.id - runSummaries[run.id] = run + var adoptedRun = run + activeRunID = adoptedRun.id if let prompt = transcript.last, prompt.role == .user, messageRunIDs[prompt.id] == nil + || adoptStagedReplacementRun(promptID: prompt.id, summary: &adoptedRun) { - messageRunIDs[prompt.id] = run.id + messageRunIDs[prompt.id] = adoptedRun.id } + runSummaries[adoptedRun.id] = adoptedRun case .runFinished(let run): + var run = run + if let current = runSummaries[run.id], run.startedAt < current.startedAt { + run.startedAt = current.startedAt + } + if let finishedAt = run.finishedAt, finishedAt < run.startedAt { + run.finishedAt = run.startedAt + } publishAllStagedTools() settleStreamingPresentation(for: run.id) runSummaries[run.id] = run @@ -2645,6 +2793,7 @@ public final class ConversationModel: Identifiable { role: .tool, text: result.content, toolCallID: result.callID, toolName: invocation.call.name, isError: result.isError, modelText: result.modelContent ) + messageRunIDs[message.id] = invocation.runID transcript.append(message) contextEstimateTracker.upsert(message) publishContextEstimateImmediately() diff --git a/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift b/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift index 31e7aae1..6632ff25 100644 --- a/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift +++ b/Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift @@ -19,60 +19,102 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { deletedRunIDs.insert(snapshot.id) return } - let runID = snapshot.id - let runDescriptor = FetchDescriptor(predicate: #Predicate { $0.id == runID }) - if let row = try context.fetch(runDescriptor).first { - row.update(snapshot) - } else { - context.insert(PersistentAgentRun(snapshot)) - } + guard !deletedRunIDs.contains(snapshot.id) else { return } + let previousDeletedRunIDs = deletedRunIDs + do { + let runID = snapshot.id + let runDescriptor = FetchDescriptor(predicate: #Predicate { $0.id == runID }) + let runRow: PersistentAgentRun + if let row = try context.fetch(runDescriptor).first { + let persistedStartedAt = row.startedAt + row.update(snapshot) + if row.startedAt < persistedStartedAt { + row.startedAt = persistedStartedAt + } + if let finishedAt = row.finishedAt, finishedAt < row.startedAt { + row.finishedAt = row.startedAt + } + runRow = row + } else { + let row = PersistentAgentRun(snapshot) + context.insert(row) + runRow = row + } - let conversationID = snapshot.conversationID - let conversationDescriptor = FetchDescriptor( - predicate: #Predicate { $0.id == conversationID } - ) - // No title seeding here. `title` means "the generated summary", and - // empty means there is not one yet — which is what lets the UI decide to - // fall back to the opening line rather than having that line frozen into - // storage as though it were a name someone chose. - if let conversation = try context.fetch(conversationDescriptor).first { - conversation.updatedAt = .now - } else { - context.insert( - PersistentAgentConversation( - id: conversationID, title: "", createdAt: snapshot.startedAt - )) - } + let conversationID = snapshot.conversationID + let conversationDescriptor = FetchDescriptor( + predicate: #Predicate { $0.id == conversationID } + ) + // No title seeding here. `title` means "the generated summary", and + // empty means there is not one yet — which is what lets the UI decide to + // fall back to the opening line rather than having that line frozen into + // storage as though it were a name someone chose. + if let conversation = try context.fetch(conversationDescriptor).first { + conversation.updatedAt = .now + } else { + context.insert( + PersistentAgentConversation( + id: conversationID, title: "", createdAt: snapshot.startedAt + )) + } - // Matched on message id, not on this run's id. A conversation's earlier - // turns are replayed into every later run, so filtering by `runID` made - // each save re-insert the whole history — colliding with the unique - // constraint and rewriting rows that were already correct. - let durableMessages = snapshot.messages.filter { !$0.isCompaction } - let ids = Set(durableMessages.map(\.id)) - let stored = try context.fetch( - FetchDescriptor( - predicate: #Predicate { ids.contains($0.id) } - )) - let storedIDs = Set(stored.map(\.id)) - var inserted = false - for (sequence, message) in durableMessages.enumerated() - where !storedIDs.contains(message.id) { - context.insert( - PersistentAgentMessage( - message, runID: runID, sequence: sequence + // Matched on message id, not on this run's id. A conversation's earlier + // turns are replayed into every later run, so filtering by `runID` made + // each save re-insert the whole history — colliding with the unique + // constraint and rewriting rows that were already correct. + let durableMessages = snapshot.messages.filter { !$0.isCompaction } + let ids = Set(durableMessages.map(\.id)) + let stored = try context.fetch( + FetchDescriptor( + predicate: #Predicate { ids.contains($0.id) } )) - insertContextAttachments(for: message) - inserted = true + var storedByID = Dictionary(uniqueKeysWithValues: stored.map { ($0.id, $0) }) + if let prompt = durableMessages.last(where: { $0.role == .user }), + let stagedMessage = storedByID[prompt.id], + stagedMessage.runID == prompt.id, prompt.id != runID, + try isStagingReplacementRun(prompt.id, message: stagedMessage, conversationID: conversationID) + { + stagedMessage.runID = runID + stagedMessage.sequence = durableMessages.firstIndex(where: { $0.id == prompt.id }) ?? 0 + let stagingID = prompt.id + let stagingDescriptor = FetchDescriptor( + predicate: #Predicate { $0.id == stagingID } + ) + if let stagingRun = try context.fetch(stagingDescriptor).first { + if runRow.startedAt < stagingRun.startedAt { + runRow.startedAt = stagingRun.startedAt + } + if let finishedAt = runRow.finishedAt, finishedAt < runRow.startedAt { + runRow.finishedAt = runRow.startedAt + } + context.delete(stagingRun) + } + deletedRunIDs.insert(stagingID) + } + let storedIDs = Set(storedByID.keys) + var inserted = false + for (sequence, message) in durableMessages.enumerated() + where !storedIDs.contains(message.id) { + let row = PersistentAgentMessage(message, runID: runID, sequence: sequence) + context.insert(row) + storedByID[message.id] = row + insertContextAttachments(for: message) + inserted = true + } + // A user row may already exist (editing or interrupted preparation) before + // the run freezes its context. Add its backing once without rewriting history. + let replayIDs = Set( + try context.fetch(FetchDescriptor()).map(\.messageID)) + for message in durableMessages where !replayIDs.contains(message.id) { + insertReplay(for: message) + } + guard inserted || context.hasChanges else { return } + try context.save() + } catch { + context.rollback() + deletedRunIDs = previousDeletedRunIDs + throw error } - // A user row may already exist (editing or interrupted preparation) before - // the run freezes its context. Add its backing once without rewriting history. - let replayIDs = Set(try context.fetch(FetchDescriptor()).map(\.messageID)) - for message in durableMessages where !replayIDs.contains(message.id) { - insertReplay(for: message) - } - guard inserted || context.hasChanges else { return } - try context.save() } @MainActor @@ -174,7 +216,7 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { let reviews = try context.fetch(FetchDescriptor()) return conversations.map { conversation in let matchingRuns = runs.filter { $0.conversationID == conversation.id } - .sorted { $0.startedAt < $1.startedAt } + .sorted(by: Self.runPrecedes) let runIDs = Set(matchingRuns.map(\.id)) // Ordered by run first, then by time within it. Sorting the whole // conversation on `createdAt` alone let two runs interleave whenever @@ -184,24 +226,7 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { uniqueKeysWithValues: matchingRuns.enumerated().map { ($0.element.id, $0.offset) } ) let transcript = messages.filter { runIDs.contains($0.runID) } - .sorted { lhs, rhs in - let left = runOrder[lhs.runID] ?? 0 - let right = runOrder[rhs.runID] ?? 0 - if left != right { return left < right } - if lhs.sequence != rhs.sequence { return lhs.sequence < rhs.sequence } - if lhs.createdAt != rhs.createdAt { return lhs.createdAt < rhs.createdAt } - // Transcript grammar gives equal-position, equal-time rows - // a stable and useful final ordering. - let rank = { (role: String) -> Int in - switch AgentTranscriptRole(rawValue: role) { - case .user: 0 - case .assistant: 1 - case .tool: 2 - case nil: 3 - } - } - return rank(lhs.role) < rank(rhs.role) - } + .sorted { lhs, rhs in Self.messagePrecedes(lhs, rhs, runOrder: runOrder) } .compactMap { row -> AgentTranscriptMessage? in guard let role = AgentTranscriptRole(rawValue: row.role) else { return nil } let calls = @@ -230,22 +255,48 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { createdAt: row.createdAt ) } - let messageRunIDs = Dictionary( + var messageRunIDs = Dictionary( uniqueKeysWithValues: messages.filter { runIDs.contains($0.runID) } .map { ($0.id, $0.runID) } ) let state = matchingRuns.last.flatMap { AgentRunState(rawValue: $0.state) } ?? .completed + var sourceMessageIDsByRun: [UUID: [String: UUID]] = [:] + for message in transcript where message.role == .assistant { + guard let runID = messageRunIDs[message.id] else { continue } + for call in message.toolCalls { + sourceMessageIDsByRun[runID, default: [:]][call.id] = message.id + } + } let cards = invocations.filter { runIDs.contains($0.runID) } .sorted { $0.createdAt < $1.createdAt } - .compactMap { storedToolCard($0, results: results, reviews: reviews) } + .compactMap { row -> StoredAgentToolCard? in + guard var card = storedToolCard(row, results: results, reviews: reviews) else { + return nil + } + // Older invocation rows predate source-message ownership. The + // raw transcript still has the exact run-local call grammar, + // so project that identity on restore instead of guessing by time. + if card.invocation.sourceMessageID == nil { + card.invocation.sourceMessageID = sourceMessageIDsByRun[row.runID]?[row.callID] + } + return card + } + let matchingResults = results.filter { runIDs.contains($0.runID) } + let recoveredTranscript = Self.recovering( + transcript, results: matchingResults, + invocations: invocations.filter { runIDs.contains($0.runID) }) + let recoveredMessageIDs = Set(recoveredTranscript.map(\.id)) + for result in matchingResults { + let messageID = AgentTranscriptMessage.toolResultID( + runID: result.runID, callID: result.callID) + if recoveredMessageIDs.contains(messageID) { + messageRunIDs[messageID] = result.runID + } + } let projection = AgentCompactionProjection.restore( - messages: Self.recovering( - transcript, - results: results.filter { runIDs.contains($0.runID) }, - invocations: invocations.filter { runIDs.contains($0.runID) } - ), + messages: recoveredTranscript, records: compactionRows.filter { $0.conversationID == conversation.id } .map(\.value) ) @@ -355,28 +406,65 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { replacingWith replacement: AgentTranscriptMessage ) async throws { try Task.checkCancellation() + guard replacement.role == .user, !replacement.isCompaction, + replacement.toolCalls.isEmpty, replacement.toolCallID == nil, + replacement.toolName == nil, !replacement.isError, + replacement.id != messageID + else { throw AgentConversationPersistenceError.messageNotFound } let allRuns = try context.fetch(FetchDescriptor()) let runs = allRuns.filter { $0.conversationID == id } - .sorted { $0.startedAt < $1.startedAt } + .sorted(by: Self.runPrecedes) let allMessages = try context.fetch(FetchDescriptor()) + let runIDs = Set(runs.map(\.id)) + let runOrder = Dictionary( + uniqueKeysWithValues: runs.enumerated().map { ($0.element.id, $0.offset) }) + let conversationMessages = allMessages.filter { runIDs.contains($0.runID) } + .sorted { Self.messagePrecedes($0, $1, runOrder: runOrder) } guard - let target = allMessages.first(where: { message in - message.id == messageID && runs.contains { $0.id == message.runID } - }), - let targetIndex = runs.firstIndex(where: { $0.id == target.runID }) + !allMessages.contains(where: { $0.id == replacement.id }), + !allRuns.contains(where: { $0.id == replacement.id }), + let boundary = conversationMessages.firstIndex(where: { $0.id == messageID }), + conversationMessages[boundary].role == AgentTranscriptRole.user.rawValue, + !conversationMessages[boundary].isCompaction, + let targetRunIndex = runs.firstIndex(where: { $0.id == conversationMessages[boundary].runID }) else { throw AgentConversationPersistenceError.messageNotFound } - let removedRuns = Array(runs[targetIndex...]) - let removedRunIDs = Set(removedRuns.map(\.id)) - let removedMessageIDs = Set(allMessages.filter { removedRunIDs.contains($0.runID) }.map(\.id)) - deletedRunIDs.formUnion(removedRunIDs) - let earlierRunIDs = Set(runs[..()) where row.conversationID == id && row.compactedThroughMessageID.map(removedMessageIDs.contains) == true @@ -384,10 +472,17 @@ public final class SwiftDataAgentRunRepository: AgentRunPersisting, Sendable { context.delete(row) } + let lastKeptRun = removesTargetRun ? runs[..()) + where messageIDs.contains(row.messageID) { context.delete(row) } + for row in try context.fetch(FetchDescriptor()) + where messageIDs.contains(row.messageID) { context.delete(row) } + for row in messages { context.delete(row) } + } + + private func deleteTargetRunTools( + runID: UUID, keptMessages: [PersistentAgentMessage], + removedMessages: [PersistentAgentMessage], boundary: Date + ) throws { + let removedMessageIDs = Set(removedMessages.map(\.id)) + let keptMessageIDs = Set(keptMessages.map(\.id)) + let keptCallIDs = Set(keptMessages.flatMap(Self.toolCallIDs)) + let removedCallIDs = Set(removedMessages.flatMap(Self.toolCallIDs)) + let invocations = try context.fetch(FetchDescriptor()) + let removedInvocations = invocations.filter { invocation in + guard invocation.runID == runID else { return false } + if let source = invocation.sourceMessageID { + if removedMessageIDs.contains(source) { return true } + if keptMessageIDs.contains(source) { return false } + } + if removedCallIDs.contains(invocation.callID) { return true } + if keptCallIDs.contains(invocation.callID) { return false } + // Legacy rows with no recoverable source or call ownership belong + // to the edited suffix at the boundary as well as after it. + return invocation.createdAt >= boundary + } + let invocationIDs = Set(removedInvocations.map(\.id)) + for row in try context.fetch(FetchDescriptor()) + where row.runID == runID && invocationIDs.contains(row.invocationID) { context.delete(row) } + for row in try context.fetch(FetchDescriptor()) + where row.runID == runID && invocationIDs.contains(row.invocationID) { context.delete(row) } + for row in removedInvocations { context.delete(row) } + } + + private func isStagingReplacementRun( + _ id: UUID, message: PersistentAgentMessage, conversationID: UUID + ) throws -> Bool { + let runDescriptor = FetchDescriptor(predicate: #Predicate { $0.id == id }) + guard let run = try context.fetch(runDescriptor).first, + run.conversationID == conversationID, run.state == AgentRunState.completed.rawValue, + run.failure == nil, message.id == id, message.runID == id, + message.role == AgentTranscriptRole.user.rawValue, !message.isCompaction + else { return false } + let messages = try context.fetch(FetchDescriptor()) + guard messages.filter({ $0.runID == id }).count == 1 else { return false } + let invocations = try context.fetch(FetchDescriptor()) + let results = try context.fetch(FetchDescriptor()) + let reviews = try context.fetch(FetchDescriptor()) + return !invocations.contains { $0.runID == id } + && !results.contains { $0.runID == id } + && !reviews.contains { $0.runID == id } + } + + private static func runPrecedes(_ lhs: PersistentAgentRun, _ rhs: PersistentAgentRun) -> Bool { + if lhs.startedAt != rhs.startedAt { return lhs.startedAt < rhs.startedAt } + return lhs.id.uuidString < rhs.id.uuidString + } + + private static func messagePrecedes( + _ lhs: PersistentAgentMessage, _ rhs: PersistentAgentMessage, runOrder: [UUID: Int] + ) -> Bool { + let left = runOrder[lhs.runID] ?? 0 + let right = runOrder[rhs.runID] ?? 0 + if left != right { return left < right } + if lhs.sequence != rhs.sequence { return lhs.sequence < rhs.sequence } + if lhs.createdAt != rhs.createdAt { return lhs.createdAt < rhs.createdAt } + let rank: (String) -> Int = { role in + switch AgentTranscriptRole(rawValue: role) { + case .user: 0 + case .assistant: 1 + case .tool: 2 + case nil: 3 + } + } + if rank(lhs.role) != rank(rhs.role) { return rank(lhs.role) < rank(rhs.role) } + return lhs.id.uuidString < rhs.id.uuidString + } + + private static func toolCallIDs(_ message: PersistentAgentMessage) -> [String] { + ((try? JSONDecoder().decode([AgentToolCall].self, from: message.toolCallsData)) ?? []).map(\.id) + } + private func insertReplay(for message: AgentTranscriptMessage) { guard message.contextSnapshot != nil || message.modelText != nil else { return } context.insert(PersistentAgentMessageReplay(message)) diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 42a66c19..511cce54 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -5,6 +5,7 @@ import SwiftUI import TermindApplication import TermindDomain import TermindInfrastructure +import TermindPersistence import TermindPresentation import TermindRemoteInfrastructure import Testing @@ -150,6 +151,53 @@ struct MobileAssistantTests { } } + @Test func swiftDataRestoreAndEditPreserveASameRunPrefixOnMobile() async throws { + let container = try TermindPersistence.makeContainer(inMemory: true) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let instant = Date(timeIntervalSinceReferenceDate: 100) + let runID = try #require(UUID(uuidString: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + let replacementID = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) + let opening = AgentTranscriptMessage( + role: .user, text: "Opening", authoredText: "Opening", createdAt: instant) + let prefix = AgentTranscriptMessage( + role: .assistant, text: "Prefix", createdAt: instant) + let steering = AgentTranscriptMessage( + role: .user, text: "Steer", authoredText: "Steer", createdAt: instant) + let suffix = AgentTranscriptMessage( + role: .assistant, text: "Suffix", createdAt: instant) + try await repository.save( + AgentRunSnapshot( + id: runID, conversationID: UUID(), state: .failed, + permissionMode: .askForApproval, + messages: [opening, prefix, steering, suffix], + startedAt: instant, finishedAt: instant, failure: "old failure")) + let store = AssistantConversationStore() + store.restore(try await repository.loadConversations()) + let conversation = try #require(store.conversations.first) + let replacement = AgentTranscriptMessage( + id: replacementID, role: .user, text: "Replacement", authoredText: "Replacement", + createdAt: instant) + + let operations = AssistantConversationOperations(repository: repository) + #expect( + try await operations.edit( + conversation, messageID: steering.id, replacement: replacement) != nil) + + #expect(conversation.messages.map(\.text) == ["Opening", "Prefix", "Replacement"]) + #expect(conversation.messageRunIDs[opening.id] == runID) + #expect(conversation.messageRunIDs[prefix.id] == runID) + #expect(conversation.messageRunIDs[replacement.id] == replacement.id) + #expect(try #require(conversation.runSummaries[replacement.id]).startedAt > instant) + let reopened = try #require(try await repository.loadConversations().first) + #expect(reopened.messages.map(\.text) == ["Opening", "Prefix", "Replacement"]) + #expect(reopened.messageRunIDs[opening.id] == runID) + #expect(reopened.messageRunIDs[prefix.id] == runID) + #expect(reopened.messageRunIDs[replacement.id] == replacement.id) + #expect(reopened.runs.map(\.id) == [runID, replacement.id]) + #expect(reopened.runs.first { $0.id == runID }?.state == .completed) + #expect(reopened.runs.first { $0.id == runID }?.failure == nil) + } + @Test func changingConversationsPreservesTheirDraftsAndArchiveNavigation() async throws { let app = try MobileAppModel(inMemory: true) let model = app.assistant.makeSceneModel() diff --git a/TFMTests/AgentPersistenceTests.swift b/TFMTests/AgentPersistenceTests.swift index 00071731..3c169e87 100644 --- a/TFMTests/AgentPersistenceTests.swift +++ b/TFMTests/AgentPersistenceTests.swift @@ -868,6 +868,266 @@ struct AgentPersistenceTests { #expect(restored.messages.map(\.text) == ["One", "Answer one", "Edited"]) } + @Test + func sameRunTruncationPreservesThePrefixAndAtomicallyAdoptsTheReplacement() async throws { + let configuration = localModelConfiguration( + "AgentSameRunTruncationTests", schema: HostPersistence.schema, + isStoredInMemoryOnly: true + ) + let container = try ModelContainer( + for: HostPersistence.schema, configurations: [configuration] + ) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let conversationID = UUID() + let oldRunID = try #require(UUID(uuidString: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + let replacementID = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) + let realRunID = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000002")) + let instant = Date(timeIntervalSinceReferenceDate: 100) + let prefixAttachment = AgentContextAttachment( + kind: .file, label: "prefix.txt", contentTypeIdentifier: "public.plain-text", + suggestedFilename: "prefix.txt", data: Data("prefix".utf8)) + let removedAttachment = AgentContextAttachment( + kind: .file, label: "removed.txt", contentTypeIdentifier: "public.plain-text", + suggestedFilename: "removed.txt", data: Data("removed".utf8)) + let replacementAttachment = AgentContextAttachment( + kind: .file, label: "replacement.txt", contentTypeIdentifier: "public.plain-text", + suggestedFilename: "replacement.txt", data: Data("replacement".utf8)) + let opening = AgentTranscriptMessage( + role: .user, text: "Opening", authoredText: "Opening", + contextAttachments: [prefixAttachment], + contextSnapshot: .init(sessionContext: "prefix context"), createdAt: instant) + let prefixCall = AgentToolCall( + id: "prefix-call", name: "inspect", arguments: .object([:])) + let prefixAssistant = AgentTranscriptMessage( + role: .assistant, text: "Prefix", toolCalls: [prefixCall], createdAt: instant) + let prefixResult = AgentTranscriptMessage( + role: .tool, text: "prefix result", toolCallID: prefixCall.id, + toolName: prefixCall.name, modelText: "prefix preview", createdAt: instant) + let steering = AgentTranscriptMessage( + role: .user, text: "Steer", authoredText: "Steer", + contextAttachments: [removedAttachment], + contextSnapshot: .init(sessionContext: "removed context"), createdAt: instant) + let suffixCall = AgentToolCall( + id: "suffix-call", name: "inspect", arguments: .object([:])) + let suffixAssistant = AgentTranscriptMessage( + role: .assistant, text: "Suffix", toolCalls: [suffixCall], createdAt: instant) + let suffixResult = AgentTranscriptMessage( + role: .tool, text: "suffix result", toolCallID: suffixCall.id, + toolName: suffixCall.name, modelText: "suffix preview", createdAt: instant) + let oldMessages = [ + opening, prefixAssistant, prefixResult, steering, suffixAssistant, suffixResult, + ] + let replacement = AgentTranscriptMessage( + id: replacementID, role: .user, text: "Replacement", authoredText: "Replacement", + contextAttachments: [replacementAttachment], + contextSnapshot: .init(sessionContext: "replacement context"), createdAt: instant) + let oldSummary = AgentRunSummary( + id: oldRunID, state: .failed, startedAt: instant, + finishedAt: instant, failure: "old failure") + let live = ConversationModel( + messages: [ + ChatMessage(id: opening.id, role: .user, text: opening.text, createdAt: instant), + ChatMessage( + id: prefixAssistant.id, role: .assistant, text: prefixAssistant.text, + createdAt: instant), + ChatMessage(id: steering.id, role: .user, text: steering.text, createdAt: instant), + ChatMessage( + id: suffixAssistant.id, role: .assistant, text: suffixAssistant.text, + createdAt: instant), + ], transcript: oldMessages, runState: .failed, + messageRunIDs: Dictionary(uniqueKeysWithValues: oldMessages.map { ($0.id, oldRunID) }), + runs: [oldSummary]) + #expect(live.editUserMessage(steering.id, replacement: replacement) != nil) + let livePrefixFinishedAt = try #require(live.runSummaries[oldRunID]?.finishedAt) + + let oldSnapshot = AgentRunSnapshot( + id: oldRunID, conversationID: conversationID, state: .failed, + permissionMode: .askForApproval, messages: oldMessages, + startedAt: instant, finishedAt: instant, failure: "old failure") + try await repository.save(oldSnapshot) + try await repository.setTitle("Preserved title", conversationID: conversationID) + + let descriptor = AgentToolDescriptor( + name: "inspect", summary: "Inspect", inputSchema: .object([:]), + target: .local, approvalPolicy: .approve) + let prefixInvocation = AgentToolInvocation( + runID: oldRunID, call: prefixCall, sourceMessageID: nil, + createdAt: instant.addingTimeInterval(1)) + let suffixInvocation = AgentToolInvocation( + runID: oldRunID, call: suffixCall, + sourceMessageID: suffixAssistant.id, createdAt: instant) + let review = GuardianDecision( + verdict: .approve, risk: .low, userAuthorization: .high, reason: "Allowed") + for invocation in [prefixInvocation, suffixInvocation] { + try await repository.record(.toolProposed(invocation, descriptor), runID: oldRunID) + try await repository.record( + .toolFinished( + invocation, + AgentToolResult(callID: invocation.call.id, content: "recorded result")), + runID: oldRunID) + let request = AgentApprovalRequest( + invocation: invocation, descriptor: descriptor, userIntent: "Inspect") + try await repository.record(.reviewFinished(request, review), runID: oldRunID) + } + // Provider-native tools can finish durably before their synthetic assistant + // source message reaches a snapshot. Its unknown source must fall through + // to the conservative timestamp rule when this steering suffix is edited. + let orphanInvocation = AgentToolInvocation( + runID: oldRunID, + call: AgentToolCall(id: "native-call", name: "web_search", arguments: .object([:])), + sourceMessageID: UUID(), createdAt: instant) + try await repository.record(.toolProposed(orphanInvocation, descriptor), runID: oldRunID) + try await repository.record( + .toolFinished( + orphanInvocation, + AgentToolResult(callID: orphanInvocation.call.id, content: "native result")), + runID: oldRunID) + + let nilBoundary = AgentCompactionRecord( + compactedThroughMessageID: nil, sequence: 0, summary: "Legacy base", + summarizedTokens: 4, keptTokens: 1, displayAnchor: instant) + let keptCompaction = AgentCompactionRecord( + compactedThroughMessageID: prefixResult.id, sequence: 1, summary: "Kept prefix", + summarizedTokens: 8, keptTokens: 2, displayAnchor: instant) + for record in [nilBoundary, keptCompaction] { + try await repository.persistCompaction(record, conversationID: conversationID) + } + + let preEditStore = AssistantConversationStore() + preEditStore.restore(try await repository.loadConversations()) + let persistedLive = try #require(preEditStore.conversations.first) + #expect( + Set(persistedLive.toolCards.map(\.invocation.id)) == [ + prefixInvocation.id, suffixInvocation.id, orphanInvocation.id, + ]) + let operations = AssistantConversationOperations(repository: repository) + #expect( + try await operations.edit( + persistedLive, messageID: steering.id, replacement: replacement) != nil) + #expect(persistedLive.toolCards.map(\.invocation.id) == [prefixInvocation.id]) + #expect(persistedLive.runSummaries[oldRunID]?.finishedAt == livePrefixFinishedAt) + + let staged = try #require(try await repository.loadConversations().first) + #expect(staged.messages.map(\.id) == [opening.id, prefixAssistant.id, prefixResult.id, replacement.id]) + #expect(staged.compactions.map(\.id) == [nilBoundary.id, keptCompaction.id]) + #expect(staged.title == "Preserved title") + #expect(staged.toolCards.map(\.invocation.id) == [prefixInvocation.id]) + #expect(staged.toolCards.first?.invocation.sourceMessageID == prefixAssistant.id) + #expect(staged.runs.map(\.id) == [oldRunID, replacement.id]) + let stagedPrefix = try #require(staged.runs.first { $0.id == oldRunID }) + #expect(stagedPrefix.state == .completed) + #expect(stagedPrefix.failure == nil) + #expect(stagedPrefix.finishedAt == livePrefixFinishedAt) + #expect(staged.messageRunIDs[opening.id] == oldRunID) + #expect(staged.messageRunIDs[prefixResult.id] == oldRunID) + #expect(staged.messageRunIDs[replacement.id] == replacement.id) + + let attachmentRows = try container.mainContext.fetch( + FetchDescriptor()) + let replayRows = try container.mainContext.fetch( + FetchDescriptor()) + #expect(Set(attachmentRows.map(\.messageID)) == [opening.id, replacement.id]) + #expect(Set(replayRows.map(\.messageID)) == [opening.id, prefixResult.id, replacement.id]) + #expect(try container.mainContext.fetchCount(FetchDescriptor()) == 1) + #expect(try container.mainContext.fetchCount(FetchDescriptor()) == 1) + #expect(try container.mainContext.fetchCount(FetchDescriptor()) == 1) + + var realSnapshot = AgentRunSnapshot( + id: realRunID, conversationID: conversationID, state: .running, + permissionMode: .askForApproval, + messages: [opening, prefixAssistant, prefixResult, replacement], + startedAt: instant) + try await repository.save(realSnapshot) + let adopted = try #require(try await repository.loadConversations().first) + #expect(adopted.runs.map(\.id) == [oldRunID, realRunID]) + #expect(try #require(adopted.runs.first { $0.id == realRunID }).startedAt > instant) + realSnapshot.state = .completed + realSnapshot.finishedAt = instant.addingTimeInterval(1) + try await repository.save(realSnapshot) + try await repository.save(oldSnapshot) + let lateInvocation = AgentToolInvocation( + runID: oldRunID, + call: AgentToolCall(id: "late-call", name: "inspect", arguments: .object([:])), + sourceMessageID: suffixAssistant.id, createdAt: instant) + try await repository.record(.toolProposed(lateInvocation, descriptor), runID: oldRunID) + + let reopened = try #require(try await repository.loadConversations().first) + #expect(reopened.messages.map(\.id) == [opening.id, prefixAssistant.id, prefixResult.id, replacement.id]) + #expect(reopened.messageRunIDs[replacement.id] == realRunID) + #expect(reopened.runs.map(\.id) == [oldRunID, realRunID]) + #expect(reopened.runs.first { $0.id == oldRunID }?.finishedAt == livePrefixFinishedAt) + #expect(reopened.lastRunState == .completed) + #expect(try #require(reopened.runs.first { $0.id == realRunID }).startedAt > instant) + #expect(reopened.toolCards.map(\.invocation.id) == [prefixInvocation.id]) + #expect( + try container.mainContext.fetch( + FetchDescriptor( + predicate: #Predicate { $0.id == replacementID } + ) + ).first?.runID == realRunID) + #expect( + try container.mainContext.fetch( + FetchDescriptor( + predicate: #Predicate { $0.id == replacementID } + ) + ).isEmpty) + } + + @Test + func editingALaterRunOpeningKeepsAnEarlierEmptyRunBeforeTheReplacement() async throws { + let configuration = localModelConfiguration( + "AgentEmptyPrefixRunTruncationTests", schema: HostPersistence.schema, + isStoredInMemoryOnly: true + ) + let container = try ModelContainer( + for: HostPersistence.schema, configurations: [configuration] + ) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let conversationID = UUID() + let emptyRunID = try #require(UUID(uuidString: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFE")) + let targetRunID = try #require(UUID(uuidString: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + let replacementID = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) + let messageClock = Date(timeIntervalSinceReferenceDate: 100) + let emptyRunClock = Date(timeIntervalSinceReferenceDate: 200) + let targetRunClock = Date(timeIntervalSinceReferenceDate: 300) + try await repository.save( + AgentRunSnapshot( + id: emptyRunID, conversationID: conversationID, state: .completed, + permissionMode: .askForApproval, messages: [], + startedAt: emptyRunClock, finishedAt: emptyRunClock)) + let target = AgentTranscriptMessage( + role: .user, text: "Later opening", authoredText: "Later opening", + createdAt: messageClock) + try await repository.save( + AgentRunSnapshot( + id: targetRunID, conversationID: conversationID, state: .failed, + permissionMode: .askForApproval, messages: [target], + startedAt: targetRunClock, finishedAt: targetRunClock, failure: "old failure")) + let store = AssistantConversationStore() + store.restore(try await repository.loadConversations()) + let conversation = try #require(store.conversations.first) + let replacement = AgentTranscriptMessage( + id: replacementID, role: .user, text: "Replacement", authoredText: "Replacement", + createdAt: messageClock) + + let operations = AssistantConversationOperations(repository: repository) + #expect( + try await operations.edit( + conversation, messageID: target.id, replacement: replacement) != nil) + + let liveStaging = try #require(conversation.runSummaries[replacement.id]) + #expect(liveStaging.startedAt > emptyRunClock) + #expect( + conversation.runSummaries.values.sorted { + if $0.startedAt != $1.startedAt { return $0.startedAt < $1.startedAt } + return $0.id.uuidString < $1.id.uuidString + }.map(\.id) == [emptyRunID, replacement.id]) + let reopened = try #require(try await repository.loadConversations().first) + #expect(reopened.runs.map(\.id) == [emptyRunID, replacement.id]) + #expect(reopened.runs.last?.startedAt == liveStaging.startedAt) + #expect(reopened.messages.map(\.id) == [replacement.id]) + } + @Test func truncationRejectsAMessageOutsideThePersistedConversation() async throws { let configuration = localModelConfiguration( diff --git a/TFMTests/ChatComposerStateTests.swift b/TFMTests/ChatComposerStateTests.swift index a3430438..d4bb3185 100644 --- a/TFMTests/ChatComposerStateTests.swift +++ b/TFMTests/ChatComposerStateTests.swift @@ -2257,7 +2257,97 @@ struct ChatComposerStateTests { #expect(conversation.workGroups.isEmpty) #expect(conversation.toolCards.isEmpty) - #expect(conversation.runSummaries.isEmpty) + #expect(conversation.runSummaries.count == 1) + let staging = try #require(conversation.runSummaries.values.first) + #expect(staging.id == conversation.transcript.last?.id) + #expect(staging.state == .completed) + #expect(staging.failure == nil) + } + + @Test + func editingSteeringKeepsCompactedSameRunPrefixAndLegacyToolOwnership() throws { + let instant = Date(timeIntervalSinceReferenceDate: 100) + let runID = UUID() + let opening = AgentTranscriptMessage( + role: .user, text: "Opening", authoredText: "Opening", createdAt: instant) + let call = AgentToolCall(id: "prefix-call", name: "inspect", arguments: .object([:])) + let firstReply = AgentTranscriptMessage( + role: .assistant, text: "First reply", toolCalls: [call], createdAt: instant) + let steering = AgentTranscriptMessage( + role: .user, text: "Steer", authoredText: "Steer", createdAt: instant) + let secondReply = AgentTranscriptMessage( + role: .assistant, text: "Second reply", createdAt: instant) + let keptCompaction = AgentCompactionRecord( + compactedThroughMessageID: firstReply.id, sequence: 0, summary: "Earlier context", + summarizedTokens: 10, keptTokens: 2, displayAnchor: instant) + let removedCompaction = AgentCompactionRecord( + compactedThroughMessageID: secondReply.id, sequence: 1, summary: "Later context", + summarizedTokens: 12, keptTokens: 2, displayAnchor: instant) + let conversation = ConversationModel( + messages: [ + ChatMessage(id: opening.id, role: .user, text: opening.text, createdAt: instant), + ChatMessage(id: firstReply.id, role: .assistant, text: firstReply.text, createdAt: instant), + ChatMessage(id: steering.id, role: .user, text: steering.text, createdAt: instant), + ChatMessage(id: secondReply.id, role: .assistant, text: secondReply.text, createdAt: instant), + ], + transcript: [steering, secondReply], runState: .failed, + messageRunIDs: [ + opening.id: runID, firstReply.id: runID, steering.id: runID, secondReply.id: runID, + ], + runs: [ + AgentRunSummary( + id: runID, state: .failed, startedAt: instant, + finishedAt: instant, failure: "old failure") + ], + compactionRecords: [keptCompaction, removedCompaction]) + conversation.title = "Keep this title" + let invocation = AgentToolInvocation( + runID: runID, call: call, sourceMessageID: firstReply.id, + createdAt: instant.addingTimeInterval(1)) + conversation.handle( + .toolProposed( + invocation, + AgentToolDescriptor( + name: call.name, summary: "Inspect", inputSchema: .object([:]), + target: .local, approvalPolicy: .approve))) + conversation.handle( + .planPresented( + AgentPlan( + title: "Same-time plan", body: "Must be removed", scratchPath: "/tmp/plan.md", + createdAt: instant))) + + let replacement = try #require( + conversation.editUserMessage(steering.id, authoredText: "Replacement")) + + #expect(conversation.messages.map(\.text) == ["Opening", "First reply", "Replacement"]) + #expect(conversation.transcript.map(\.text) == ["Replacement"]) + #expect(conversation.toolCards.map(\.id) == [invocation.id]) + #expect(conversation.compactionRecords == [keptCompaction]) + #expect(conversation.compactions.map(\.id) == [keptCompaction.id]) + #expect(conversation.plans.isEmpty) + #expect(conversation.title == "Keep this title") + let prefix = try #require(conversation.runSummaries[runID]) + #expect(prefix.state == .completed) + #expect(prefix.finishedAt == instant) + #expect(prefix.failure == nil) + #expect(conversation.messageRunIDs[opening.id] == runID) + #expect(conversation.messageRunIDs[firstReply.id] == runID) + #expect(conversation.messageRunIDs[replacement.id] == replacement.id) + + let stagingStartedAt = try #require(conversation.runSummaries[replacement.id]).startedAt + let realRunID = UUID() + conversation.handle( + .runStarted( + AgentRunSummary(id: realRunID, state: .running, startedAt: instant))) + #expect(conversation.messageRunIDs[replacement.id] == realRunID) + #expect(conversation.runSummaries[realRunID]?.startedAt == stagingStartedAt) + conversation.handle( + .runFinished( + AgentRunSummary( + id: realRunID, state: .completed, startedAt: instant, + finishedAt: instant))) + #expect(conversation.runSummaries[realRunID]?.startedAt == stagingStartedAt) + #expect(conversation.runSummaries[realRunID]?.finishedAt == stagingStartedAt) } private func waitForTerminal(_ conversation: ConversationModel) async { From d74f39bbda5b51d685c1922a818208b86488eb6a Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 11:41:58 +0800 Subject: [PATCH 08/30] Document Assistant message-boundary history edits --- .../architecture/engineering-invariants.json | 60 +++++- .../concepts/agent-runtime-and-assistant.json | 182 ++++++++++++++---- .../concepts/persistence-and-secrets.json | 120 +++++++++++- .../behavioral-boundaries-and-fixtures.json | 90 ++++++++- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 80 ++++---- .../architecture/engineering-invariants.md | 16 +- .../concepts/agent-runtime-and-assistant.md | 31 ++- openwiki/concepts/persistence-and-secrets.md | 30 ++- .../behavioral-boundaries-and-fixtures.md | 30 ++- 10 files changed, 522 insertions(+), 121 deletions(-) diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index f8aaf4e8..53e427ea 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:564964f95e8176182ff915ce6d8834e7717b6a3a3be946666c5b1d1d749906cc", + "pageVersion": "sha256:902f023527c34c7a6aacb2a481737931b5f30946486a41b279723af0321cc821", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -341,8 +341,8 @@ "statement": "Preparation has a separate identity from streaming: cancellation invalidates it synchronously and joins tracked history/preparation work before interrupted persistence; stale edits are rejected, archive/delete cancel preparation, manual compaction refuses it, and changing selection alone does not cancel it.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L236-L259", - "version": "repo-lines-v1:sha256:fb96479831645e246050471c6bfb603e36dfa4c433cea61b917671f897948614:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2YmY3NzkyYmMwMzMxOWEyNThmMDMxMzZlNTg4MWE0YjdmNTI3ZWVkNTQ4ZDQ4MGRlZWE5Zjg0ZGE2NTczZWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwNTRiZjkyODIzYmI3NmNlYjU1YmNkZGI1Y2UwNTk0MTUzNjJjZGNhZjk3MTQ1MzhmMDg0NzQ1ZmQ4Njg2OWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MDZhNjU1YWM2ZmRkYzgzZGYwOGUyMmQyNjc1MzM3ZjZjYWYzN2E0NGZhMDdjOGEzNWViZGIwZWNlYTVjMWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxYWI2OTA5MDBkNWZjZmRjMmMxNjEzYjRlNDA0ODc3YTU2MjZjMjM2MWZiNGQ0MTJjMGIyMDlkYjBlNzJlMzgifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", + "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L21", @@ -357,18 +357,62 @@ "version": "repo-lines-v1:sha256:10dde8f96383fc4e7c331b4e927c1c5e61a7173356ef4b5b49d67c68ba29e2e3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExMGUxZDhkNTQ2OGFiNjQ4MzUxNzE3MWM1ZDQ0NzZiNjUzNjIwNDJlYzVlNzZhYjNlZWQ2NTM4YjhmZmRiN2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiZWNkMDQ0NWJmNGEzYTg5NTcwNTczNGE0YmEyYjFjYzQxMjRiNzM1NzYyYWY0ZWYxYzQyODcyNTkyMThlNzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiNDkxY2E1OGFjYmFiYWNhOTBhZDBkYmQyNjgxODk3NzFhMjY2YTYyM2FhZmE3NzRhYzY1MTg5OTk3YjU1YWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L770", - "version": "repo-lines-v1:sha256:405ec2eca133a1dd12f58eb7f2099860c2da18f294af59b286e2f1f935847c4c:eyJzZWxlY3RlZExpbmVDb3VudCI6ODcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0OWVhNzVmZmFiZjU2OTUxMTk2YTQ4ODljNDE1OGZmYjM3NDY5NThhMmI3NDIxODE1NjVkYTU3NDkxZDI2YmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkNjUzOWRjYTQ0MjY0ZTViYjAwZTU2MzRmY2M2NGI4YmMwOThmNTViNDAxZDBjNDIzZTYzNTlmNDJhOGE0ZjEifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L762", + "version": "repo-lines-v1:sha256:7a08479880142384554fef4c4143ed597856d65be0c552a76400a967ada09df3:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L829-L839", - "version": "repo-lines-v1:sha256:1cfe225932062439ff267e988637e685c28e2ecefa0b0e93398784b85cb79032:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhMmRhZDQzODJjOWExZTA4YjBkMDE4YjZmM2E5ZTE3ZDJlODU3YTA0ZjdjOWUyN2RhODQxOGI3YjUzN2U2MzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwMjczNmZkMDQ2ZjFlNWQ4Y2ExZTYyNjRhNWJjZjhlNTc2YmY4YzFmM2RmNmM4OTk2MTIxZDI1ZWIyMDAzMzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYyZDRiYThiMzc0ODExZjYxZDdmOTQ4NWYwNzMzMzFiMDczYTNmNTI5ZDdmM2ZiNDU2MDlmMTRhMTMxMjc1M2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJiMDY3MDk2YzhiOTcxNzZhODc3ZGQ3YzIyNjRmYjU3MTljMGRlYWRkMWIxMGI4YjcxYzljYWZjZDk2OWIyNDAifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L830-L839", + "version": "repo-lines-v1:sha256:ce3f227083ebc0587a705272d2c18cfa110d702714310ad736937b34384653a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmNzA5MTI3NmEyNGM5MTU5YzRjZGUzZWZiY2RlYWM3YmQ4ZWM3ZjQxYzVkYjNhNmIyOWM3MTE1OGZkZDg5Y2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlOGQzZmM2YzYwOTVmMmM4OTlhMmM0MTRlMTg4Zjg4MjY3MTA1ZjQ0ZTQ3ZTZhODQ4NjE3MTM1NGE1ZmYzYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY5MTNmNzA3ZWY5NzdkM2FlMDUxOTgzMGM2YTc2NmYxYmI1OTdjZjJlMDgxOTQ3MDM0MzU0ZWU0MTQ1ZjA0NmUifQ" + } + ] + }, + { + "id": "claim_5ebe57a209164945b6910dd77f3e1352", + "statement": "History edits commit before live replacement, truncate at the selected user message in canonical run/message order, preserve the same-run prefix and its owned tool graph with a neutral completed summary, and prune compactions by removed message boundaries while considering compacted display history for live ownership and title retention.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", + "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1089-L1218", + "version": "repo-lines-v1:sha256:248cfe3f3ede17937a4b48e8c211924e23677e9224b64834e14b6a03a7601804:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzNmMxMTM5ZTBjOGMxM2Q5MDU2ZTIxYTE4NTEyZjA1OWY0MmI5MzNkNDI3OWU4MWE1NzUyM2NkNDFlYzEyZTk5IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1OTA5YTUzYTYxOGQwMjhlMWQzMDAwYzBhN2FiY2VlY2JlNWZiMzdmNGU1MTFlZmU0NjY0M2ZhZDZlNjY4NzJmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxY2FmYzE2N2VlOTliNmY1YzgxZTA2Yzg5MzYzYmNhMGJkM2VlODVmZGMwZDVmZDNkMDZiNGMwYTIwNTkxZTgwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmYjA0NTczZTY0ZjNmYjg2YTQ1OTZjYmRmYzZmOTNkYTIxYzE1OGJiNjNiYTgyMjg1OWNkMzNmZTNhNTM0OWRlIn0" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L398-L513", + "version": "repo-lines-v1:sha256:786d58b2e1a37cd62e8cde6443f38ed2fbb0dbd0ce10fdb8bc90b441b3c6eed4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI1NTIyYzQyY2QxOWM2ZDAzZDgwOGM0NTI3MjA1NTI4MjViN2VhMWM1YjQ5MWIyMTg1MTFjYWM3NDE1Y2Y5ZDliIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMGU2YmJkMjI2NGYyMzJkYTlhOGNlYzkxZWQzMDk1ZmEwODgzYjU3MjU5YzUzODY1MjM4NjVmYjM2MWYwMDY5IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhMDk3ZjZkNTBkOTY0NWU2MmNhODlkOWQ1MTJhM2U4N2ZiYThkNzkzZTRkMjk2NzlhNWZmMmRiNDMzYWU2ZjlhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIxOTYxN2Y1M2MzNjY1NjllOWI3ZGRhMDk5M2ExOWJjNzBhZTQ2ZjIxMzA3OGY1YWUzNDIyYzFiNTdiYzc0YTMwIn0" + } + ] + }, + { + "id": "claim_fd359e43825344cb87abb3a4938e1663", + "statement": "An edited message first owns its same-ID staging run; the first real snapshot atomically reassigns only the staged row and removes staging, while live running/failed/interrupted adoption preserves provenance and chronology. Repository-instance retired IDs reject late saves/events; missing-model/credential failures still publish live before asynchronous save.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", + "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", + "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2625-L2646", + "version": "repo-lines-v1:sha256:078528287f4cac7401a61342ac1db3154e36f5566407f8bb9332cd63c543e273:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjZWUwZWU1M2M2NjUxODNlOTE0NjU5MDViZWY3ZDQyMTI0ZDRmM2JkYTJkNDg5MzlkNWIwNWQ1Yzc1MzIyODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODMwOTk4YzhiZmMyY2EwMGQ0MGM5NDlmZTgxOTQ5ZmQwZDg4MTg1ZWUzOGNlMDNjMjAzNzg4MDZhZWQ4YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2YzUyZmU0ZTgxMjIzMWU2NGIyMGFmODc0MmI0ZGJjZWNhMjlkMDFjMTVjYzk1NDNjYzc0ZGFhNDAzNjlkZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmZGI5NTg3M2IxNmNiMTEwMGJjNGVmMGZmYWFiMzZmODc1Y2EzYmVjODY2MmIwMDA5YzEwY2JlNWE2YWVjNTMifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L156-L165", + "version": "repo-lines-v1:sha256:c887b3a1878d05b6825a0061c2a4ac62fa358fae324872204ffb83d6b940c2ed:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MDIwMDcwNjJmOGU3MDc5ODNiZjBkYmI4ODMyMDYwMjZiMzM1ZGY5ODBmODNlZjQ0YWYzYjRjMWI3NDA0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMwNzU5M2RmMjVjYjQxMmZiNWI3NWUwZWI3NWQ3NGM1ZWY0YjBhMzE1YmJlMWRmYjcwMTg4N2MwMjEyMGM1NjcifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L16-L121", + "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T03:34:06.838Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index c5ff0753..1be7cea8 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1dba6f651d9536e5ea63b9bd551b7e20cc973f0816613aec3593304b42c8cbfb", + "pageVersion": "sha256:46c6736cc2e227449885be7dd7fbf51a7b98259f7509c85a334881cc20382e90", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -163,15 +163,19 @@ "version": "repo-lines-v1:sha256:d63fa5240e197b14ca235275b415affcb5df50fd36b04407f3853864de0eec2f:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2JlNzJmMzczMTQyYTJhMDE4M2U5YjUyYzE4OTMxZDkwNTI5ZWFjOGQwZmJhY2FlMmU3Njc1MTZjMTVlZmIzYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjZlYzhlMjVhMTk4MGQ0YmVmNDRmYzk1Y2JlYTAzNmRiZjM2ODQ2MWU1YTU3NmRhNjA2MTNhYzFhYmFjODk1MiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGYzMzJlODFkY2ZiZjQyZmQyMjU3NDk1OWM2YjdjMTAxNzJjOWMwOWUwOTY3MjczMmUxNDM5Mzg1ZmNkZDNmZiJ9" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L47-L75", - "version": "repo-lines-v1:sha256:3e3cf6dd4cb38f70d589568031e9bdf159967aaa4a80c86fe225a88a4414f107:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlOGZlM2YzMDFmYjI4M2EzNDNmNWNhZWVkYWZkMTAzMGY0OTQ5YWE4NDQ5MjFiNzUxZDI0NWU0MDViOWMwMWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3ZmIwOGJmYjIyNTk4ZmJiOWM0NTgyNjRmM2MxYzZiMDA2MTY4YjY5MTY2MmJjNTBhZTVjYzVlMjg2ZDU2ZmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4OTZhNDc4MzIxYTFmMGQwOTUyMDliMjkyMTBmNmFkZmMyMmQ3YjQ2ZjMxYzMzODhlNGQzMjk5OWRhMTFjNzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEifQ" + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L156-L189", + "version": "repo-lines-v1:sha256:96d8bf791ad0209c27eea28ba6e5a377ce55cc1d23e8f033b9b5dc954224032f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3ZmIwOGJmYjIyNTk4ZmJiOWM0NTgyNjRmM2MxYzZiMDA2MTY4YjY5MTY2MmJjNTBhZTVjYzVlMjg2ZDU2ZmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEifQ" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L550-L605", - "version": "repo-lines-v1:sha256:6aa3a9f8b2bd8335e17c386f68b91564551f30ee25e239ae960ae032ef4f90fd:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzNzE0ODNiZmQ5OGM1MGM5NDg5ODlmOWEwNTJjMzI1MGUzNzMyYTczYTc2MGY1ZDJhN2I2MjFhNTJhNmUzYjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYjUzMTU2YTNkYjA5MDdmY2I4NDEwNjJjNjc5NjdiMzMwY2I5NTk1ZjViNWEzM2I4ZWVmNTZjZTVlMzczZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyZTEzMDIxNmFiMTA4MGE5ZWZiY2Q1NGIzMDdiNDk0ODU2YTUzMTYxNDc2Y2Y4ZWNjZGM0YTYxYzk3NWVlMTIifQ" + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L60-L114", + "version": "repo-lines-v1:sha256:118c8d30f1c995a58390a2ee4694a6fd949d3684e9874e3b8e8e6e3fe003fe43:eyJzZWxlY3RlZExpbmVDb3VudCI6NTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYxZjE3YzM4MDE4ZjhkMDliMDEwMWYzMDI4ZTlmZDk1MGJmZWVhMjljYjAzMTkxYWVlNDNlNzBmODU2MTViMTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxN2Y3NTdhNGJiYzVkNGYwMzI5YjU3YmM0NThmODFmNDhiYzBlMTkwMGJiMjU5MGE4YThmNTMyMjU0NzNmMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMxNmI4NGRlYzM1NDFiMTNkMzFmZTkyN2I0YTM2ZDEwZTBiNWNlNjgzYTVmM2E4YjZjMWRiOWNhODFlYWJhMjYifQ" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L671-L683", + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L746-L786", + "version": "repo-lines-v1:sha256:0bbf3410aaea40a96735687ffa5a11a929afa27945aae74e81a0b4f54512d7bf:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYzMDUyZGM5NmZhZDk0ODE0NjNkNjcyNzYyOTZkNzk1Y2Q1ZTAyODJmZmNkMDAzNzAxZTEyMzcwMjM3Mzk3NDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljMjdkYzQ2OTBhZmJjZmM5NjgxOGIyYWZkNDY4ZjkwOGUyNDBhYTgwZWEzZWYwZjcyYjM2ZjNhOThjYTUyNjgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ5ODY4MDg2NjZjMjE5ZjM3Y2M3MzlhZGRlOWQ4NjBmZDIxNWRkNDc4MDU1OWI4ZWFkMzlkN2Y4ODFhYjI2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiZTE1OGU4OWZkYzM4OTc0NzZhMGZmYTc2NjE0MmUxNGNkYjIyMTNjYjIzMmQwYTEzMGU3NTlhNWM3ZDllNGUifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L853-L865", "version": "repo-lines-v1:sha256:f142043bb1b01de9a0458b3995239712303b9ee5f7cd1bc4531d3ee6da2913f1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5NGQ4ZDkyN2FkMTU0ZTM2ZmFhZjZiMmI2MjMxZDllN2Q2MzRiY2IzZTliMjI2MzA4ZmJhMGVkOWQ3MDY0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] @@ -453,12 +457,12 @@ "statement": "Desktop and mobile prepare edits synchronously before creating a UI task, preserving original attachment payload/suffix but capturing fresh submission context; performAgentEdit checks identity before tracked persistence and again before runtime preparation, and failed persistence does not apply the live edit.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L448-L495", - "version": "repo-lines-v1:sha256:838ceb32878a6eeab928ca75de90ff336039a453f732420b741e243c655d60e7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L450-L503", + "version": "repo-lines-v1:sha256:1c6b9ba0438e47aa7828a8f0a41cdc7625ac586fd9baee676a531b04a954769a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhOTU4ZTIzMDAzNTJiNWU3NmQ0YzYyZWM3ODUyYjY4NjViZTQ4ZDgxODg3NDg2Nzk2ZTkyMTI2MmZkNGRmNTcifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", - "version": "repo-lines-v1:sha256:cd90c6361276a4a76ce8f418f2c1c6e84412990ab662ed6d79c7aedaf37f184e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L216-L281", @@ -466,7 +470,7 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1054-L1067", - "version": "repo-lines-v1:sha256:4f484d7cfc22662766e35da9dbd2bbae1e661c48fb02f3e454ed67e39e0efe50:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNDEwYjZlNzRkZThiNjdmYWNlOGI5MDE5M2EzYWNiMzg2Y2U2OTk1YjQzMGUwZTVlNThhZmFiZWE0NDFmYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmOWI5MjU0ZmFlZTRiMGFkZWU1YmRlMDZjYjY3MjI2ZGI3MDQ2NzUwYjU3NDVkZWQxNzI4YjQ2OTMyODEwZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1YjAwYjk5MjY2NDI4ZmU1NTI1NDYwZDE0YzNhMjZlNDI5Yzc3Yjk1ODUyZmEzNzEyYzEzYmNiMTMwM2I3MTAifQ" + "version": "repo-lines-v1:sha256:4f484d7cfc22662766e35da9dbd2bbae1e661c48fb02f3e454ed67e39e0efe50:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNDEwYjZlNzRkZThiNjdmYWNlOGI5MDE5M2EzYWNiMzg2Y2U2OTk1YjQzMGUwZTVlNThhZmFiZWE0NDFmYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmOWI5MjU0ZmFlZTRiMGFkZWU1YmRlMDZjYjY3MjI2ZGI3MDQ2NzUwYjU3NDVkZWQxNzI4YjQ2OTMyODEwZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxODE2NWIyNGI3ZDdhMjdiNTZlZTIxZDVmNzk5NTFmODRkNTk5MDY1YmY3YzZlMzZkYzE5ZjBkZjM2ZWYyZGMifQ" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L215-L227", @@ -488,7 +492,7 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L760", - "version": "repo-lines-v1:sha256:64ee147e1dcaafe8cc19bb2697cc20ec0c213ab4605a69f330102ab70ac0e0c7:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" + "version": "repo-lines-v1:sha256:7a08479880142384554fef4c4143ed597856d65be0c552a76400a967ada09df3:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L69-L83", @@ -517,12 +521,8 @@ "statement": "Archive/delete cancel and join pending preparation before their activity guards; manual compaction refuses preparation, navigation leaves it active, and automatic compaction uses captured preparation settings while preserving persistence-before-live-apply ordering.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L253", - "version": "repo-lines-v1:sha256:139ac4c69442f66025b379fc4089c53b7a0146bb84dc60c78a255b867eec9ffa:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0NDQ1ZGQ3NzZkZGY3MmE2OGI5ODIzMjUxMjgyYzFlNTI3NmIwNTQwYWVlMzQ3YmViZjEwMWQyODlkZGNmZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" - }, - { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L372-L393", - "version": "repo-lines-v1:sha256:86b94fd10a8c9d7ca8a12fd280f314f6ed57b669f92add6b32897973ab5fbe31:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUxNDk2NDE4YTVkMGU3OTIyYmYzZjczOGMzMzJmZmNhM2UxZGJlMjkwNmFiMWVhMzBkYmE1YjVmNzdiNGYzNDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBjOGE2MGMzNzZlZGJkOWFjYTMwZGY1NDgwMjUwOGVmMDcxYWU4YmYzN2VmZWE3ZjI3ZTkwOTk0Y2VhZTBhYjAifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", + "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L28", @@ -537,30 +537,12 @@ "version": "repo-lines-v1:sha256:b7ea68c5df571de68ab0ae45d9c94ed876783cac80350d6c5d0b17aaf3a8683e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjMGM5OTdhNmNkYjEzZjBmMGY3NDJjNjE2YWRkMmMzYjRlOGMwZDY2OTQ2ZTlkNzQ5Nzc1YjY3YTBkNGY5Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZmNTNlNmY5NWE4YTI0ODczYjhiMTUyMjJmODMwOWNmNTE2OTY3N2NmYjgwN2JhN2YwYWQwMWQxYTAxMWJhMzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg1ZThlZjg0MGM3NWUwYTg0ZDlhYTdjN2NkMWY4MmRhMWU2ZmNlM2VjOTY4Yjk5YjIwYzYyZmU1MWYwZDg2OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L829-L835", - "version": "repo-lines-v1:sha256:26e72807f43e248f95f1c994dac14bb23d50bba63007496164e70adadf2c6e4a:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWEyZGFkNDM4MmM5YTFlMDhiMGQwMThiNmYzYTllMTdkMmU4NTdhMDRmN2M5ZTI3ZGE4NDE4YjdiNTM3ZTYzNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGY0NjJiN2UxZTkxMWYzN2VhMDM5OWU0OTk1ZWUwMWZmYjc5NzcyNzA0MzU4ZTIzYjQ5M2RhYjBmMjliNzU5NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjJkNGJhOGIzNzQ4MTFmNjFkN2Y5NDg1ZjA3MzMzMWIwNzNhM2Y1MjlkN2YzZmI0NTYwOWYxNGExMzEyNzUzZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDQ2MTIwNmJkOGQ1ZTI2MGNmOWJkYjA0MTlmZjA2MDQyM2IxODIzMWE2ZDE1ZjI4OWY4ZDZhYWI0NDYyNTAxYSJ9" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L830-L839", + "version": "repo-lines-v1:sha256:ce3f227083ebc0587a705272d2c18cfa110d702714310ad736937b34384653a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmNzA5MTI3NmEyNGM5MTU5YzRjZGUzZWZiY2RlYWM3YmQ4ZWM3ZjQxYzVkYjNhNmIyOWM3MTE1OGZkZDg5Y2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlOGQzZmM2YzYwOTVmMmM4OTlhMmM0MTRlMTg4Zjg4MjY3MTA1ZjQ0ZTQ3ZTZhODQ4NjE3MTM1NGE1ZmYzYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY5MTNmNzA3ZWY5NzdkM2FlMDUxOTgzMGM2YTc2NmYxYmI1OTdjZjJlMDgxOTQ3MDM0MzU0ZWU0MTQ1ZjA0NmUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L881-L890", - "version": "repo-lines-v1:sha256:7c9756ce11f5a802355fdf037aff24ef90f81e71df715249370eef766c0a4ab0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MTZmMWZiZWQwMTYzZTEyMTU4OGUxOTZkMjA3Zjk1ZWYwNTFmMGZiMDEwN2YyNDI4OTA1MzAyOTEyYTVhNmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlMGJhYWZiMDE3NmFmY2M1Yzg3NzI0MTkzYmY4ZmM2NWU4MzgwNGUyYWUwYzk5YjZkODViZjViMmY2ZWFmODEifQ" - } - ] - }, - { - "id": "claim_8420ecde122546a3a1bc25a77957def7", - "statement": "The SwiftData edit transaction checks cancellation before any synchronous truncation work, but still removes the entire target run and later runs; editing a steering message can therefore remove earlier same-run durable messages that the live model retains before the target message.", - "evidence": [ - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1070-L1095", - "version": "repo-lines-v1:sha256:52e55fbf29dbf0f6cdbb7f1e7a8649d82049f4e361faa3b2fabb517687dd0841:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1OTY0YzBmNzc2OTUyZTBkNTg5ZmJiNTBiNzMxMDg2Njg5Y2EyMDc3MzRkNmI0YTcwNmRhYTljMjQ2ZTg0ZjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkNDQ2MGM1Y2JlNjMyMzgwMmViZWI5M2EzYTkzNjY1ZTU5ZmM5ODYxZjhlYTEzZjY5MDlhYmIyNDZmMWRmOGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyZTkwOGFhOTFkNDc4NzMzMTdhNjJkYjBlNzIwZTU5MjZhNjgyYTQxMjBiZTgyZjVjZmQ3Y2ZmNDc3YjU0ZjMifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", - "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" - }, - { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L352-L412", - "version": "repo-lines-v1:sha256:3a86a7bd8ecbbb84119dfaaa13c7eede8a28a6321764a743ba61677b6dcd662f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L881-L892", + "version": "repo-lines-v1:sha256:511d122c9d8b8981de6d7c39b078ca3b5f6af4930853207aa98b7d3365557bf8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZkNWViODBiM2MyZWVhZTUwMjUzNWRmMTM0YzJjMGY2NzljZGU3OTU4NDQzNjdmYzNhZjVjNDAzZWE4YzMyNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlMGJhYWZiMDE3NmFmY2M1Yzg3NzI0MTkzYmY4ZmM2NWU4MzgwNGUyYWUwYzk5YjZkODViZjViMmY2ZWFmODEifQ" } ] }, @@ -648,7 +630,7 @@ "evidence": [ { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L85", - "version": "repo-lines-v1:sha256:13e7b01f19946ab38cdef60520d4429dc12ac6ba60679734bb93decce1577a38:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" + "version": "repo-lines-v1:sha256:80d25cc0451a928bc50f91e07a28b411b83c0d7299889938c179b8d5e96fb33b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L348-L379", @@ -699,10 +681,128 @@ "version": "repo-lines-v1:sha256:8c395370a5d6db13c09099290c322db66dec28cafff52ef018a409820775f53e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhNTA0ZjZiZmJkY2EwOGIwZTE4NmFlMjA4YTBjODQwNTFlYmVjYzg5YjU5NTcxOTc1M2U1MGRhNjJiMDcwNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliNGNhZGRmMjhjMGJkYmRjNmVkMWM0NzRiNWIwNTNlZDk3OWQ1ZDQyZjA0NTZlODQ4YmIwNzY5ZjViOWVjMzUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZWJkZTg4ZjUxNjFmNzkyMmRjOWY0ZjdlYzA4OTg2ZDU2MzdjMmI3NDZiNzYxMWVjOGZkY2VkMzM2YTNjZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" } ] + }, + { + "id": "claim_dce95033c20b4420b1a4e93eaa088f42", + "statement": "A validated history edit commits before live mutation and truncates from the selected ordinary user message in canonical run/message order, preserving a same-run prefix and its owned tool graph with a neutral completed summary while deleting wholly later runs, including empty runs.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", + "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L398-L474", + "version": "repo-lines-v1:sha256:b4de660c3a3b7c33e6354f0941bc3b8f8b034a3983e044a414a4039dd3073f6e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwOTdmNmQ1MGQ5NjQ1ZTYyY2E4OWQ5ZDUxMmEzZTg3ZmJhOGQ3OTNlNGQyOTY3OWE1ZmYyZGI0MzNhZTZmOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0ZmU5ZTVmYmZmMzY5YzBkODIzMzZlMDA1NGFiMmM4NzZhODBkN2VhZTYzNDVkODgyMDA0YjgyYTc5ZDkxYTgifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L690-L713", + "version": "repo-lines-v1:sha256:487650c28b9099463e7bfa7834c959b0cf74fc3d13d1b1961ff07461c20fe72d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmMDAxZTQ3OTg4NTAxYjJjMTQ3YmI0ODRlN2FiMWFkMTJhODcwM2JlZTA2MGJkOTdkNWM4Yzg3YjMwNGYxZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwZmIwMDQ2MGVkM2FlMTI3MTgwYzExMDE3YzE3ZDYwMTAwNWM2MmFiMDVjOTFjYzZhYzk0OGRiOTAxMTE2YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdkM2E0OWNiYWFhZmQzZTYxYzdlNGZhZWU4NzViNGQ0ZjBlMzA2NzAxMzM2ZmNiNTI0MTMyMjJhOTk2MTU0YjYifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L872-L1074", + "version": "repo-lines-v1:sha256:2a68be1f9c455cfee76bc0c6c557a1d8af1bf909f951786b861e71498a31b67e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxYzlhMjg4MzM2YWFlNTM2ZWUyNDBiMGZjMGU5MTI5YzMwYmYzZGZiNmJiNWQwYzI1YThlNTk3ODk2N2Y1Y2Y3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI5ZDAxODMwNWY5YTk2MzdiODgxYTY4ZWZlZjM3OGU0NTdhMDkxYWExMmIwYzQ2ZmYzZDVmYjgzN2RkOTE1ZGY2In0" + } + ] + }, + { + "id": "claim_9e856827319647aa8e174ce79b9e4450", + "statement": "Edit tool cleanup prioritizes known source/call ownership; restore infers legacy source IDs and assigns recovered result ownership, letting live edits use retained result and full display-prefix ownership through compaction. Only unrecoverable ownership falls back to the inclusive timestamp cutoff, including modern provider-native events persisted before their source-message snapshot; compactions prune explicit removed boundaries, nil bases survive, and retained openings preserve the title.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1099-L1213", + "version": "repo-lines-v1:sha256:391561a2c3b5ec7d720aad49d064e49ee0835d8ae1b7238c893acd5db9f1c2a2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2NTExMWVkYzQ0MzRmMWNjY2RjZTRiMWJmYjliMzg0NzliZTQ0OWE4NTI1YmM1NWY1ZDVkNTgzMWUxMWEwODQ2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0OGQ0MWEzMDg0NzY3MDkwN2YwNTdmYTM2Yzk4M2JjMjgxMTY4OGIxZjY0MjBiNzY5NTdmMjZjNmZiNDM4ZDM2IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJiZmYwNGNhNmE5MmRiNWE1ZWFlYTgyNDU5YjQwMTZhNDY3Yzk0NDFkZjUwODBjZGZhNTU2YWE4ODc2YTc5YzU2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0NGYyZWVkNzJiOTA1YmY3MGFlYzgwNTYwN2ZmMTE2NmI4NDg4NDVlM2M1OTE2YTIyMzU4YWMzZDIzMTY0Mzg1In0" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L265-L300", + "version": "repo-lines-v1:sha256:18f9323794aa82ed081c01504f42cbadf30f212d167e36843b0c25b5bf4e037c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwMGY1NTMxZTgyMjQzZmE5YTUxMTljZjRhYmQ2YTFlNmMyODM4ZmJmOGY2MThkYzFmNzk0OGE2NmU1M2U0YmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZlMWU1OGJiYjE1OGJmM2Y1NmU4NDU2NTFkOWYzMmNiNGQwNzY1ODE5ZWE0YjBlYjc4MWE2NWE4Njk2YjQ2N2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc1ZWM1NjU4Y2ViYTY5NzExNGJjZmQwMWExOWI0OWZhNDY0YjZmMDk3MjFiNWJjYzExZGRhM2VkOWU4MGRhY2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ZjY4M2EzYWQ3ZTNiMmY1M2NiYjYzMzc0ZGEzYTBlZGI1ODAzNjM4MTA3N2VlOTMzNjg4OTJlMWU1NDcyYWQifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L463-L474", + "version": "repo-lines-v1:sha256:3566b5c55e0c816569eed81c87e1d5fb164f13ddf0411b083be2192939c87efa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg4MzQ2OTgxMTA0N2U0OGM3YjIwZjMyZTZiMzY3MTkyMTU1YTRiZDNkYTQ5MmNkMGRhY2YwY2E1MWY5NTdiN2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ0NWZmYTMzMWFiMzkxNWJhMzk3Mjc1YzJkNDg1MzMyM2M5NTk4OGUxY2Q2NDk5MDE5ZTE4MmViMDdkMTU4YjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0ZmU5ZTVmYmZmMzY5YzBkODIzMzZlMDA1NGFiMmM4NzZhODBkN2VhZTYzNDVkODgyMDA0YjgyYTc5ZDkxYTgifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L642-L669", + "version": "repo-lines-v1:sha256:bd434e864596637e645d6eb82622055fd9b8b8d494f895a8278ecdca3581663c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg2MjZkYmE4MTBjOWI5NmZhYmI1MmJkNjhhMTFhY2ZhOTRmYWQyZjZmYWY1ODJhMzMyYjA3MmIzNzE2NWI0NzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgyMDZiNTMyMTkzMjkzZThiM2U3OGI2ZmY2MzczNDVlMzhmZTJlYTRmZTQyMjc0YTc2NDhiYzlhNGYzNjk5ZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3NDJmODZmOTJlMzg5MTljZjQzOGQ5MjdlYTUwZDMyZDkwMjljZjgxOGJmYTFkNWYzYWZiYTk1NGQxMmNlNzMifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L972-L1015", + "version": "repo-lines-v1:sha256:a4c0ef3257d9a4c53b2a8cac0f20fd52f9bf2f82bbca7a44d7840155674361d4:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxZmUxMjYxYjA1NGUzZDE5ZDFjNWZmOTcyMzg2NDRkZGZlNGVjNzQ2MGYwMjNmNmZlZTA3MGM2ZDA0MzFjODAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4NzU5MzZhZDg0ZTBlZTQxMTdmOGQ2NmQzNjE4MjA2MTBhZDAwMTM5M2Y3YTdkNWE4MjNmZjM4MDM1ZjBlNWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZhNzUyZjZmMDU4ODJhMGVlNzg0YzNhZWY1Y2ViMDZmNTg1ZmM2YWI2MzI3MDIwZTc1YjA4MTdmYjMzNDkwMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM2NmVkODY5OTU0Zjg2OGM1NTRkODg5NDgxOTk5OWY0ODI5NjQ1M2E4MGFmYjMxOTkwYzU1Y2I0ZDA0ODZmYzgifQ" + }, + { + "resource": "repo://TFMTests/ChatComposerStateTests.swift#L2268-L2328", + "version": "repo-lines-v1:sha256:ccca1326878280a11f0532679307a3e4e5508d97e205689a2ffbaffd1fef8f22:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTZiMTA4ZmJkYTIyOTRkMjBjNmZiOWVmMzk2MDNiMTI3NzFhOTdmMzY2ODM5YWM3ZjUyNmExYWYxNGI3ZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjN2Q0MjFiMGIyZjI0ODZkYzk1NGU1OTBhY2JmZTA3OWIzOTMzZmFiMzFhZGFhYjdhNDlkNjVmNGFhOTA5MjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYzZTlhMTY2N2Y2YWUwNzk1YmFiNTMyNzQ3Mzk0NWFiMmUyNzM0ZDdkNTUyZTJiNzBhNTU3MjZkYmRhMjlhZmYifQ" + } + ] + }, + { + "id": "claim_1980c8db4e9a4fa9ad04ec0bc310b025", + "statement": "A replacement message initially owns a same-ID completed staging run; the first real snapshot with that staged final user message transactionally reassigns only its row and sequence, retains attachments/replay backing, deletes staging and leaves historical replay owners unchanged. Staging orders after retained runs and supplies a start-time lower bound preserved on subsequent saves and terminal timestamps.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L16-L121", + "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L476-L503", + "version": "repo-lines-v1:sha256:c1a8d918c5409018d4549fe64ed7fe6080468331b1c24a04cd3bb17761a6272b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwNzAyNmVmMjJiMzJkYmQ3OWY3NDc4ZDYzMTg3YTBjZDI4ODNjYjYyMTM4MmU3N2QzNGQyMjcyYTA3MzFlOTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3NDM2MjdlZDk4ZTAyYjU3OGY1MjY5Yzc2MGVlNTg0ZWU1NDJjM2UzYzlhNDQwODkxY2QxNTE2MjVhNTlkZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlZjBlYzVlMTdjMGI5MTM2OTFhYzdjYWFiYmVkZjA1MDRiN2Q3YmUwMjBmOGZlZGMzOGQ4NTMzNzk2MjVjOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlkZjlmMzU1MjY2ZmFjMjQ4ZDgxZDMwMDc2YWY4YWRiODFjNDBjOTY3NjFkMWQ3MzQ5OGUxY2RmMDNlYWZlMmUifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L671-L688", + "version": "repo-lines-v1:sha256:9430ebe704cfe45e02e2a23637debf77c4d62d0849a7065c405e4b78d26bff00:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY0MDUyMDRhZTk4ZDM4NjdlMTNmYTU5Y2E0MTU4YmU1Y2FkMTMxODZiZTBiMzlhOTZmNTNkMGQ2YmZkNTZiZjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQwYTA4ZjYzZjc3MGZlNGQ4MDFjZmU5YmFmZTg3NjdhMTEzZWM3ZmE2Njg4ZjA1OGU1NTc0ZGJjYTNjMGIyZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFmNjMyY2Y1OTNjNzEzYzQwMjI0YzhkNGVhZjhmZWI2ZjUyMWMyNDIzYzRiMTVhYzVkYzFhODAzMGEwM2U3MWIifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L1076-L1135", + "version": "repo-lines-v1:sha256:0f33e4023a498a0b07b9840bc6164aab10ee8ecf9af5c6a7b7c562e62cd3dd5f:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE0ZTAzOTdjN2QwYzg5OTg4NTkxYWFhZTZkZmZlNTdiNzI4NmYwODczNTYxMmRkOTc5M2FlYzJhNGYyZjU4MDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJhZjRjZmQyZjMzYTRkZmMwMDJmZmE2MWVkMDg2ZWU0YWIzNWU3NjA1YTZhNGFlZmM5YTc5YTFkZTMyYzIwYzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzNjFmOGZjOTJhNWE1NWNhNmJmYTI5ZDE5NDQ0MTQ5ZmIxMjZhNDRiOWUwZDMzMTExM2UxNzlkNjgwY2Y2NWIifQ" + } + ] + }, + { + "id": "claim_eb12b2d099c145dba48004e4797b19a7", + "statement": "Live run-started, captured-failure and preparation-interrupted paths adopt the staged replacement's real run provenance and chronology. Cancellation awaits save before adopting after an identity check, whereas missing-model/credential failure projects live before asynchronous save, so the end of preparation is not a durability barrier.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L476-L534", + "version": "repo-lines-v1:sha256:435b7b43d6dfe28c7d197656e31af82f72b308168427674bccfeb12777d04735:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwZWU0MDJjOGFkMzY5M2YwZjFjNzY1MzZlZTk5OTVmZWMxYjkzNTBkNzU5YTVkOGUyMDY0YmRhYjRlN2Y1MzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", + "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", + "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2625-L2646", + "version": "repo-lines-v1:sha256:078528287f4cac7401a61342ac1db3154e36f5566407f8bb9332cd63c543e273:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjZWUwZWU1M2M2NjUxODNlOTE0NjU5MDViZWY3ZDQyMTI0ZDRmM2JkYTJkNDg5MzlkNWIwNWQ1Yzc1MzIyODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODMwOTk4YzhiZmMyY2EwMGQ0MGM5NDlmZTgxOTQ5ZmQwZDg4MTg1ZWUzOGNlMDNjMjAzNzg4MDZhZWQ4YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2YzUyZmU0ZTgxMjIzMWU2NGIyMGFmODc0MmI0ZGJjZWNhMjlkMDFjMTVjYzk1NDNjYzc0ZGFhNDAzNjlkZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmZGI5NTg3M2IxNmNiMTEwMGJjNGVmMGZmYWFiMzZmODc1Y2EzYmVjODY2MmIwMDA5YzEwY2JlNWE2YWVjNTMifQ" + } + ] + }, + { + "id": "claim_56734480c5014ddf8eee4da01df5347b", + "statement": "Repository-instance retired IDs block late saves and tool events from edited target, removed later and adopted staging runs; truncation and snapshot-save errors roll back SwiftData changes and restore the previous retired-ID set.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L156-L165", + "version": "repo-lines-v1:sha256:c887b3a1878d05b6825a0061c2a4ac62fa358fae324872204ffb83d6b940c2ed:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MDIwMDcwNjJmOGU3MDc5ODNiZjBkYmI4ODMyMDYwMjZiMzM1ZGY5ODBmODNlZjQ0YWYzYjRjMWI3NDA0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMwNzU5M2RmMjVjYjQxMmZiNWI3NWUwZWI3NWQ3NGM1ZWY0YjBhMzE1YmJlMWRmYjcwMTg4N2MwMjEyMGM1NjcifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L16-L121", + "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L443-L451", + "version": "repo-lines-v1:sha256:30e4b2269fdafb0b95f119cd557ef5b625420f3ef1dab432701fb54521508e92:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWJiOTAwN2E3ZTAwNDcwMjE5MWJjNGJiYzcyN2RkODJmMGM0NTE3N2ZhODY0OTU1YjMyYzA5NWFkOGMxYjVlMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMThkODhkZDM2NTI2NGYyYjVlZGNmYjM5NDBmMzg2Mzk1MGI2ZDRkYWJhOWY3NDc4ZjM5MzA1ZGRmZTM5MGY0NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDdmMTAyMGM1YzI5MDdiZWJiM2Q0NTFlMjdjNzE3NjE1ZDNiMWNiNWU5ZjM2ZDM4Y2ZjMThmNWNhOTI1MDRhNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTQ0NDYwZGEwZGZkMWMxYzZhYzc4YTAwYTRjN2NmNzAwYTc2OGE5NjE2NzI2Y2M2NTdkYTMyMjhlZDQxOTc2YiJ9" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L504-L516", + "version": "repo-lines-v1:sha256:f5fa05397f6c5f103da1b22e51552d54d9f197d9f1cd73b34142a57e11b9d9cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYxZjE3YzM4MDE4ZjhkMDliMDEwMWYzMDI4ZTlmZDk1MGJmZWVhMjljYjAzMTkxYWVlNDNlNzBmODU2MTViMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhZGUyYTZhM2JkNmI1M2Q1MWI3ZGZjMzk1MjQ2MDBiM2I0ZDQ2OGMzMWM1Y2ZjNWM5MThiNjgyMDExOGY0ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0Y2FmM2FiYTk4ODdmMWYwNWQ0OTgyM2ViOGQwNWE3YjhhMDkwMzhlNDQxNWE2ZmYzMDg3ZDQxZWRhOTQ2YWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI2ZjkxNzkxMGMyOWI0MDdmMThlZjI1MDJiNmU1NWE5NDc1M2NlYjVhNDIyMzY0NDUwODgyODBiZjg2M2E1YzUifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T03:40:50.823Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index fa3371ad..abfa490f 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1235c885f381c01020d2218636f500707c8fc6b3b1e8e4fbd712336ef4deb064", + "pageVersion": "sha256:6f9cbf0a3aa5fea9637709e225bee6cf28af7cdf172609b3c903f75bade148b7", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", @@ -199,15 +199,15 @@ "version": "repo-lines-v1:sha256:da292992815fff9efe5274ba635a38f9c4189246f1737ba3ca30ef3208ab7ad7:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcxMjIxN2Y4MzliNTcyYmE2ZWI3ODA3YTNkMDZkODBjMmFhNTA0NzU1MDUxZTlhMWQ3MTk5YjJlZmNiN2FhMmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVmNjA3ODI4ZDQxZDU5YjhjM2Y0YmZmOWQ0ZWQyZDRkODYyMzRlOGZmOGE4M2U5OTQ4OGYyMjhjNWQzMzMzZWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjliZWQ2YWM2NjQyZTQ4OWUxY2Y0NmNiY2EzNjhmYTg0MzM2OWYzYzgxNGYzMDZlM2RjMGRiMjQ0M2VjYjY0MWYifQ" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L550-L605", - "version": "repo-lines-v1:sha256:6aa3a9f8b2bd8335e17c386f68b91564551f30ee25e239ae960ae032ef4f90fd:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzNzE0ODNiZmQ5OGM1MGM5NDg5ODlmOWEwNTJjMzI1MGUzNzMyYTczYTc2MGY1ZDJhN2I2MjFhNTJhNmUzYjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYjUzMTU2YTNkYjA5MDdmY2I4NDEwNjJjNjc5NjdiMzMwY2I5NTk1ZjViNWEzM2I4ZWVmNTZjZTVlMzczZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyZTEzMDIxNmFiMTA4MGE5ZWZiY2Q1NGIzMDdiNDk0ODU2YTUzMTYxNDc2Y2Y4ZWNjZGM0YTYxYzk3NWVlMTIifQ" + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L16-L121", + "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L6-L75", - "version": "repo-lines-v1:sha256:d6a55a5ac83894bc6f31facb7b9ec18a1aa3d1c286a07c6868034542f73c54f2:eyJzZWxlY3RlZExpbmVDb3VudCI6NzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzNzE0OTM1NDEzMWZlZDcyNmU1MTA3MDQ0NjQzM2M5YzczMGY2MWQ5MWJlZmY1MDY4OTYzZTI2NjhkZThjYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3ZmIwOGJmYjIyNTk4ZmJiOWM0NTgyNjRmM2MxYzZiMDA2MTY4YjY5MTY2MmJjNTBhZTVjYzVlMjg2ZDU2ZmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE4NjBhNzU1MzgyZWFiYWMzMmM1MTBkNmYwNjU1ZjIxMWJmYjM4MzA4YzZjMzU5YTdkNzk0YzM1NmRiNmNkYzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwODY1Mjc5OWI1MWU3OWUxYTg3OTZlZDMzNDM5ZDVmNTEwNTYzYjA5ODY1ZDgyOGJjYTFiZDMwYTY3MThmYmEifQ" + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L746-L786", + "version": "repo-lines-v1:sha256:0bbf3410aaea40a96735687ffa5a11a929afa27945aae74e81a0b4f54512d7bf:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYzMDUyZGM5NmZhZDk0ODE0NjNkNjcyNzYyOTZkNzk1Y2Q1ZTAyODJmZmNkMDAzNzAxZTEyMzcwMjM3Mzk3NDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljMjdkYzQ2OTBhZmJjZmM5NjgxOGIyYWZkNDY4ZjkwOGUyNDBhYTgwZWEzZWYwZjcyYjM2ZjNhOThjYTUyNjgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ5ODY4MDg2NjZjMjE5ZjM3Y2M3MzlhZGRlOWQ4NjBmZDIxNWRkNDc4MDU1OWI4ZWFkMzlkN2Y4ODFhYjI2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiZTE1OGU4OWZkYzM4OTc0NzZhMGZmYTc2NjE0MmUxNGNkYjIyMTNjYjIzMmQwYTEzMGU3NTlhNWM3ZDllNGUifQ" }, { - "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L671-L683", + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L853-L865", "version": "repo-lines-v1:sha256:f142043bb1b01de9a0458b3995239712303b9ee5f7cd1bc4531d3ee6da2913f1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5NGQ4ZDkyN2FkMTU0ZTM2ZmFhZjZiMmI2MjMxZDllN2Q2MzRiY2IzZTliMjI2MzA4ZmJhMGVkOWQ3MDY0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] @@ -321,10 +321,116 @@ "version": "repo-lines-v1:sha256:a89d5d90ca621038753469ce401c92c24921c2515141ca78fc72195b90062176:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDM0ZmQzMmQzZjgwNTA3MTc4NmY5MjIwMGZhMDhjOWZjMWRkZmRkZTk4ZDk0MjkyMzZhYTkwNDcwYjA1MTkwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzkzMmI2ZmJjYTcwYzM4M2RmM2Q5OWNhMjEyODM0ZTIzMmE2NzFhYWVjOGFmOTM1YjZiMWUwNjFhZTRhY2U1OCJ9" } ] + }, + { + "id": "claim_da4a4d45226a486b9d742042ac641b6c", + "statement": "Assistant restore and truncation share canonical run start/UUID and message run/sequence/time/role/UUID order. Truncation validates before mutation, removes the target message and suffix plus later runs, and preserves a same-run prefix with a neutral completed summary ending at its last retained non-tool message.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L214-L229", + "version": "repo-lines-v1:sha256:bf0a95952f58864d9e6156cdff6e8a1b8ba739ec591ea780cd43f2922ba6f493:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMWM2ZTBhOTU1MmUxOGZjODUyZjU3MjhkM2ZiOTkyY2Q0MDY4NmU1ZmI5MzdhZTk2NGMzZTcyMmMxNTZjM2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjNmY3Mjg3ZjAwYjYzYTNhZTEzMDA1YTU2MmI1OTg2MjNhNzMyY2QyNDM1YTUzOWZkYzk3NzU1NDVlOWI0NjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFjZDFmZWMzYjNhNjkyNmFmMWE2MWU0OGYwNmQ5MDViMzk5Nzg4MGU0YTk4ZWQ1OGI0NDdiYmIxN2NlMmE1NzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ4NTkxZTdiMmE2ZTc2MzhiZThhNzA0YTI5MmNlYzZjM2E3ZWViNDE2NzMzZjRkYTM2ZDEzOTcxNjA4NzIyMjMifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L398-L468", + "version": "repo-lines-v1:sha256:e172abbe510013e60b30792072b63805778cdff71e214aa3a350786a1e97e53d:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0OTJmYjhlNzg0OTUwYzYxMWY1ZjY0NmY4NTcwNjIxMzQwMWViMDM3NmYyNDExNzdhN2E1NDgwMjlhODYxMWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwOTdmNmQ1MGQ5NjQ1ZTYyY2E4OWQ5ZDUxMmEzZTg3ZmJhOGQ3OTNlNGQyOTY3OWE1ZmYyZGI0MzNhZTZmOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVlNmFlN2MzOThiOGNmNDc4NjJmODRjZWJiZjQ1NjIzMjA5NWQ4OWEyMGU0NWJlNDE0MDg4YzVlOWQ2NjMwYjEifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L690-L713", + "version": "repo-lines-v1:sha256:487650c28b9099463e7bfa7834c959b0cf74fc3d13d1b1961ff07461c20fe72d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmMDAxZTQ3OTg4NTAxYjJjMTQ3YmI0ODRlN2FiMWFkMTJhODcwM2JlZTA2MGJkOTdkNWM4Yzg3YjMwNGYxZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwZmIwMDQ2MGVkM2FlMTI3MTgwYzExMDE3YzE3ZDYwMTAwNWM2MmFiMDVjOTFjYzZhYzk0OGRiOTAxMTE2YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdkM2E0OWNiYWFhZmQzZTYxYzdlNGZhZWU4NzViNGQ0ZjBlMzA2NzAxMzM2ZmNiNTI0MTMyMjJhOTk2MTU0YjYifQ" + } + ] + }, + { + "id": "claim_9650e27cbdde419f9b8ba9bda9cbf0d7", + "statement": "Partial-run graph cleanup prioritizes source-message then call ownership before an inclusive unknown-ownership timestamp fallback that also covers modern provider-native events preceding their source-message snapshot, deletes associated result/review and message backing rows, and prunes only compactions with removed explicit boundaries. Restore infers legacy sources and recovered result ownership before compaction projection.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L265-L300", + "version": "repo-lines-v1:sha256:18f9323794aa82ed081c01504f42cbadf30f212d167e36843b0c25b5bf4e037c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwMGY1NTMxZTgyMjQzZmE5YTUxMTljZjRhYmQ2YTFlNmMyODM4ZmJmOGY2MThkYzFmNzk0OGE2NmU1M2U0YmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZlMWU1OGJiYjE1OGJmM2Y1NmU4NDU2NTFkOWYzMmNiNGQwNzY1ODE5ZWE0YjBlYjc4MWE2NWE4Njk2YjQ2N2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc1ZWM1NjU4Y2ViYTY5NzExNGJjZmQwMWExOWI0OWZhNDY0YjZmMDk3MjFiNWJjYzExZGRhM2VkOWU4MGRhY2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ZjY4M2EzYWQ3ZTNiMmY1M2NiYjYzMzc0ZGEzYTBlZGI1ODAzNjM4MTA3N2VlOTMzNjg4OTJlMWU1NDcyYWQifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L470-L474", + "version": "repo-lines-v1:sha256:ac6200348d5fa872e89fa89195d9ad6b850dfc7436542e8a1198c7e6d86b55df:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2M0M2FjMWM4MjNhNWQ0MDM2OThiODE1MTI4MDNiNDdlYmE3NzE3YjgwYWZjNjM5MjkxNDkxMGI4OGY1MzE4YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGEyYjlmNjUyOWJlMmNiYjFkYWNmMDA0ZGI5ZWIwNjU2N2ZiYmI2MTE3ZjQ4Y2U2N2ZjMmU0NjZiM2M0NWRjNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzRmZTllNWZiZmYzNjljMGQ4MjMzNmUwMDU0YWIyYzg3NmE4MGQ3ZWFlNjM0NWQ4ODIwMDRiODJhNzlkOTFhOCJ9" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L633-L669", + "version": "repo-lines-v1:sha256:00ed4f8e4c2bab3bf461e4171d9fdcbef3414b107c82c5bd771cdf8ab34b18b3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1ZDcxNzk4OTdjOGViNTc4NjU2NWY0M2E1Nzk1M2I3OTkzYzUwM2ViYjM0YjllOWUwMmU3MzFjYzY4OGJiNmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVjNWUzYWMwNGVhMDg0ZjQ4ZGJhMDY5MjhiNjUxOWQ1OTI3MzE4YzhlZjc2ZTMyNmE0NDc5MWFiOWE0ZWU3NzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3NDJmODZmOTJlMzg5MTljZjQzOGQ5MjdlYTUwZDMyZDkwMjljZjgxOGJmYTFkNWYzYWZiYTk1NGQxMmNlNzMifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L972-L1015", + "version": "repo-lines-v1:sha256:a4c0ef3257d9a4c53b2a8cac0f20fd52f9bf2f82bbca7a44d7840155674361d4:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxZmUxMjYxYjA1NGUzZDE5ZDFjNWZmOTcyMzg2NDRkZGZlNGVjNzQ2MGYwMjNmNmZlZTA3MGM2ZDA0MzFjODAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4NzU5MzZhZDg0ZTBlZTQxMTdmOGQ2NmQzNjE4MjA2MTBhZDAwMTM5M2Y3YTdkNWE4MjNmZjM4MDM1ZjBlNWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZhNzUyZjZmMDU4ODJhMGVlNzg0YzNhZWY1Y2ViMDZmNTg1ZmM2YWI2MzI3MDIwZTc1YjA4MTdmYjMzNDkwMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM2NmVkODY5OTU0Zjg2OGM1NTRkODg5NDgxOTk5OWY0ODI5NjQ1M2E4MGFmYjMxOTkwYzU1Y2I0ZDA0ODZmYzgifQ" + } + ] + }, + { + "id": "claim_cafd574718084c48b15cfd901883e8e9", + "statement": "An edited replacement is stored in a same-ID completed staging run ordered after all retained runs; its first real snapshot transactionally adopts only that message's run/sequence, retains replay/attachment backing, deletes staging and preserves ordinary historical owners. Failed/interrupted snapshots share the adoption path, and subsequent saves retain the adopted chronological lower bound.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L71-L112", + "version": "repo-lines-v1:sha256:081bbe5beeb6834585986890a850825852d4c0e5010dcce28caea70d9a958c8d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyMmZhOTY2Mzk0NWFiMDAxZWNjMWQ4NDY0NzE4OGYwZmZkZDlhNjM3MGEzMWEyOTBiMDlhYWI1NzcxNmY4YWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YTJiYWYxMjA1ZDE2OTUzOGVjMWE5MTljMTg2NTc2N2M4YzJkMDg1NmQzOTJlM2RmZDhhNjIwYjQ0OTdjNGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3MzE2YzY1YWJjMWM2NmY5MWYzZjM4NWM3OTNkMjEzMjNlYjk3ZTkxZjNiNGVmYWI0OTNjMGE4YmY1YzUzZGIifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L16-L121", + "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L476-L503", + "version": "repo-lines-v1:sha256:c1a8d918c5409018d4549fe64ed7fe6080468331b1c24a04cd3bb17761a6272b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwNzAyNmVmMjJiMzJkYmQ3OWY3NDc4ZDYzMTg3YTBjZDI4ODNjYjYyMTM4MmU3N2QzNGQyMjcyYTA3MzFlOTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3NDM2MjdlZDk4ZTAyYjU3OGY1MjY5Yzc2MGVlNTg0ZWU1NDJjM2UzYzlhNDQwODkxY2QxNTE2MjVhNTlkZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlZjBlYzVlMTdjMGI5MTM2OTFhYzdjYWFiYmVkZjA1MDRiN2Q3YmUwMjBmOGZlZGMzOGQ4NTMzNzk2MjVjOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlkZjlmMzU1MjY2ZmFjMjQ4ZDgxZDMwMDc2YWY4YWRiODFjNDBjOTY3NjFkMWQ3MzQ5OGUxY2RmMDNlYWZlMmUifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L671-L688", + "version": "repo-lines-v1:sha256:9430ebe704cfe45e02e2a23637debf77c4d62d0849a7065c405e4b78d26bff00:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY0MDUyMDRhZTk4ZDM4NjdlMTNmYTU5Y2E0MTU4YmU1Y2FkMTMxODZiZTBiMzlhOTZmNTNkMGQ2YmZkNTZiZjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQwYTA4ZjYzZjc3MGZlNGQ4MDFjZmU5YmFmZTg3NjdhMTEzZWM3ZmE2Njg4ZjA1OGU1NTc0ZGJjYTNjMGIyZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFmNjMyY2Y1OTNjNzEzYzQwMjI0YzhkNGVhZjhmZWI2ZjUyMWMyNDIzYzRiMTVhYzVkYzFhODAzMGEwM2U3MWIifQ" + } + ] + }, + { + "id": "claim_19d50bbe64434f3bbc06c0abae0e1ef0", + "statement": "Edited target, removed later and adopted staging runs are retired within the repository instance; late snapshots/events are ignored. Truncation and snapshot save roll back SwiftData and restore the prior in-memory retired-ID set on errors, without adding persisted tombstones.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L156-L165", + "version": "repo-lines-v1:sha256:c887b3a1878d05b6825a0061c2a4ac62fa358fae324872204ffb83d6b940c2ed:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyYmI2ZDU3ODMxMmY2MmZkZDc5Yzg4N2EwY2M2ZWQwNGNkOTA5NDBmMWFhYTdlOWE1MDIwZWY2OTI5MWE5NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2MDIwMDcwNjJmOGU3MDc5ODNiZjBkYmI4ODMyMDYwMjZiMzM1ZGY5ODBmODNlZjQ0YWYzYjRjMWI3NDA0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMDIxY2E1Njc1M2E4ZWM0MDdkZjY5ZjhhNWM4ZGMwYmJiODEyOWE2MTQ1N2NjYWNhYTdjOGYwNzEyMWQ4NTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMwNzU5M2RmMjVjYjQxMmZiNWI3NWUwZWI3NWQ3NGM1ZWY0YjBhMzE1YmJlMWRmYjcwMTg4N2MwMjEyMGM1NjcifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L443-L450", + "version": "repo-lines-v1:sha256:ac80ea14eb69afa592a1e4e5c065857c6f79fb565723f8272ad51d7de5f79865:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWJiOTAwN2E3ZTAwNDcwMjE5MWJjNGJiYzcyN2RkODJmMGM0NTE3N2ZhODY0OTU1YjMyYzA5NWFkOGMxYjVlMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGMzMDJiOTM5ZDRkZjk3Yzg4YzkxMDE0ZmRmNjRkMTI2M2I2ODEzYTYxYjhhNThlNjBjYzM0MzRkZDM4NDg0NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDdmMTAyMGM1YzI5MDdiZWJiM2Q0NTFlMjdjNzE3NjE1ZDNiMWNiNWU5ZjM2ZDM4Y2ZjMThmNWNhOTI1MDRhNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDhhZTFlYzI0ZTJkZDEwY2Y0Yjc5ZDRjNzUyODNhNzkxZTMxNjI0ZTRkYzQ2NmRlNzAwYzAwYmI3ODEwMzA0NyJ9" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L504-L516", + "version": "repo-lines-v1:sha256:f5fa05397f6c5f103da1b22e51552d54d9f197d9f1cd73b34142a57e11b9d9cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYxZjE3YzM4MDE4ZjhkMDliMDEwMWYzMDI4ZTlmZDk1MGJmZWVhMjljYjAzMTkxYWVlNDNlNzBmODU2MTViMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhZGUyYTZhM2JkNmI1M2Q1MWI3ZGZjMzk1MjQ2MDBiM2I0ZDQ2OGMzMWM1Y2ZjNWM5MThiNjgyMDExOGY0ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0Y2FmM2FiYTk4ODdmMWYwNWQ0OTgyM2ViOGQwNWE3YjhhMDkwMzhlNDQxNWE2ZmYzMDg3ZDQxZWRhOTQ2YWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI2ZjkxNzkxMGMyOWI0MDdmMThlZjI1MDJiNmU1NWE5NDc1M2NlYjVhNDIyMzY0NDUwODgyODBiZjg2M2E1YzUifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L6-L24", + "version": "repo-lines-v1:sha256:892b2744f0e5c8d0f42d21afaff71c62cb2d48bf0ccdb614593ec779a826ae65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzNzE0OTM1NDEzMWZlZDcyNmU1MTA3MDQ0NjQzM2M5YzczMGY2MWQ5MWJlZmY1MDY4OTYzZTI2NjhkZThjYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjMjU4YjdiNGY1YjRhZjg4NjE4ZDI4NGJkODg1Y2VmYzNiMmQxOWRhMTk5OTlmN2ExNDI0YmQxMDliMjNjZjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE4NjBhNzU1MzgyZWFiYWMzMmM1MTBkNmYwNjU1ZjIxMWJmYjM4MzA4YzZjMzU5YTdkNzk0YzM1NmRiNmNkYzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyNTlhZmUxOWRiYmEzYWM5ZDQ2ZjRmNGNlMmU0MzM5N2YzZTQ0ZjYyYTQ4NmQ2ZGNlODcxZGQ0YjQ1N2JiODMifQ" + }, + { + "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L90-L121", + "version": "repo-lines-v1:sha256:f2273d4756ab02a62dc182fe44d4409246cd7c58e706cb74cf8dfdb820a99c9c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExZWJkNzZiM2QyNjMxNTM3MzYzZmEzODEwMjI3MjEzOTJjZmYwZDI1YWNkMzJiNDQyYzA4MWQ4OGI3NDBjMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjZTM3YjM3ZjA0YjE4ZDA4OTQ2YWZhNjczZTBjMjM2NzIxYTAzOTA1NDZiMzg1NTJjMjRhZjcwNzhlN2FiMzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRjM2U2ZTI3ZjQ1YTQwZjU2ZmU1YjRiMjNlZjQ0ODQ3NjMyZTJhZTIzZTk5ZGQ1YjZkNWIxNTA0OTMxMmZhZWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1NzFlZjZmNWU5NjMxMThkMzcxN2I0ZDgyYjRiMGI5OWIyYjQ5Mjc2NjM0YWM1ZmI2ZmEwYTdlNWM0MGVmOGMifQ" + } + ] + }, + { + "id": "claim_b0b5b4708fda4de6bcc5775728746d34", + "statement": "Application history mutation publishes only after truncation commits, but captured missing-model/credential failure still projects live before a separate asynchronous save; live failed/interrupted adoption therefore does not turn preparation completion into a universal persistence barrier.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", + "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", + "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", + "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T03:40:50.823Z" } } diff --git a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json index d1a9589b..0279647d 100644 --- a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json +++ b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:57c765ca650c5a286d85e4b4740dbd3e17d8d481a2fbdae60324abe952962271", + "pageVersion": "sha256:a1bfef0a62680b258aa4a9db1d84956798dfe6deb3c50df7c2aa29ba2ffa6ebb", "claims": [ { "id": "claim_b7fbd64158554b8b8ca80a0a613b3bc3", @@ -159,12 +159,8 @@ "statement": "Agent replay persistence tests verify frozen context/tool-result restoration, prevent replay-row duplication across later snapshots, remove replay backing on conversation deletion, and preserve a durable result recorded before the next snapshot.", "evidence": [ { - "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L12-L64", - "version": "repo-lines-v1:sha256:1d91c37540c7dea05756c281709cc014a95b0723c78284b8a979570f6e9ea3d6:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBiNTFmNjI3NGQ5NDk5MTAwOGM3NzI0OTMwMmZhOGQ5NjcxZDE5NmExOTk5MzdmOWM1ZDhmZDk1M2UzYmY0YjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkYmFkZWE5NTg5OGQxNzlkNmY2NjRlMmZhYWU2ZjkxY2YxYmZlNDc3ZTRkNjAxMjBiOWU3NTFiZDNiOGIyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3MzE2YzY1YWJjMWM2NmY5MWYzZjM4NWM3OTNkMjEzMjNlYjk3ZTkxZjNiNGVmYWI0OTNjMGE4YmY1YzUzZGIifQ" - }, - { - "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L66-L93", - "version": "repo-lines-v1:sha256:a7a6a69692b66acfabab87d67d6cdce42517ed039c7a5aca47c32a120bac565c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5YzY1ZTM1MTNlYmM3YzUxNTU2ZTY5N2M1ZmM1OWQ4NzQ0M2Q0MTEwOWEzYzcxNWE1N2Q5ODQ5YzEyZWI3ODciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L12-L69", + "version": "repo-lines-v1:sha256:5f1da27d6edf07122f88fab1e06ba2ca7c5d2e2f42a474ed81a99a27bef44804:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBiNTFmNjI3NGQ5NDk5MTAwOGM3NzI0OTMwMmZhOGQ5NjcxZDE5NmExOTk5MzdmOWM1ZDhmZDk1M2UzYmY0YjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkYmFkZWE5NTg5OGQxNzlkNmY2NjRlMmZhYWU2ZjkxY2YxYmZlNDc3ZTRkNjAxMjBiOWU3NTFiZDNiOGIyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3MzgwYmYzNGNkYmJjNGVmODdiMWZhMTc2NWNiYTY1OTFiZGJmODk4ZDAzOGNkNmY2MDg5YTAxYzUxNGU3MzEifQ" } ] }, @@ -235,10 +231,88 @@ "version": "repo-file-v1:sha256:fb1177900e93b9a2d3f30d4b728e61381d7d1336226ac22610e85779b8d0dadc" } ] + }, + { + "id": "claim_3b969ef21d1f4b9a9a5de6d84cf1daed", + "statement": "The same-run SwiftData edit regression uses equal timestamps, reversed UUIDs, compacted production restoration, legacy and unknown tool ownership, backing-row counts, staging adoption and late-write attempts; the empty-run regression separately proves live/restored ordering when the replacement clock moves backward.", + "evidence": [ + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L1076-L1129", + "version": "repo-lines-v1:sha256:efe03eb3ded07d84ffe8704f6ed27d9afb9bb2bfb21c8f53d5fbfa2c747a6552:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJhZjRjZmQyZjMzYTRkZmMwMDJmZmE2MWVkMDg2ZWU0YWIzNWU3NjA1YTZhNGFlZmM5YTc5YTFkZTMyYzIwYzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFmZjBkMGU1OWU0ZjE1YTE3NTI4Njg1NTE2MjkxZDVlNTM3N2ZjZjVmMTIyNmM1Y2U3NjVmZWM5YzM4YWU1YTYifQ" + }, + { + "resource": "repo://TFMTests/AgentPersistenceTests.swift#L872-L1074", + "version": "repo-lines-v1:sha256:2a68be1f9c455cfee76bc0c6c557a1d8af1bf909f951786b861e71498a31b67e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxYzlhMjg4MzM2YWFlNTM2ZWUyNDBiMGZjMGU5MTI5YzMwYmYzZGZiNmJiNWQwYzI1YThlNTk3ODk2N2Y1Y2Y3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI5ZDAxODMwNWY5YTk2MzdiODgxYTY4ZWZlZjM3OGU0NTdhMDkxYWExMmIwYzQ2ZmYzZDVmYjgzN2RkOTE1ZGY2In0" + } + ] + }, + { + "id": "claim_9334c68e96f0408a9c627c888cd2c1aa", + "statement": "Failed/interrupted staging-adoption tests verify one real message/run/replay graph and restore provenance, state, failure and frozen context from a new ModelContext and repository over the same in-memory container rather than proving disk relaunch.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L71-L112", + "version": "repo-lines-v1:sha256:081bbe5beeb6834585986890a850825852d4c0e5010dcce28caea70d9a958c8d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyMmZhOTY2Mzk0NWFiMDAxZWNjMWQ4NDY0NzE4OGYwZmZkZDlhNjM3MGEzMWEyOTBiMDlhYWI1NzcxNmY4YWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YTJiYWYxMjA1ZDE2OTUzOGVjMWE5MTljMTg2NTc2N2M4YzJkMDg1NmQzOTJlM2RmZDhhNjIwYjQ0OTdjNGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3MzE2YzY1YWJjMWM2NmY5MWYzZjM4NWM3OTNkMjEzMjNlYjk3ZTkxZjNiNGVmYWI0OTNjMGE4YmY1YzUzZGIifQ" + } + ] + }, + { + "id": "claim_c9d1bfa40a4f4aa7b0a058a3fbfed8e2", + "statement": "Composer regression covers compacted same-run display-prefix, tool, title, plan, compaction and run chronology retention; mobile exercises SwiftData restore and operations edit to compare live and restored prefix ownership.", + "evidence": [ + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L154-L199", + "version": "repo-lines-v1:sha256:d337b352f4c959c9d41ff735d806ee5d7b84f73490eb20bbd0c7e4ed6d69643a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxMjNjNGMzNzhiOWZiNzhjM2ZjOWNhMzc0YTNhNjQ0OGE5ZjAzMzJiYWY1ZmEyNDIxMGJjMDc0YjlkMmRmNTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU1ZjljZmVjYTlmZmQyNDRmMzFjNzdjZmNmNjMzYTA2NmZkZWQyYzI3OGVjNWQ2ZmRiZTU2ODM0NGY1ZWM1N2YifQ" + }, + { + "resource": "repo://TFMTests/ChatComposerStateTests.swift#L2268-L2351", + "version": "repo-lines-v1:sha256:c62222b56de5a198a7a34c403aeae467a2c6d9500b19185607bbfe80920954d7:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTZiMTA4ZmJkYTIyOTRkMjBjNmZiOWVmMzk2MDNiMTI3NzFhOTdmMzY2ODM5YWM3ZjUyNmExYWYxNGI3ZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ5NzZiYzRlZGFlZTk4NTVlZWFkMjc5MDI4ZmZlMDgwMjE5YjQwMzQzMWM1NDA3NDRkZWYzNjgyZmIyYTdhZDYifQ" + } + ] + }, + { + "id": "claim_612b95bf7d214bf6b16c67c57ea31156", + "statement": "Application edit tests reject invalid replacements before repository calls and preserve live history after injected repository rejection; controller cancellation after committed editing checks interrupted staging adoption. These fixtures replace the persistence port and do not inject real SwiftData storage failure.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L228-L266", + "version": "repo-lines-v1:sha256:d504dc650e02ce7236152c633382cef19bd6762a026047c05f7c1ad0785f7a7c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyNWIwMmI1NzE0ZDA2NmZkMzIxNmQyMjMxYmRkZGFjYjM2ZDcwODc3OTE4NmRhYzg5MjQyYWViZDA2ZjQ1M2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiYjYzYjhkYTE1ZTdkOGZhNDBlNjhmYjhkNjY0YzhjZDQ4NGFlMzQ4NGEyMjEzNWU0NDAwYjA4NjI3Yjg2ZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNWU4YzMwNjkwMDIyNmYwNDA2OWM5YTZmMTZjNmY0ODE2NzkzYzM5NTNjZDM3YWFlNzQ5OWY2OGQ1YjAyNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L290-L309", + "version": "repo-lines-v1:sha256:1aadebda28ab65aaf172c7524dd75bfbc882c73879618892eee83488349e51aa:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMGI5NDg0YjQ4YTE3M2QzMTM2ZjhkYWRiNzdiNTUwYWQ5ZmRiMzU4YjMyMjllNTRjOGQyNjg1NGNkMWVlNGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc1MjhmNGExMDgwN2M3ZDcxOTRjZTUxNTlmMzNmMjFiZjM1YzYxMzA5ZGNhNDkzNjgyOTViZjBkYzE3Y2QyYTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkMWY5Njk2ZTM2ODQ2MzcxNzIxODJiOGMzZmNlMWFlMzQ4Yzc4MzJkOGU4OTE0ZDVjZjJlZjU1NDIxNDIwYWUifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L450-L534", + "version": "repo-lines-v1:sha256:fd0683e00df11cd8ca35568565d1b1d43a68ad37ab7bf54e5f68803954f14349:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + } + ] + }, + { + "id": "claim_9b91e98a29a34c4caa268050b13a5f3e", + "statement": "Missing-credential and selection-during-preparation tests wait for the recording repository's failed snapshot as well as checking live failure because production captured failures publish before asynchronous save; actor waiters handle both already-recorded and future snapshots.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", + "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L86", + "version": "repo-lines-v1:sha256:80d25cc0451a928bc50f91e07a28b411b83c0d7299889938c179b8d5e96fb33b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L661-L689", + "version": "repo-lines-v1:sha256:74df831f049f3d94b2df3e3684005e41efcfdbae4210f2a76d72f07088772ff4:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3M2UyMmU5ZjA4MTYzZWY3NDM5ODE2MTc3YzcyODZiZjg0MGRhODFhYTAxODc0ZTUxOTExNDM3NTNhNTIzNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4YTQ5OThkMTFmOGNhNTNkOWY1ZWE2OWQ3ZmJmYmE1MGViMTFhMzBhMTdkNDZiNzQzZDZiN2EyNzA5MDhlYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVjYWZkOTVhYTdjMWM0MzA0YWEyNTI0OTg4YWNlMTM1NjQwMjU1MGZjYTg1ZmY5ODlhODEzYjNmNTEwNmJmZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRlMzE4ODBjNjRmODIxZTk5ZmMxNGNjNWE0MDY5OTE0MzQzZTIwNGUwYmQ3N2U4NjU5ODhjMzQ1Y2RkZTdiMDgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L765-L793", + "version": "repo-lines-v1:sha256:e87d08aec434dbaca6f03d91f4f5c0cab6e7e04e6040b3e13dc277d1eff4ed2f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4MzY5NDdmMWVlZjZmNjIwZWJlZmQzMGE0N2EwNWI4ZDhiZWI2ZTNkYTFlZjkzOTM3MTg5OTZiZTI1NDkxYWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T03:34:06.838Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index a932dbed..858ad735 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T02:48:46.050Z", + "updatedAt": "2026-09-23T03:41:38.907Z", "command": "update", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index b50b9778..46e5b881 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,53 +2,53 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:aacd9e53709881d4edac532fd23dfffe4e8ba8cf7a5b611a79bfd846f1128d8a", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:564964f95e8176182ff915ce6d8834e7717b6a3a3be946666c5b1d1d749906cc", + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", + "pageVersion": "sha256:902f023527c34c7a6aacb2a481737931b5f30946486a41b279723af0321cc821", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "03a5a83b-2791-4c16-87fa-a3244c996460" }, "/openwiki/architecture/mobile-ui.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:5a57c9f6cd1214629ea2a75c3ba60386a70d5c2170cdb552f4dea473f0dde193", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/module-map.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:4139c19be34c8fc47c74c1b05a07851c8e18bdfafe33237f7507fbc542e4b360", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/native-app-directories.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:f1dc10b8e646cf711a546c2b764e9d898ad017b4ef0f3bdc7f1932e54e746ae5", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/platform-runtime.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:0e61cbe711eed5c27ef6466c5ce521193641c61d25abf03298b9098834235f2f", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/shared-module-directories.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:afb1bab3e4c91081f597fea301a3472ef878abc9493b4e6bcc9014ba1aabe4e9", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/supporting-system-directories.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -65,11 +65,11 @@ "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "pageVersion": "sha256:1dba6f651d9536e5ea63b9bd551b7e20cc973f0816613aec3593304b42c8cbfb", + "pageVersion": "sha256:46c6736cc2e227449885be7dd7fbf51a7b98259f7509c85a334881cc20382e90", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9", + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,11 +79,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:1235c885f381c01020d2218636f500707c8fc6b3b1e8e4fbd712336ef4deb064", + "pageVersion": "sha256:6f9cbf0a3aa5fea9637709e225bee6cf28af7cdf172609b3c903f75bade148b7", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9", + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -156,18 +156,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:89970353463e9a930e9c659614c1e55ef4107801b0721beda98be323539ac450", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:57c765ca650c5a286d85e4b4740dbd3e17d8d481a2fbdae60324abe952962271", + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", + "pageVersion": "sha256:a1bfef0a62680b258aa4a9db1d84956798dfe6deb3c50df7c2aa29ba2ffa6ebb", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "03a5a83b-2791-4c16-87fa-a3244c996460" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { + "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", + "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", "pageVersion": "sha256:8ed164f2bf48e753c9305fac8b0f7e7754b6e223ed99d68ba71ed6c8ca191334", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef", - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691" + "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" } } } diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index f7aa1ab2..d1ce7fa6 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -10,6 +10,8 @@ sources: resource: repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift - id: openwiki-source-5f99e8af445e073ffe9bc257 resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift + - id: openwiki-source-1c48f72c0c7a3164d7d5c715 + resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift - id: openwiki-source-5c91b796f7e6d651aedf3023 resource: repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f @@ -36,6 +38,8 @@ sources: resource: repo://Frameworks/TermindPersistence/CloudSyncSafetyStore.swift - id: openwiki-source-fe1dc993163bb9ff61397be7 resource: repo://Frameworks/TermindPersistence/HostPersistence.swift + - id: openwiki-source-28d6772acb1a409d29ff1973 + resource: repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift - id: openwiki-source-7a9c6d90cba4ebd1d1398673 resource: repo://Frameworks/TermindPersistence/TermindPersistence.swift - id: openwiki-source-8185a608f0b79f1af097efbd @@ -110,10 +114,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T02:40:53.789Z" } +generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T03:34:06.838Z --- # Cross-Cutting Engineering Invariants @@ -506,6 +510,14 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo history/preparation work before its interrupted save; stale captures cannot edit or start. Archive/delete cancel pending preparation, manual compaction refuses it, and navigation alone leaves it running. See [Assistant Runtime](/openwiki/concepts/agent-runtime-and-assistant.md) for the preparation boundary and existing runtime/recovery limitations. +- History edits validate before persistence and publish their live replacement only after truncation commits. The boundary is the selected + user message in canonical run/message order, not its whole run: retain the same-run prefix and owned tool graph with a neutral completed + summary, remove the suffix and later runs, and prune compactions by removed message IDs. Compacted display history still informs ownership + and opening-title preservation. See [Persistent Data](/openwiki/concepts/persistence-and-secrets.md) for graph cleanup and rollback. +- An edited replacement initially owns a staging run whose ID equals its message ID. The first real snapshot transaction adopts only that + staged row and deletes the staging run; historical replay rows keep their owners. Live running, failed and interrupted projections use + the same provenance and preserve the staging chronology lower bound. Retired run IDs reject late saves/events in the repository instance. + This is not universal persist-before-publish: missing-model/credential failures still project live before their asynchronous save. - Project tool output once before `toolFinished`: bounded preview and scratch reference, retaining crash/replay metadata and paging offsets. Later completion never shortens earlier results; `sftp_read`/`scratch_read` report complete lines/truthful offsets. - Terminal suggestions use latest delivered user snapshot, not focus. Surface availability is partial and does not restrict independent SSH/ diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index 2d337007..12544a4e 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -74,12 +74,14 @@ sources: resource: repo://TFMTests/AgentScratchTests.swift - id: openwiki-source-7e117096f381ecc32caff068 resource: repo://TFMTests/AgentSkillTests.swift + - id: openwiki-source-78797a18dd3153d23a2c42e2 + resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T02:40:53.789Z" } +generated: { by: "codex", at: "2026-09-23T03:40:50.823Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T03:40:50.823Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -226,9 +228,21 @@ Persisted text, tool arguments, titles, reasoning, and attachments go through re Tool cards resolve their persisted presenter ID through the immutable presenter registry. Cards predating those IDs use the closed legacy tool-name list and its `builtin.` IDs. Registration and surface-availability changes preserve those identities and the persisted context key; they do not require a SwiftData schema change. -Compaction changes the **model-facing** transcript, not the scrollable display history. Its summary record is persisted before the live conversation applies it; after recovery the model receives a synthetic compaction summary plus recent messages while the user can still read the full original conversation. Editing a user message truncates the target run and later runs atomically, creates the replacement turn, and removes compactions whose boundary was removed. +Compaction changes the **model-facing** transcript, not the scrollable display history. Its summary record is persisted before the live conversation applies it; after recovery the model receives a synthetic compaction summary plus recent messages while the user can still read the full original conversation. -That durable edit boundary is still run-level, whereas the live model truncates from the target message. Editing a steering user message within a run can therefore leave earlier messages from that same run in the live model after persistence removed them. This existing durable/live mismatch is separate from stale-preparation cancellation and is not repaired by synchronous capture. +### Message-boundary edits and replacement provenance + +`AssistantConversationOperations.edit` validates an ordinary replacement user message before persistence and applies the live mutation only after truncation succeeds. SwiftData checks cancellation before its synchronous transaction and uses the same canonical order for restore and truncation: runs by start time then UUID, messages by run order then sequence, timestamp, role and UUID. The selected message and everything after it are removed, not automatically its entire run. For `opening → assistant → steering → assistant`, editing the steering turn retains the opening and preceding assistant/tool batch. The retained partial run becomes a neutral `.completed` summary with no old failure and ends at its last retained non-tool message; wholly later runs, including empty ones, are removed. + +Tool cleanup follows ownership before time. Known source-message and run-local call identities distinguish retained prefix tools from removed suffix tools, with results and reviews deleted alongside removed invocations. Restore infers missing legacy source IDs from the raw assistant call grammar; deterministic recovered tool-result IDs receive run ownership. The live projection additionally uses retained result ownership and the full visible prefix when compaction has hidden raw messages from the model transcript. Only invocations/cards without recoverable ownership fall back to `createdAt >= target.createdAt`, conservatively including the exact cutoff. This includes legacy rows and modern provider-native events persisted before their source message reaches a snapshot. Compaction records are removed only when their explicit boundary message is removed; nil-boundary fork bases survive, and live compaction marks are rebuilt. Editing after a retained opening does not clear its title, even when that opening is compacted out of the model transcript; live plans at or after the cutoff are removed. + +The replacement is durable before a runtime exists: its message ID also identifies an independent, completed, failure-free staging run containing that one user row and no tool graph. Its start time is placed after the latest retained run if clocks tie or move backward, including when the earlier retained run has no messages. On the first real snapshot containing that staged final user turn, `save` transactionally moves only the staged message to the real run, assigns its snapshot sequence, retains its attachments/replay backing, deletes the staging run and retires its ID. Historical messages replayed into the snapshot keep their existing owners and are not duplicated. Adoption preserves the staging start-time lower bound; subsequent snapshots cannot move it backward, and terminal time is clamped to that start when necessary. + +`ConversationModel` mirrors adoption on `runStarted`, captured failure and preparation interruption; `runFinished` retains the adjusted chronology. A failed or interrupted replacement therefore has the real run's provenance rather than an abandoned staging summary. The ordering of publication still depends on the path: preparation cancellation awaits the interrupted save and rechecks identity before adopting, while `recordFailedRun` for a missing model/credential projects live first and starts a separate asynchronous save. Neither `!isPreparingAgentRun` nor a visible failed state proves that this save finished. The persist-before-live contract above is specifically the history mutation boundary. + +The repository instance retires the edited target run, removed later runs and adopted staging run so late snapshots or durable tool events cannot resurrect the discarded suffix. Truncation and snapshot save roll back SwiftData changes and restore the previous in-memory retired-ID set on error. These rules use the existing schema and Termind's Application/Persistence adapters, without changing AgentKit or its package pin. See [Persistent Data](persistence-and-secrets.md) for the storage contract. + +### Restore and metadata At repository initialization, every nonterminal persisted run becomes `interrupted`; Termind never replays unfinished execution automatically. Restore reconstructs completed tool cards and can recover a durable tool result that was recorded before its next transcript snapshot. If an outcome remains unknown, repair records an explicit unknown/refusal result rather than assuming a call was never dispatched. @@ -281,12 +295,13 @@ New submissions capture whether they have a memory-session identity before prepa The focused tests establish behavior that is important when changing this area: - `TFMTests/AgentRuntimeTests.swift` covers schema-before-approval/execution, approval mode routing, Guardian timeout/failure fail-closed behavior, single-use secret handles, steering delivery, tool cancellation, registry shadowing, and durable-event filtering. -- `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, deletion of the persistent graph, edit truncation, fork persistence, and compaction restoration. -- `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. -- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order mutations, activity guards, and that immediate stop prevents a queued runtime's model call. That last test does not establish snapshot persistence for the runtime handoff gap. +- `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, graph deletion, same-run edit prefix/tool preservation, canonical equal-time ordering, empty retained runs, staging adoption, late-write rejection, fork persistence and compaction restoration. `ChatComposerStateTests.swift` checks the compacted live prefix, tool cards, title, plans and adopted run summaries. +- `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption and reloads through a fresh `ModelContext` and repository to verify durable provenance and replay backing. +- `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, failed/interrupted replacement provenance, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. Repository snapshot waiters distinguish async save completion from a live failed state. +- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order/edit mutations, invalid edit rejection before repository calls, failed edits leaving the live prefix unchanged, activity guards, and that immediate stop prevents a queued runtime's model call. That last test does not establish snapshot persistence for the runtime handoff gap. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. - `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. -- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. +- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. Mobile's SwiftData restore/edit fixture also verifies same-run prefix preservation and replacement provenance. - `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. ## Change guidance diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index 5f27bed3..2a5adf79 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -4,6 +4,12 @@ title: Persistent Data, Migrations, and Secret Boundaries description: How Termind owns SwiftData and GRDB durable data, evolves compatible schemas, projects records through repositories, and keeps credentials in Keychain-backed and runtime-only boundaries. tags: [persistence, swiftdata, grdb, migrations, keychain, secrets, security] sources: + - id: openwiki-source-685b7f8e7dbcf91b4e2a50e2 + resource: repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift + - id: openwiki-source-5f99e8af445e073ffe9bc257 + resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift + - id: openwiki-source-1c48f72c0c7a3164d7d5c715 + resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f resource: repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift - id: openwiki-source-7ca30f0e781e8c2df826b47a @@ -54,6 +60,8 @@ sources: resource: repo://Frameworks/TermindPersistence/TermindSchemaV29HostModels.swift - id: openwiki-source-15e5026028d13d2202844f68 resource: repo://Frameworks/TermindPersistence/TermindSchemaV33IdentityModels.swift + - id: openwiki-source-017f5e2abe9b13df755a5322 + resource: repo://FrameworkTests/AgentReplayPersistenceTests.swift - id: openwiki-source-bf7a64e7c819eb9279e2f9e4 resource: repo://FrameworkTests/CloudSyncLifecycleTests.swift - id: openwiki-source-40cfadc2320da9a292c64d26 @@ -70,12 +78,14 @@ sources: resource: repo://TFM/App/TermindApp.swift - id: openwiki-source-0854be84b166268626a3cb08 resource: repo://TFM/Features/Sidebar/WindowSidebarView.swift + - id: openwiki-source-715de8d16be5a3d06ae915ad + resource: repo://TFMTests/AgentPersistenceTests.swift - id: openwiki-source-1d1efb20e239b1f2142f6cb7 resource: repo://TFMTests/SettingsSyncTests.swift -generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } +generated: { by: "codex", at: "2026-09-23T03:40:50.823Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T03:40:50.823Z --- # Persistent Data, Migrations, and Secret Boundaries @@ -112,7 +122,21 @@ SwiftData rows are persistence representations, not the feature API. `SwiftDataH Deletion is a durable saga, not an immediate blind removal: `requestHostDeletion` atomically writes a pending-deletion marker, outbox jobs, and the revision. The snapshot hides marked hosts. Later steps remove dependent jump/snippet/history/forwarding references and finally remove host-owned identities, the host, and the marker. This lets a crash expose either the live host or a complete, resumable deletion intent rather than a partial state. -Agent conversations use a separate SwiftData repository with similarly deliberate projections. `SwiftDataAgentRunRepository` stores run snapshots, messages, context/replay backing, tool invocations/results, reviews, compactions, and ordering. It writes only new message IDs rather than duplicating replayed earlier turns, sanitizes persisted text and tool-call JSON, and marks nonterminal runs interrupted when the repository is initialized. Loading orders transcript rows by run and sequence, then restores recorded tool results that were committed before the next transcript snapshot. +Agent conversations use a separate SwiftData repository with similarly deliberate projections. `SwiftDataAgentRunRepository` stores run snapshots, messages, context/replay backing, tool invocations/results, reviews, compactions, and ordering. It deduplicates replayed earlier turns by message ID, sanitizes persisted text and tool-call JSON, and marks nonterminal runs interrupted when the repository is initialized. Loading orders transcript rows by run and sequence, then restores recorded tool results that were committed before the next transcript snapshot. + +### Assistant history edits: one message boundary, one transaction + +Restore and truncation share a canonical ordering: runs by `startedAt` then UUID; messages by that run order, then `sequence`, `createdAt`, role rank and UUID. Timestamps alone do not identify the cutoff. `truncateConversation` checks cancellation and validates an ordinary user replacement with a fresh ID that collides with neither a stored message nor a run before mutation. It removes the selected user message and the ordered suffix, including whole later runs. When the target run still has a prefix, that run and its retained messages survive: the summary becomes neutral `.completed`, clears its previous failure and ends at the last retained non-tool message. Editing its opening removes the target run only when no prefix remains. + +Graph cleanup follows the same boundary. Removed messages lose their replay and context-attachment backing. Within a partially retained run, invocation source-message ownership takes priority, followed by call identity in retained/removed transcript rows; only unrecoverable ownership uses the conservative `createdAt >= target.createdAt` fallback. This covers both legacy rows and modern provider-native events persisted before their source-message snapshot. Results and reviews follow the deleted invocation IDs. Compactions are deleted only if their explicit boundary is among the removed message IDs, so retained-prefix records and nil-boundary fork bases survive. The generated title is cleared only when the opening user turn is removed. On load, raw run-local assistant calls supply missing legacy source IDs, and reconstructed deterministic tool-result messages receive run ownership before compaction projection; this lets the live model preserve the same graph through compacted history. + +The replacement is inserted with its replay/attachment backing into a standalone, failure-free `.completed` staging run whose ID equals the replacement message ID. Staging must sort after the latest retained run, including an empty run: when the replacement timestamp ties or precedes that run's start, its run start becomes the retained start plus one millisecond. The message's own timestamp is not rewritten. + +The first real snapshot whose final ordinary user turn is this staged replacement adopts it in the same `save` transaction. The staging predicate requires the same conversation, the same-ID user row, a completed run without failure, exactly one message and no invocation/result/review graph. Adoption changes only the staged row's run ID and snapshot sequence, retains its backing, deletes the staging run and retires the staging ID. Other historical rows included in replay remain attached to their original runs. The real run inherits at least the staging start time; later saves preserve that lower bound and clamp `finishedAt` when necessary. Failed and interrupted first snapshots use this same path, not a separate copy of the replacement. + +The repository's in-memory retired-ID set includes the edited target run even when its prefix survives, all removed later runs, and adopted staging runs. `save` and durable-event `record` ignore retired IDs, preventing late work from rebuilding the discarded suffix. This is a repository-instance guard, not a new persisted tombstone or schema migration. Both truncation and snapshot save restore the previous retired-ID set and roll back their `ModelContext` changes if the transaction fails. + +`AssistantConversationOperations` applies the live history edit only after the repository commits; invalid replacements and rejected writes leave history unchanged. Live running, failed and interrupted projections adopt staging provenance too, but publication ordering is not universal: a captured missing-model/credential failure still updates live state before its separate asynchronous save. See [Assistant Runtime](agent-runtime-and-assistant.md) for that boundary and the distinct immediate-stop runtime-handoff limitation. Shared replay tests restore failed/interrupted adoption through a fresh context; desktop and mobile fixtures compare same-run prefix history and provenance after edits. These fixtures do not inject a real disk-full or read-only SwiftData failure. ## Schema evolution: preserve the past exactly diff --git a/openwiki/testing/behavioral-boundaries-and-fixtures.md b/openwiki/testing/behavioral-boundaries-and-fixtures.md index 9945125b..d27ee04c 100644 --- a/openwiki/testing/behavioral-boundaries-and-fixtures.md +++ b/openwiki/testing/behavioral-boundaries-and-fixtures.md @@ -6,10 +6,16 @@ tags: [testing, regression, fixtures, architecture, persistence, security, lifec sources: - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md + - id: openwiki-source-5f99e8af445e073ffe9bc257 + resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift - id: openwiki-source-b347308e8c5cc6c0b0d29b7d resource: repo://Frameworks/TermindRemoteInfrastructure/Transfers/SFTPTransferContext.swift - id: openwiki-source-017f5e2abe9b13df755a5322 resource: repo://FrameworkTests/AgentReplayPersistenceTests.swift + - id: openwiki-source-247755d873c5d94b0a364217 + resource: repo://FrameworkTests/AssistantConversationOperationsTests.swift + - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 + resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift - id: openwiki-source-bf7a64e7c819eb9279e2f9e4 resource: repo://FrameworkTests/CloudSyncLifecycleTests.swift - id: openwiki-source-195e2cb735dfac7fb6dbda27 @@ -26,6 +32,8 @@ sources: resource: repo://TFI/Infrastructure/MobileWorkspaceStore.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-a154b5d36ec59496392d45e4 + resource: repo://TFITests/MobileAssistantTests.swift - id: openwiki-source-7a29627adf93e3da4a56dd5d resource: repo://TFITests/MobileCardRenderingTests.swift - id: openwiki-source-16ec96bd78162e7e62effd0e @@ -34,8 +42,12 @@ sources: resource: repo://TFITests/MobileWorkspaceStoreTests.swift - id: openwiki-source-cfbc34def649c94f760c5695 resource: repo://TFMTests/AgentHostAuthenticationTests.swift + - id: openwiki-source-715de8d16be5a3d06ae915ad + resource: repo://TFMTests/AgentPersistenceTests.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift + - id: openwiki-source-78797a18dd3153d23a2c42e2 + resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-c15cd5bef4646aaf64df407e resource: repo://TFMTests/ConnectionHistoryTests.swift - id: openwiki-source-53e2b2fd2e661123f63e69e1 @@ -44,10 +56,10 @@ sources: resource: repo://TFMTests/VaultSyncTests.swift - id: openwiki-source-a08489338be00125d646c77e resource: repo://TFMTests/VisibleWindowTestTrait.swift -generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } +generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T03:34:06.838Z --- # Testing Strategy and Behavioral Boundaries @@ -82,6 +94,19 @@ Persistence tests are compatibility tests, not just encode/decode checks. `Persi Agent replay persistence demonstrates the same principle for durable runtime state: an in-memory `SwiftDataAgentRunRepository` must restore frozen context and tool result projection without duplicating replay backing across subsequent snapshots, and deletion must remove that backing. A fixture should deliberately model the timing boundary it protects—for example, recording a tool result before the following run snapshot—rather than requiring a live provider. +### Assistant history: compare live state, backing rows and restore + +A message edit inside one run needs a fixture such as `opening → assistant/tool batch → steering → assistant/tool batch`, not just one user turn per run. The regression boundary spans several owners: + +- `AgentPersistenceTests.sameRunTruncationPreservesThePrefixAndAtomicallyAdoptsTheReplacement` uses equal timestamps and deliberately reversed UUID order. It restores a compacted conversation through the production store and operations, then checks the retained prefix, neutral completed summary, title, attachments, replay rows, invocations/results/reviews and compaction bases. A legacy prefix invocation has no stored source ID and a later timestamp; its known call ownership must still preserve it. An unknown native-tool-like invocation at the exact cutoff must be removed. Real running/completed snapshots must adopt staging once without changing historical owners, and late old snapshots/events must not regrow the suffix. +- `editingALaterRunOpeningKeepsAnEarlierEmptyRunBeforeTheReplacement` checks an earlier retained run with no messages and a replacement clock older than the retained run. Live and restored staging order must agree. Ordinary replay fixtures give genuinely later runs a later start time instead of relying on random UUID tie order. +- Shared `AgentReplayPersistenceTests` parameterizes the first real replacement snapshot as `.failed` or `.interrupted`. It checks the single surviving message/run/replay backing and reloads with a new `ModelContext` and repository over the same in-memory container to verify provenance, terminal state, failure and frozen context. This is fresh-context visibility, not a disk-relaunch test. +- `ChatComposerStateTests.editingSteeringKeepsCompactedSameRunPrefixAndLegacyToolOwnership` checks full visible-prefix retention even when model history is compacted, alongside tool cards, title, plans, compaction records/marks and run-summary chronology through `runStarted`/`runFinished`. Mobile's `swiftDataRestoreAndEditPreserveASameRunPrefixOnMobile` exercises the same repository/store/operations path and compares live and restored message ownership. + +Application error and timing fixtures complement those real SwiftData checks. `AssistantConversationOperationsTests` proves an invalid replacement never reaches persistence and a rejecting truncation leaves both visible and model history unchanged. Controller tests hold preparation at a memory barrier, cancel after a successful edit, and assert interrupted real-run provenance replaces staging. These seams do not inject disk-full or read-only failures into the real SwiftData transaction; its rollback path must not be described as fault-injection tested. + +Use the boundary that the assertion needs as its wait condition. Captured missing-model/credential failure projects `.failed` before launching a separate asynchronous repository save, so `!isPreparingAgentRun` is not a save barrier. The controller fixture's `waitUntilSnapshot(state:)` checks existing snapshots or suspends an actor-owned continuation; `save` records the snapshot before resuming matching waiters. The missing-credential and navigation tests retain both live-state and repository-state assertions and wait for the latter explicitly. This does not change production failure ordering or prove durability across the separate queued-runtime immediate-stop handoff. + ### CloudKit compatibility: test prohibited uploads, not just decoding The safety fixture substitutes only the CloudKit transport. It uses the production Vault, Settings and Metrics coordinators, journals, record coding, synchronous batches and final record providers. The same cases run for all three categories: @@ -151,6 +176,7 @@ In Xcode, set `TERMIND_WINDOW_TESTS=1` in the Test action environment. Existing | --- | --- | --- | | Framework dependency or data-owner change | `ArchitectureBoundaryTests` plus affected framework suite | No reverse import, concrete implementation leak, duplicate AgentKit type, or SwiftData model outside Persistence. | | SwiftData schema, migration, or durable agent record | Persistence compatibility/replay fixtures | Old stores open, records retain intended values, interrupted timing remains recoverable, and deletes clean backing rows. | +| Assistant message-boundary edits or replacement provenance | Agent persistence/replay, conversation operations/controller, composer and mobile Assistant fixtures | Same-run prefix and graph survive, suffix backing is removed, staging adopts once, chronology remains stable, live/restore agree, and late retired writes cannot resurrect history. | | CloudKit format, upload scheduling, conflict or recovery behavior | Shared lifecycle, coordinator-safety and on-disk safety fixtures | Unsupported IDs never upload, compatible peers progress, restrictions survive retry/reopen, and failed reconciliation cannot unblock data. | | SSH credentials, prompts, host trust, or agent authentication | Authentication and interaction-policy fixtures | Ephemeral secrets stay ephemeral, prompt scope/order/coalescing is correct, forbidden work stays noninteractive, and secrets are invalidated after use. | | SSH/SFTP transfer scheduling or pane/session teardown | `SFTPTransferContextTests` with a controlled channel harness | Concurrency bounds hold, background transfer ownership survives UI teardown, cancellation cleans up, and fallback does not invite interaction. | From 947c0dc2abedbacba8ec8b8ae4c28190f802378a Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 11:59:11 +0800 Subject: [PATCH 09/30] Refine Assistant and action menus --- Frameworks/Resources/Localizable.xcstrings | 160 +++++++++--------- TFI/Features/Ports/MobileForwardingView.swift | 9 +- .../Snippets/MobileSnippetsView.swift | 24 +-- TFITests/MobileConnectionPickerTests.swift | 6 +- TFITests/MobileIconRenderingTests.swift | 6 +- TFM/App/AppModel.swift | 4 +- TFM/DesignSystem/MenuSymbol.swift | 3 +- TFM/Features/Assistant/AssistantView.swift | 82 ++++++--- .../HostChooser/HostChooserView.swift | 8 +- TFM/Features/Ports/PortForwardingView.swift | 16 +- TFM/Features/Sidebar/WindowSidebarView.swift | 4 +- TFM/Features/Snippets/SnippetsPageView.swift | 23 +-- TFM/Features/Workspaces/WindowModel.swift | 11 +- TFM/Features/Workspaces/WindowState.swift | 1 - TFMTests/AssistantConversationMenuTests.swift | 33 ++++ TFMTests/WindowStateTests.swift | 6 +- 16 files changed, 219 insertions(+), 177 deletions(-) diff --git a/Frameworks/Resources/Localizable.xcstrings b/Frameworks/Resources/Localizable.xcstrings index 000965e5..80616cfe 100644 --- a/Frameworks/Resources/Localizable.xcstrings +++ b/Frameworks/Resources/Localizable.xcstrings @@ -2630,6 +2630,22 @@ } } }, + "Address" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "地址" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "位址" + } + } + } + }, "Address unavailable" : { "extractionState" : "stale", "localizations" : { @@ -10530,86 +10546,6 @@ } } }, - "Execute a destructive script?" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "执行破坏性脚本?" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "執行破壞性指令碼?" - } - } - } - }, - "Execute destructive scripts?" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "执行破坏性脚本?" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "執行破壞性指令碼?" - } - } - } - }, - "Execute on %lld Hosts" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "执行%lld主机" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "執行%lld主機" - } - } - } - }, - "Execute on 1 Host" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "在1主机上执行" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "在1主機上執行" - } - } - } - }, - "Execute on Hosts" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "在主机上执行" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "在主機上執行" - } - } - } - }, "Exit code" : { "extractionState" : "stale", "localizations" : { @@ -21902,6 +21838,70 @@ } } }, + "Run a destructive script?" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行破坏性脚本?" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "執行破壞性指令碼?" + } + } + } + }, + "Run destructive scripts?" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行破坏性脚本?" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "執行破壞性指令碼?" + } + } + } + }, + "Run on %lld Hosts" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在%lld台主机上运行" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "在%lld部主機上執行" + } + } + } + }, + "Run on 1 Host" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在1台主机上运行" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "在1部主機上執行" + } + } + } + }, "Run the copied command in a terminal on this Mac. Enable the server before connecting." : { "localizations" : { "zh-Hans" : { diff --git a/TFI/Features/Ports/MobileForwardingView.swift b/TFI/Features/Ports/MobileForwardingView.swift index a7699ff6..5606f42c 100644 --- a/TFI/Features/Ports/MobileForwardingView.swift +++ b/TFI/Features/Ports/MobileForwardingView.swift @@ -84,11 +84,14 @@ struct MobileForwardingView: View { Button(runtime.isFailed ? "Retry" : "Start", systemImage: "play") { start(rule) }.disabled( !canStart(rule)) } + Button("Copy Address", systemImage: "doc.on.doc") { UIPasteboard.general.string = rule.bindAddress } + Button("Copy SSH Command", systemImage: "apple.terminal") { + UIPasteboard.general.string = app.forwardingLibrary.sshCommand(for: rule) + } + .disabled(app.forwardingLibrary.sshCommand(for: rule) == nil) + Divider() Button("Edit", systemImage: "pencil") { editing = rule }.disabled(runtime.isActive) Button("Duplicate", systemImage: "plus.square.on.square") { duplicate(rule) } - Button("Copy Address", systemImage: "doc.on.doc") { UIPasteboard.general.string = rule.bindAddress } - Button("Copy SSH Command") { UIPasteboard.general.string = app.forwardingLibrary.sshCommand(for: rule) } - .disabled(app.forwardingLibrary.sshCommand(for: rule) == nil) Divider() Button("Delete", systemImage: "trash", role: .destructive) { removing = rule } } diff --git a/TFI/Features/Snippets/MobileSnippetsView.swift b/TFI/Features/Snippets/MobileSnippetsView.swift index ddf36b2f..cbff024e 100644 --- a/TFI/Features/Snippets/MobileSnippetsView.swift +++ b/TFI/Features/Snippets/MobileSnippetsView.swift @@ -97,7 +97,6 @@ struct MobileSnippetsView: View { MobileSnippetDetailView( app: app, snippetID: id, execute: { requestExecution($0, overrideTargets: false) }, - executeOnHosts: { requestExecution($0, overrideTargets: true) }, edit: { editorRequest = SnippetEditorRequest(snippetID: id, packageID: nil) }, @@ -121,7 +120,7 @@ struct MobileSnippetsView: View { } .sheet(item: $targetRequest, onDismiss: completeTargetSelection) { request in MobileSnippetHostPicker( - app: app, selected: [], title: "Execute on Hosts", actionTitle: "Execute", + app: app, selected: [], title: "Run", actionTitle: "Run", requiresSelection: true ) { ids in pendingTargetExecution = ExecutionRequest(snippetID: request.snippetID, hostIDs: ids) @@ -153,7 +152,7 @@ struct MobileSnippetsView: View { Text(packageDeletionMessage) } .confirmationDialog( - "Execute a destructive script?", + "Run a destructive script?", isPresented: Binding( get: { destructiveRequest != nil }, set: { if !$0 { destructiveRequest = nil } }), @@ -289,14 +288,10 @@ struct MobileSnippetsView: View { } } } actions: { - Button("Execute", systemImage: "play.fill") { + Button("Run", systemImage: "play.fill") { requestExecution(snippet.id, overrideTargets: false) } .disabled(app.snippets.hosts.isEmpty) - Button("Execute on Hosts", systemImage: "play.rectangle") { - requestExecution(snippet.id, overrideTargets: true) - } - .disabled(app.snippets.hosts.isEmpty) Divider() Button("Edit", systemImage: "pencil") { editorRequest = SnippetEditorRequest(snippetID: snippet.id, packageID: nil) @@ -405,10 +400,10 @@ struct MobileSnippetsView: View { } private var destructiveExecuteTitle: String { - guard let request = destructiveRequest else { return String(localized: "Execute") } + guard let request = destructiveRequest else { return String(localized: "Run") } return request.hostIDs.count == 1 - ? String(localized: "Execute on 1 Host") - : String(localized: "Execute on \(request.hostIDs.count) Hosts") + ? String(localized: "Run on 1 Host") + : String(localized: "Run on \(request.hostIDs.count) Hosts") } private var destructiveMessage: String { @@ -436,7 +431,6 @@ struct MobileSnippetDetailView: View { let app: MobileAppModel let snippetID: Snippet.ID let execute: (Snippet.ID) -> Void - let executeOnHosts: (Snippet.ID) -> Void let edit: () -> Void let duplicate: (Snippet.ID) -> Void let delete: () -> Void @@ -459,7 +453,7 @@ struct MobileSnippetDetailView: View { Button { execute(snippet.id) } label: { - Label("Execute", systemImage: "play.fill") + Label("Run", systemImage: "play.fill") .frame(maxWidth: .infinity).padding(.vertical, 6) } .buttonStyle(.borderedProminent) @@ -477,10 +471,6 @@ struct MobileSnippetDetailView: View { } .mobileProminentAction() MobileToolbarOverflow { - Button("Execute on Hosts", systemImage: "play.rectangle") { - executeOnHosts(snippet.id) - } - .disabled(app.snippets.hosts.isEmpty) Button("Duplicate", systemImage: "plus.square.on.square") { duplicate(snippet.id) } diff --git a/TFITests/MobileConnectionPickerTests.swift b/TFITests/MobileConnectionPickerTests.swift index f60289d6..d0a340ba 100644 --- a/TFITests/MobileConnectionPickerTests.swift +++ b/TFITests/MobileConnectionPickerTests.swift @@ -197,12 +197,12 @@ struct MobileConnectionPickerTests { try await withStandaloneNavigation( MobileSnippetHostPicker( - app: app, selected: [], title: "Execute on Hosts", actionTitle: "Execute", + app: app, selected: [], title: "Run", actionTitle: "Run", requiresSelection: true, save: { _ in }), language: "en" ) { navigation in - let execute = try #require(toolbarButton(named: "Execute", in: navigation)) - #expect(!execute.isEnabled) + let run = try #require(toolbarButton(named: "Run", in: navigation)) + #expect(!run.isEnabled) } try await withStandaloneNavigation( diff --git a/TFITests/MobileIconRenderingTests.swift b/TFITests/MobileIconRenderingTests.swift index 2210f0aa..2a41c991 100644 --- a/TFITests/MobileIconRenderingTests.swift +++ b/TFITests/MobileIconRenderingTests.swift @@ -70,7 +70,7 @@ struct MobileIconRenderingTests { NavigationStack { MobileSnippetDetailView( app: snippetApp, snippetID: snippet.id, execute: { _ in }, - executeOnHosts: { _ in }, edit: {}, duplicate: { _ in }, delete: {}) + edit: {}, duplicate: { _ in }, delete: {}) } .environment(\.locale, locale) } @@ -87,8 +87,8 @@ struct MobileIconRenderingTests { } try await capture("snippet-execution-targets-\(suffix)", language: language, scheme: scheme) { MobileSnippetHostPicker( - app: snippetApp, selected: [], title: "Execute on Hosts", - actionTitle: "Execute", requiresSelection: true, save: { _ in } + app: snippetApp, selected: [], title: "Run", + actionTitle: "Run", requiresSelection: true, save: { _ in } ) .environment(\.locale, locale) } diff --git a/TFM/App/AppModel.swift b/TFM/App/AppModel.swift index 99a6cde3..04bdbd7c 100644 --- a/TFM/App/AppModel.swift +++ b/TFM/App/AppModel.swift @@ -554,7 +554,7 @@ final class AppModel { if window.sidebar.assistantConversationID == id { window.sidebar.assistantConversationID = nil if wasArchived { - window.sidebar.isAssistantArchivePresented = true + window.isAssistantArchivePresented = true } } } @@ -958,7 +958,7 @@ final class AppModel { } /// Turns Snippet execution into ordinary visible sessions rather than a - /// hidden job. `overrideHostIDs` powers Execute on Hosts; `fallbackHostIDs` + /// hidden job. `overrideHostIDs` powers explicit host overrides; `fallbackHostIDs` /// is used only by snippets whose saved target list is empty. func execute( _ snippets: [Snippet], overrideHostIDs: [Host.ID]? = nil, diff --git a/TFM/DesignSystem/MenuSymbol.swift b/TFM/DesignSystem/MenuSymbol.swift index 19240c9c..98fd2627 100644 --- a/TFM/DesignSystem/MenuSymbol.swift +++ b/TFM/DesignSystem/MenuSymbol.swift @@ -73,7 +73,6 @@ nonisolated enum MenuSymbol { static let insert = "text.insert" static let paste = "doc.on.clipboard" static let execute = "play" - static let executeOnHosts = "server.rack" static let start = "play" static let stop = "stop.fill" static let refresh = "arrow.clockwise" @@ -126,7 +125,7 @@ nonisolated enum MenuSymbol { newIdentity, generateKey, generateCommand, importItem, download, export, upload, details, pause, selectDevices, open, openFullScreen, edit, rename, resetName, duplicate, copy, insert, paste, - execute, executeOnHosts, start, stop, refresh, showHiddenFiles, hideHiddenFiles, editPath, + execute, start, stop, refresh, showHiddenFiles, hideHiddenFiles, editPath, editPermissions, test, pin, unpin, archive, unarchive, delete, workspace, nextWorkspace, previousWorkspace, currentWorkspace, newWorkspace, newWindow, moveToNewWindow, diff --git a/TFM/Features/Assistant/AssistantView.swift b/TFM/Features/Assistant/AssistantView.swift index 635d968d..2b449510 100644 --- a/TFM/Features/Assistant/AssistantView.swift +++ b/TFM/Features/Assistant/AssistantView.swift @@ -13,6 +13,43 @@ nonisolated enum AssistantSidebarEscapePolicy { } } +enum AssistantConversationMenuSurface { + case fullScreen + case sidebar + + /// New Chat and Show Archive are the Assistant's global verbs, so they stay + /// live with nothing under the pointer. AppKit asks for this after the click, + /// so the menu reads the matching surface's current state at that moment. + func blankAreaMenu( + in window: WindowModel, locale: Locale + ) -> [NativeMenuEntry] { + [ + NativeMenuEntry( + title: AppLocalization.string("New Chat", locale: locale), + symbol: MenuSymbol.newChat + ) { + window.openNewAssistantChat() + }, + NativeMenuEntry( + title: AppLocalization.string("Show Archive", locale: locale), + symbol: MenuSymbol.archive, + isOn: isArchivePresented(in: window) + ) { showArchive(in: window) }, + ] + } + + private func isArchivePresented(in window: WindowModel) -> Bool { + window.isAssistantArchivePresented + } + + private func showArchive(in window: WindowModel) { + switch self { + case .fullScreen: window.showAssistantArchive() + case .sidebar: window.showAssistantArchiveInSidebar() + } + } +} + /// Assistant's conversation navigator inside the window's shared sidebar: the /// chat list, over a bar holding the two things you do to the list itself. /// @@ -30,6 +67,7 @@ struct AssistantSidebarView: View { selectedID: window.isAssistantArchivePresented ? nil : window.assistantLibrary.selectedConversationID, query: window.assistantQuery, + menuSurface: .fullScreen, onOpen: { window.selectAssistant($0) } ) @@ -113,14 +151,14 @@ struct AssistantPanelView: View { ) .animation( Motion.panel(reduceMotion: reduceMotion), - value: window.sidebar.isAssistantArchivePresented + value: window.isAssistantArchivePresented ) .clipped() } @ViewBuilder private var workspaceLayer: some View { - if window.sidebar.isAssistantArchivePresented { + if window.isAssistantArchivePresented { AssistantArchiveView( isCompact: true, isInteractive: isFocusedOnWorkspace, @@ -231,6 +269,7 @@ struct AssistantPanelView: View { AssistantConversationList( selectedID: window.sidebar.assistantConversationID, isInteractive: isFocusedOnWorkspace, + menuSurface: .sidebar, onOpen: { window.openAssistantInSidebar($0) } ) .overlay { @@ -291,6 +330,7 @@ private struct AssistantConversationList: View { /// False for a copy the Assistant panel keeps mounted behind the one on /// screen. See `AssistantConversationListContainer.isInteractive`. var isInteractive: Bool = true + let menuSurface: AssistantConversationMenuSurface let onOpen: (UUID) -> Void var scope: Scope = .active var archiveMaxWidth: CGFloat? @@ -389,7 +429,7 @@ private struct AssistantConversationList: View { ) window.dragging = nil }, - blankAreaMenu: { Self.blankAreaMenu(in: window, locale: locale) }, + blankAreaMenu: { menuSurface.blankAreaMenu(in: window, locale: locale) }, isInteractive: isInteractive ) { item in renderedItem(item, window: window) @@ -406,7 +446,7 @@ private struct AssistantConversationList: View { window: window, topInset: Metrics.Space.m, bottomInset: Metrics.Space.m, - blankAreaMenu: { Self.blankAreaMenu(in: window, locale: locale) }, + blankAreaMenu: { menuSurface.blankAreaMenu(in: window, locale: locale) }, isInteractive: isInteractive ) { item in renderedItem(item, window: window) @@ -417,30 +457,6 @@ private struct AssistantConversationList: View { .background(Color.clear) } - /// New Chat and Show Archive are the Assistant's global verbs, so they stay - /// live with nothing under the pointer. That also covers the one place the - /// titlebar cannot: the chat list showing while Vault is the front content. - /// - /// Static, and given the window rather than reading it: AppKit asks for this - /// when the pointer is already down, long after the body that built the list. - private static func blankAreaMenu( - in window: WindowModel, locale: Locale - ) -> [NativeMenuEntry] { - [ - NativeMenuEntry( - title: AppLocalization.string("New Chat", locale: locale), - symbol: MenuSymbol.newChat - ) { - window.openNewAssistantChat() - }, - NativeMenuEntry( - title: AppLocalization.string("Show Archive", locale: locale), - symbol: MenuSymbol.archive, - isOn: window.isAssistantArchivePresented - ) { window.showAssistantArchive() }, - ] - } - private var activeItems: [AssistantConversationListItem] { guard !isSearching else { return window.assistantLibrary.search(query, scope: .active).map(item) @@ -1123,6 +1139,7 @@ private struct AssistantArchiveView: View { selectedID: nil, query: query, isInteractive: isInteractive, + menuSurface: isCompact ? .sidebar : .fullScreen, onOpen: onOpen, scope: .archived, archiveMaxWidth: isCompact ? nil : Metrics.Chat.contentMaxWidth @@ -1183,6 +1200,15 @@ private struct AssistantArchiveView: View { .padding(.vertical, 2) .background(.quaternary, in: .capsule) Spacer(minLength: 0) + if isCompact { + Button { + window.showAssistantArchive() + } label: { + Image(systemName: "arrow.up.left.and.arrow.down.right") + } + .buttonStyle(.borderless) + .help(Text("Open Full Screen")) + } } } } diff --git a/TFM/Features/HostChooser/HostChooserView.swift b/TFM/Features/HostChooser/HostChooserView.swift index 318ef1f4..b49b662e 100644 --- a/TFM/Features/HostChooser/HostChooserView.swift +++ b/TFM/Features/HostChooser/HostChooserView.swift @@ -53,7 +53,7 @@ nonisolated enum HostChooserPurpose: Hashable, Sendable, Identifiable { case .connect: "Connect" case .batchStartup: "Batch Startup" case .metrics: "Add Server" - case .snippet: "Execute on Hosts" + case .snippet: "Run" } } @@ -72,7 +72,7 @@ nonisolated enum HostChooserPurpose: Hashable, Sendable, Identifiable { switch self { case .connect, .batchStartup: "Connect" case .metrics: "Add Server" - case .snippet: "Execute" + case .snippet: "Run" } } @@ -237,13 +237,13 @@ struct HostChooserView: View { } } .confirmationDialog( - "Execute a destructive script?", + "Run a destructive script?", isPresented: $isConfirmingDestructiveSnippet, titleVisibility: .visible ) { Button( chooser.selected.count == 1 - ? "Execute on 1 Host" : "Execute on \(chooser.selected.count) Hosts", + ? "Run on 1 Host" : "Run on \(chooser.selected.count) Hosts", role: .destructive ) { performSnippetExecution() diff --git a/TFM/Features/Ports/PortForwardingView.swift b/TFM/Features/Ports/PortForwardingView.swift index 229f8dda..76b4b978 100644 --- a/TFM/Features/Ports/PortForwardingView.swift +++ b/TFM/Features/Ports/PortForwardingView.swift @@ -64,12 +64,20 @@ struct PortForwardingPageView: ManagedCardPage { systemImage: MenuSymbol.start ) { start(item) }.disabled(!canStart(item)) } + Menu("Copy", systemImage: MenuSymbol.copy) { + Button("Address", systemImage: MenuSymbol.copy) { + copyToPasteboard(item.bindAddress) + } + Button("SSH Command", systemImage: MenuSymbol.ssh) { + if let command = app.forwardings.sshCommand(for: item) { + copyToPasteboard(command) + } + } + .disabled(app.forwardings.sshCommand(for: item) == nil) + } + Divider() Button("Edit", systemImage: MenuSymbol.edit) { open(item) } Button("Duplicate", systemImage: MenuSymbol.duplicate) { duplicate(item) } - Button("Copy Address", systemImage: MenuSymbol.copy) { copyToPasteboard(item.bindAddress) } - Button("Copy SSH Command") { - if let command = app.forwardings.sshCommand(for: item) { copyToPasteboard(command) } - }.disabled(app.forwardings.sshCommand(for: item) == nil) } Divider() DestructiveMenuButton("Delete") { diff --git a/TFM/Features/Sidebar/WindowSidebarView.swift b/TFM/Features/Sidebar/WindowSidebarView.swift index 90727bce..c39c7e18 100644 --- a/TFM/Features/Sidebar/WindowSidebarView.swift +++ b/TFM/Features/Sidebar/WindowSidebarView.swift @@ -590,7 +590,7 @@ private struct SnippetsSidebarView: View { ) } .confirmationDialog( - "Execute a destructive script?", + "Run a destructive script?", isPresented: Binding( get: { destructiveSnippet != nil }, set: { if !$0 { destructiveSnippet = nil } } @@ -598,7 +598,7 @@ private struct SnippetsSidebarView: View { titleVisibility: .visible, presenting: destructiveSnippet ) { snippet in - Button("Execute", role: .destructive) { executeNow(snippet) } + Button("Run", role: .destructive) { executeNow(snippet) } Button("Cancel", role: .cancel) { destructiveSnippet = nil } } message: { snippet in let assessment = CommandRiskClassifier.assess(snippet.body) diff --git a/TFM/Features/Snippets/SnippetsPageView.swift b/TFM/Features/Snippets/SnippetsPageView.swift index 686c3101..a16b94d9 100644 --- a/TFM/Features/Snippets/SnippetsPageView.swift +++ b/TFM/Features/Snippets/SnippetsPageView.swift @@ -165,7 +165,6 @@ struct SnippetsPageView: ManagedCardPage { extendSelection: context.extendSelection, actionSnippets: context.actionItems, execute: requestExecution, - executeOnHosts: executeOnHosts, edit: { edit(snippet, action: context.select) }, duplicate: duplicate, delete: { @@ -327,18 +326,6 @@ struct SnippetsPageView: ManagedCardPage { app.execute(snippets, in: window) } - private func executeOnHosts(_ snippets: [Snippet]) { - guard !snippets.isEmpty else { return } - window.openVaultWork( - .snippetExecutionTargets( - SnippetHostChooserRequest( - snippetIDs: snippets.map(\.id), strategy: .overrideAllTargets - ) - ), - on: .snippets - ) - } - private func executionMessage(_ snippets: [Snippet]) -> String { snippets.map { snippet in let wanted = Set(snippet.targetHostIDs) @@ -407,7 +394,7 @@ struct SnippetsPagePresentation: ManagedCardPresentationModifier { actions.deleteSnippets(batch.snippets) } case .executeDestructive(let batch): - Button("Execute", role: .destructive) { + Button("Run", role: .destructive) { actions.performExecution(batch.snippets) } case nil: @@ -422,7 +409,7 @@ struct SnippetsPagePresentation: ManagedCardPresentationModifier { batch.snippets.count == 1 ? Text("Delete Snippet?") : Text("Delete \(batch.snippets.count) Snippets?") - case .executeDestructive: Text("Execute destructive scripts?") + case .executeDestructive: Text("Run destructive scripts?") case nil: Text("") } } @@ -454,7 +441,6 @@ private struct SnippetCard: View { let extendSelection: () -> Void let actionSnippets: [Snippet] let execute: ([Snippet]) -> Void - let executeOnHosts: ([Snippet]) -> Void let edit: () -> Void let duplicate: (Snippet) -> Void let delete: ([Snippet]) -> Void @@ -470,10 +456,7 @@ private struct SnippetCard: View { SnippetCardContent(snippet: snippet, allowsTextSelection: true) } .contextMenu { - Button("Execute", systemImage: MenuSymbol.execute) { execute(actionSnippets) } - Button("Execute on Hosts", systemImage: MenuSymbol.executeOnHosts) { - executeOnHosts(actionSnippets) - } + Button("Run", systemImage: MenuSymbol.execute) { execute(actionSnippets) } if actionSnippets.count == 1 { Divider() Button("Edit", systemImage: MenuSymbol.edit, action: edit) diff --git a/TFM/Features/Workspaces/WindowModel.swift b/TFM/Features/Workspaces/WindowModel.swift index 58177476..a25e742e 100644 --- a/TFM/Features/Workspaces/WindowModel.swift +++ b/TFM/Features/Workspaces/WindowModel.swift @@ -632,6 +632,7 @@ final class WindowModel { func showAssistantArchive() { isAssistantArchivePresented = true + sidebar.assistantConversationID = nil selectedDestination = .assistant } @@ -642,20 +643,20 @@ final class WindowModel { func openAssistantInSidebar(_ id: ConversationModel.ID) { guard assistantLibrary.conversation(id: id) != nil else { return } - sidebar.isAssistantArchivePresented = false + isAssistantArchivePresented = false sidebar.assistantConversationID = id refreshSessionContext() } func closeAssistantInSidebar() { - sidebar.isAssistantArchivePresented = false + isAssistantArchivePresented = false sidebar.assistantConversationID = nil refreshSessionContext() } func showAssistantArchiveInSidebar() { sidebar.assistantConversationID = nil - sidebar.isAssistantArchivePresented = true + isAssistantArchivePresented = true refreshSessionContext() } @@ -671,7 +672,7 @@ final class WindowModel { if wasArchived, wasSelected { isAssistantArchivePresented = true } if sidebar.assistantConversationID == id { sidebar.assistantConversationID = nil - if wasArchived { sidebar.isAssistantArchivePresented = true } + if wasArchived { isAssistantArchivePresented = true } } } @@ -733,7 +734,7 @@ final class WindowModel { } if sidebar.assistantConversationID == id { sidebar.assistantConversationID = nil - sidebar.isAssistantArchivePresented = false + isAssistantArchivePresented = false } } func forkAssistantConversation( diff --git a/TFM/Features/Workspaces/WindowState.swift b/TFM/Features/Workspaces/WindowState.swift index 4777a116..4d510b82 100644 --- a/TFM/Features/Workspaces/WindowState.swift +++ b/TFM/Features/Workspaces/WindowState.swift @@ -51,7 +51,6 @@ final class WindowSidebarState { /// top-level destination. `nil` is the history + composer landing screen. var assistantConversationID: ConversationModel.ID? var assistantDraft = ConversationModel() - var isAssistantArchivePresented = false /// What the focused pane is, for whichever composer this column is showing. /// diff --git a/TFMTests/AssistantConversationMenuTests.swift b/TFMTests/AssistantConversationMenuTests.swift index 3b185aa4..ae804d1e 100644 --- a/TFMTests/AssistantConversationMenuTests.swift +++ b/TFMTests/AssistantConversationMenuTests.swift @@ -7,6 +7,39 @@ import Testing @Suite @MainActor struct AssistantConversationMenuTests { + @Test + func archiveStateIsSharedWhileSidebarActionKeepsWorkspaceInFront() throws { + let host = Host(name: "Server", host: "server.example.invalid") + let workspace = Workspace.sshSession(host: host) + let window = WindowModel(workspaces: [workspace]) + + window.showAssistantArchive() + window.select(workspace.id) + + let fullScreenArchive = try #require( + AssistantConversationMenuSurface.fullScreen + .blankAreaMenu(in: window, locale: Locale(identifier: "en")) + .first { $0.symbol == MenuSymbol.archive } + ) + let sidebarArchive = try #require( + AssistantConversationMenuSurface.sidebar + .blankAreaMenu(in: window, locale: Locale(identifier: "en")) + .first { $0.symbol == MenuSymbol.archive } + ) + + #expect(fullScreenArchive.isOn) + #expect(sidebarArchive.isOn) + + window.closeAssistantInSidebar() + + #expect(!window.isAssistantArchivePresented) + + sidebarArchive.action() + + #expect(window.selectedDestination == .workspace(workspace.id)) + #expect(window.isAssistantArchivePresented) + } + @Test func deleteIsOnlyOfferedForArchivedConversations() { let conversation = ConversationModel() diff --git a/TFMTests/WindowStateTests.swift b/TFMTests/WindowStateTests.swift index f604ca72..6372ac83 100644 --- a/TFMTests/WindowStateTests.swift +++ b/TFMTests/WindowStateTests.swift @@ -202,7 +202,7 @@ struct WindowStateTests { #expect(window.selectedDestination == .assistant) #expect(window.assistantLibrary.selectedConversation == nil) #expect(window.sidebar.assistantConversationID == nil) - #expect(!window.sidebar.isAssistantArchivePresented) + #expect(!window.isAssistantArchivePresented) } @Test @@ -224,11 +224,11 @@ struct WindowStateTests { #expect(window.assistantLibrary.selectedConversation === conversation) window.showAssistantArchiveInSidebar() - #expect(window.sidebar.isAssistantArchivePresented) + #expect(window.isAssistantArchivePresented) #expect(window.sidebarAssistantConversation == nil) window.openAssistantInSidebar(conversation.id) - #expect(!window.sidebar.isAssistantArchivePresented) + #expect(!window.isAssistantArchivePresented) #expect(window.sidebarAssistantConversation === conversation) } From fddccff4ee92043ca9e53862a422a2e7726bdc54 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 12:19:32 +0800 Subject: [PATCH 10/30] Refine Sidebar glass and selection states --- TFM/Design/DesignTokens.swift | 19 ++-- TFM/Features/Assistant/AssistantView.swift | 103 ++++++++++--------- TFM/Features/Sidebar/WindowSidebarView.swift | 85 ++++++--------- TFM/UI/Components/SidebarRow.swift | 2 +- 4 files changed, 100 insertions(+), 109 deletions(-) diff --git a/TFM/Design/DesignTokens.swift b/TFM/Design/DesignTokens.swift index e448dad0..c671e737 100644 --- a/TFM/Design/DesignTokens.swift +++ b/TFM/Design/DesignTokens.swift @@ -481,9 +481,9 @@ struct LegacySidebarMaterialBackground: NSViewRepresentable { } } -/// A native-feeling emphasized selection: the key window uses an accent fill -/// with reversed content, while an inactive window keeps the same destination -/// legible with a quiet accent wash and accent-colored content. +/// A native selection: the key window uses the system selection color with +/// reversed content; an inactive window uses the system's neutral selection +/// background and unemphasized text color. /// /// The row's context menu is drawn here rather than by the caller, and it is a /// native menu rather than SwiftUI's: `contextMenu` derives its source rectangle @@ -554,9 +554,7 @@ struct TermindSidebarRowBackground: ViewModifier { private var fillColor: Color { if isSelected { - controlActiveState == .key - ? TermindColor.accent - : TermindColor.accent.opacity(0.14) + SidebarSelectionStyle.background(controlActiveState: controlActiveState) } else { Highlight.fill(isSelected: false, isHovering: isHovering) } @@ -564,6 +562,13 @@ struct TermindSidebarRowBackground: ViewModifier { } enum SidebarSelectionStyle { + static func background(controlActiveState: ControlActiveState) -> Color { + Color( + nsColor: controlActiveState == .key + ? .selectedContentBackgroundColor : .unemphasizedSelectedContentBackgroundColor + ) + } + static func foreground( isSelected: Bool, controlActiveState: ControlActiveState, @@ -572,7 +577,7 @@ enum SidebarSelectionStyle { guard isSelected else { return unselected } return controlActiveState == .key ? AnyShapeStyle(TermindColor.selectedForeground) - : AnyShapeStyle(.tint) + : AnyShapeStyle(Color(nsColor: .unemphasizedSelectedTextColor)) } } diff --git a/TFM/Features/Assistant/AssistantView.swift b/TFM/Features/Assistant/AssistantView.swift index 2b449510..9ef5f383 100644 --- a/TFM/Features/Assistant/AssistantView.swift +++ b/TFM/Features/Assistant/AssistantView.swift @@ -57,7 +57,7 @@ enum AssistantConversationMenuSurface { /// every mode of the column ends at the same line. Its two controls are shaped /// by what they are: New Chat is a capsule because it is a labelled action that /// takes the width, and Archived is a circle because it is one glyph that -/// toggles — the same distinction the mode rail at the top of the column makes. +/// toggles. Both share neutral glass, including the archive's selected state. struct AssistantSidebarView: View { @Environment(WindowModel.self) private var window @@ -71,54 +71,63 @@ struct AssistantSidebarView: View { onOpen: { window.selectAssistant($0) } ) - HStack(spacing: Metrics.Space.s) { - Button { - window.openNewAssistantChat() - } label: { - Label("New Chat", systemImage: "plus.bubble") - .frame( - maxWidth: .infinity, - maxHeight: .infinity, - alignment: .center - ) - .contentShape(.capsule) - } - .buttonStyle(.plain) - .termindGlassCapsule(isInteractive: true) + footer + .frame(height: Metrics.Sidebar.searchFieldHeight) + .padding(.horizontal, Metrics.Space.m) + .padding(.vertical, Metrics.Space.s) + } + } - Button { - window.showAssistantArchive() - } label: { - Image(systemName: "archivebox") - // Filled and reversed while it is the destination on - // screen, the same way a selected row states itself. - // An accent-on-glass glyph reads as "tinted", not as - // "this is where you are". - .foregroundStyle( - window.isAssistantArchivePresented - ? AnyShapeStyle(TermindColor.selectedForeground) - : AnyShapeStyle(.primary) - ) - .frame( - width: Metrics.Sidebar.searchFieldHeight, - height: Metrics.Sidebar.searchFieldHeight - ) - .background { - if window.isAssistantArchivePresented { - Circle().fill(Color.accentColor) - } - } - .contentShape(.circle) - } - .buttonStyle(.plain) - .termindGlassCircle(isInteractive: true) - .help(Text("Archived")) - .accessibilityLabel(Text("Archived")) - .accessibilityAddTraits(window.isAssistantArchivePresented ? .isSelected : []) + @ViewBuilder + private var footer: some View { + if #available(macOS 26.0, *) { + GlassEffectContainer(spacing: Metrics.Space.s) { + footerButtons } - .frame(height: Metrics.Sidebar.searchFieldHeight) - .padding(.horizontal, Metrics.Space.m) - .padding(.vertical, Metrics.Space.s) + } else { + footerButtons + } + } + + private var footerButtons: some View { + HStack(spacing: Metrics.Space.s) { + Button { + window.openNewAssistantChat() + } label: { + Label("New Chat", systemImage: "plus.bubble") + .foregroundStyle(.primary) + .frame( + maxWidth: .infinity, + maxHeight: .infinity, + alignment: .center + ) + .contentShape(.capsule) + } + .buttonStyle(.plain) + .termindGlassCapsule(isInteractive: true) + + Button { + window.showAssistantArchive() + } label: { + Image(systemName: "archivebox") + .foregroundStyle(.primary) + .fontWeight(window.isAssistantArchivePresented ? .semibold : .regular) + .frame( + width: Metrics.Sidebar.searchFieldHeight, + height: Metrics.Sidebar.searchFieldHeight + ) + .background { + if window.isAssistantArchivePresented { + Circle().fill(Color.primary.opacity(0.10)) + } + } + .contentShape(.circle) + } + .buttonStyle(.plain) + .termindGlassCircle(isInteractive: true) + .help(Text("Archived")) + .accessibilityLabel(Text("Archived")) + .accessibilityAddTraits(window.isAssistantArchivePresented ? .isSelected : []) } } } diff --git a/TFM/Features/Sidebar/WindowSidebarView.swift b/TFM/Features/Sidebar/WindowSidebarView.swift index c39c7e18..d436a9c5 100644 --- a/TFM/Features/Sidebar/WindowSidebarView.swift +++ b/TFM/Features/Sidebar/WindowSidebarView.swift @@ -7,13 +7,15 @@ import TermindPresentation import struct TermindDomain.Host -/// The window's one left-hand column. A glass mode rail switches the Navigator -/// and three Workspace tools in place without the square selection segments of -/// `NSSegmentedControl`. +/// The window's one left-hand column. Its mode buttons share one glass capsule +/// with a neutral selection highlight, keeping the symbols above the material. struct WindowSidebarView: View { @Environment(WindowModel.self) private var window @Environment(\.accessibilityReduceMotion) private var reduceMotion + @Environment(\.controlActiveState) private var controlActiveState + @FocusState private var focusedMode: WindowSidebarState.Mode? + @Namespace private var modeSelectionNamespace var body: some View { VStack(spacing: 0) { @@ -33,44 +35,20 @@ struct WindowSidebarView: View { private var focusedPaneID: Pane.ID? { window.selectedWorkspace?.focusedPaneID } - @ViewBuilder private var modeSelector: some View { - Group { - if #available(macOS 26.0, *) { - GlassEffectContainer(spacing: Metrics.Space.xs) { - modeButtons(usesSystemGlass: true) - } - .padding(Metrics.Space.xs) - .glassEffect(.regular, in: .capsule) - } else { - modeButtons(usesSystemGlass: false) - .padding(Metrics.Space.xs) - .termindGlassCapsule() + HStack(spacing: Metrics.Space.xs) { + ForEach(WindowSidebarState.Mode.allCases) { mode in + modeButton(mode) } } + .padding(Metrics.Space.xs) + .termindGlassCapsule() .animation(reduceMotion ? nil : Motion.panel, value: window.sidebarMode) .accessibilityElement(children: .contain) .accessibilityLabel(Text("Sidebar")) } - private func modeButtons(usesSystemGlass: Bool) -> some View { - HStack(spacing: Metrics.Space.xs) { - ForEach(Array(WindowSidebarState.Mode.allCases.enumerated()), id: \.element.id) { - index, mode in - if index > 0 { - Divider() - .frame(height: 18) - .opacity(0.55) - } - modeButton(mode, usesSystemGlass: usesSystemGlass) - } - } - } - - private func modeButton( - _ mode: WindowSidebarState.Mode, - usesSystemGlass: Bool - ) -> some View { + private func modeButton(_ mode: WindowSidebarState.Mode) -> some View { let isSelected = window.sidebarMode == mode let isEnabled = window.isSidebarModeEnabled(mode) @@ -78,23 +56,27 @@ struct WindowSidebarView: View { guard isEnabled else { return } window.activateSidebarMode(mode) } label: { - Group { - if #available(macOS 26.0, *), usesSystemGlass, isSelected { - modeIcon(mode, isSelected: true, isEnabled: isEnabled) - .background(Circle().fill(Color.accentColor)) - .glassEffect(.clear.interactive(), in: .circle) - } else { - modeIcon(mode, isSelected: isSelected, isEnabled: isEnabled) - .background { - if isSelected { - Circle().fill(Color.accentColor) - } - } + modeIcon(mode, isSelected: isSelected, isEnabled: isEnabled) + .frame(maxWidth: .infinity) + .background { + if isSelected { + Capsule() + .fill(Color.primary.opacity(0.10)) + .matchedGeometryEffect(id: "selection", in: modeSelectionNamespace) + } } - } - .frame(maxWidth: .infinity) + .contentShape(.capsule) } .buttonStyle(.plain) + .focused($focusedMode, equals: mode) + .focusEffectDisabled() + .overlay { + if focusedMode == mode && controlActiveState == .key { + Capsule() + .strokeBorder(Color.primary.opacity(0.35), lineWidth: 2) + .allowsHitTesting(false) + } + } .disabled(!isEnabled) .help(Text(mode.label)) .accessibilityLabel(Text(mode.label)) @@ -107,14 +89,9 @@ struct WindowSidebarView: View { isEnabled: Bool ) -> some View { Image(systemName: mode.symbol) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle( - isSelected - ? AnyShapeStyle(TermindColor.selectedForeground) - : isEnabled ? AnyShapeStyle(.primary) : AnyShapeStyle(.tertiary) - ) + .font(.system(size: 15, weight: isSelected ? .semibold : .medium)) + .foregroundStyle(isEnabled ? AnyShapeStyle(.primary) : AnyShapeStyle(.tertiary)) .frame(width: 36, height: 36) - .contentShape(.circle) } @ViewBuilder diff --git a/TFM/UI/Components/SidebarRow.swift b/TFM/UI/Components/SidebarRow.swift index 273b05b6..3edb142a 100644 --- a/TFM/UI/Components/SidebarRow.swift +++ b/TFM/UI/Components/SidebarRow.swift @@ -65,7 +65,7 @@ struct SidebarInlineRenameField: NSViewRepresentable { guard isSelected else { return .labelColor } return controlActiveState == .key ? .alternateSelectedControlTextColor - : .controlAccentColor + : .unemphasizedSelectedTextColor } private var placeholderColor: NSColor { From 11f8e4f8670313633d13aa5c047c440f15b8cd64 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 12:35:35 +0800 Subject: [PATCH 11/30] Match Sidebar selection colors to Finder --- TFM/Design/DesignTokens.swift | 28 +++++++++++-------- TFM/Features/Assistant/AssistantView.swift | 10 +++---- TFM/Features/Sidebar/WindowSidebarView.swift | 8 ++---- .../Workspaces/WorkspaceNavigatorView.swift | 5 ++-- TFM/UI/Components/SidebarRow.swift | 10 ++----- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/TFM/Design/DesignTokens.swift b/TFM/Design/DesignTokens.swift index c671e737..10f74b56 100644 --- a/TFM/Design/DesignTokens.swift +++ b/TFM/Design/DesignTokens.swift @@ -481,9 +481,8 @@ struct LegacySidebarMaterialBackground: NSViewRepresentable { } } -/// A native selection: the key window uses the system selection color with -/// reversed content; an inactive window uses the system's neutral selection -/// background and unemphasized text color. +/// A quiet, neutral selection matching the sidebar's glass controls. Both +/// active and inactive windows keep readable foreground content on a gray fill. /// /// The row's context menu is drawn here rather than by the caller, and it is a /// native menu rather than SwiftUI's: `contextMenu` derives its source rectangle @@ -563,10 +562,7 @@ struct TermindSidebarRowBackground: ViewModifier { enum SidebarSelectionStyle { static func background(controlActiveState: ControlActiveState) -> Color { - Color( - nsColor: controlActiveState == .key - ? .selectedContentBackgroundColor : .unemphasizedSelectedContentBackgroundColor - ) + Color.primary.opacity(controlActiveState == .key ? 0.10 : 0.06) } static func foreground( @@ -574,10 +570,20 @@ enum SidebarSelectionStyle { controlActiveState: ControlActiveState, unselected: AnyShapeStyle ) -> AnyShapeStyle { - guard isSelected else { return unselected } - return controlActiveState == .key - ? AnyShapeStyle(TermindColor.selectedForeground) - : AnyShapeStyle(Color(nsColor: .unemphasizedSelectedTextColor)) + guard controlActiveState == .key else { return AnyShapeStyle(.secondary) } + return isSelected ? AnyShapeStyle(.primary) : unselected + } + + static func iconForeground( + isSelected: Bool, + controlActiveState: ControlActiveState + ) -> AnyShapeStyle { + if controlActiveState == .key { return AnyShapeStyle(TermindColor.accent) } + return foreground( + isSelected: isSelected, + controlActiveState: controlActiveState, + unselected: AnyShapeStyle(.primary) + ) } } diff --git a/TFM/Features/Assistant/AssistantView.swift b/TFM/Features/Assistant/AssistantView.swift index 9ef5f383..d28a4e6b 100644 --- a/TFM/Features/Assistant/AssistantView.swift +++ b/TFM/Features/Assistant/AssistantView.swift @@ -1358,10 +1358,9 @@ struct AssistantConversationRow: View { HStack(spacing: Metrics.Space.s) { Image(systemName: conversation.isArchived ? "archivebox" : "sparkles") .foregroundStyle( - SidebarSelectionStyle.foreground( + SidebarSelectionStyle.iconForeground( isSelected: isSelected, - controlActiveState: controlActiveState, - unselected: AnyShapeStyle(.secondary) + controlActiveState: controlActiveState )) if isRenaming { SidebarInlineRenameField( @@ -1385,9 +1384,8 @@ struct AssistantConversationRow: View { } label: { Image(systemName: conversation.isArchived ? "arrow.uturn.backward" : "archivebox") .font(.caption) - // The row's own foreground, so on a selected card in a - // key window this reads white with the title beside it - // rather than as an accent-coloured glyph on accent. + // Keep this action legible with the row's text in both + // active and inactive windows. .foregroundStyle( SidebarSelectionStyle.foreground( isSelected: isSelected, diff --git a/TFM/Features/Sidebar/WindowSidebarView.swift b/TFM/Features/Sidebar/WindowSidebarView.swift index d436a9c5..ae2320e2 100644 --- a/TFM/Features/Sidebar/WindowSidebarView.swift +++ b/TFM/Features/Sidebar/WindowSidebarView.swift @@ -705,7 +705,6 @@ private struct SnippetsSidebarView: View { /// the selection highlight goes. private struct SidebarCard: View { let symbol: String? - var symbolTint: AnyShapeStyle? let title: String var titleIsMonospaced = false var titleLineLimit: Int? @@ -722,10 +721,9 @@ private struct SidebarCard: View { if let symbol { Image(systemName: symbol) .foregroundStyle( - SidebarSelectionStyle.foreground( + SidebarSelectionStyle.iconForeground( isSelected: isSelected, - controlActiveState: controlActiveState, - unselected: symbolTint ?? AnyShapeStyle(.secondary) + controlActiveState: controlActiveState ) ) .frame(width: Metrics.Sidebar.rowIcon) @@ -1144,8 +1142,6 @@ private struct SFTPBrowseHistorySidebar: View { private func row(_ entry: SFTPHistoryEntry, displayedPath: String) -> some View { SidebarCard( symbol: entry.kind.symbol, - symbolTint: entry.kind == .directory - ? AnyShapeStyle(.tint) : AnyShapeStyle(.secondary), title: entry.name, subtitle: displayedPath, truncatesSubtitleInMiddle: true, diff --git a/TFM/Features/Workspaces/WorkspaceNavigatorView.swift b/TFM/Features/Workspaces/WorkspaceNavigatorView.swift index c90ae731..437ad7dd 100644 --- a/TFM/Features/Workspaces/WorkspaceNavigatorView.swift +++ b/TFM/Features/Workspaces/WorkspaceNavigatorView.swift @@ -400,10 +400,9 @@ private struct WorkspaceNavigatorRow: View { HStack(spacing: Metrics.Space.s) { Image(systemName: workspace.symbol) .foregroundStyle( - SidebarSelectionStyle.foreground( + SidebarSelectionStyle.iconForeground( isSelected: isSelected, - controlActiveState: controlActiveState, - unselected: AnyShapeStyle(.secondary) + controlActiveState: controlActiveState ) ) .frame(width: Metrics.Sidebar.rowIcon) diff --git a/TFM/UI/Components/SidebarRow.swift b/TFM/UI/Components/SidebarRow.swift index 3edb142a..4b45fb16 100644 --- a/TFM/UI/Components/SidebarRow.swift +++ b/TFM/UI/Components/SidebarRow.swift @@ -62,10 +62,7 @@ struct SidebarInlineRenameField: NSViewRepresentable { } private var textColor: NSColor { - guard isSelected else { return .labelColor } - return controlActiveState == .key - ? .alternateSelectedControlTextColor - : .unemphasizedSelectedTextColor + controlActiveState == .key ? .labelColor : .secondaryLabelColor } private var placeholderColor: NSColor { @@ -237,10 +234,9 @@ struct SidebarNavigatorRow: View { HStack(spacing: Metrics.Space.s) { Image(systemName: symbol) .foregroundStyle( - SidebarSelectionStyle.foreground( + SidebarSelectionStyle.iconForeground( isSelected: isSelected, - controlActiveState: controlActiveState, - unselected: AnyShapeStyle(.secondary) + controlActiveState: controlActiveState ) ) .frame(width: Metrics.Sidebar.rowIcon) From 7a3ecbe555c3a120e759539a0b63805c1c4168cd Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 13:05:17 +0800 Subject: [PATCH 12/30] Use the native Sidebar background material --- TFM/App/RootView.swift | 24 ++++++++++++++---------- TFM/Design/DesignTokens.swift | 18 ------------------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/TFM/App/RootView.swift b/TFM/App/RootView.swift index b9adeb64..cde4ead8 100644 --- a/TFM/App/RootView.swift +++ b/TFM/App/RootView.swift @@ -31,16 +31,20 @@ struct RootView: View { @Bindable var presentations = window.presentations NavigationSplitView(columnVisibility: sidebarVisibility) { - ZStack { - SidebarGlassBackground() - .ignoresSafeArea() - WindowSidebarView() - } - .navigationSplitViewColumnWidth( - min: Metrics.Sidebar.min, - ideal: min(Metrics.Sidebar.ideal, sidebarMaxWidth), - max: sidebarMaxWidth - ) + WindowSidebarView() + .background { + // Modern split views supply the system sidebar material. + // An additional glass surface changes its tint and contrast. + if #unavailable(macOS 26.0) { + LegacySidebarMaterialBackground() + .ignoresSafeArea() + } + } + .navigationSplitViewColumnWidth( + min: Metrics.Sidebar.min, + ideal: min(Metrics.Sidebar.ideal, sidebarMaxWidth), + max: sidebarMaxWidth + ) } detail: { detail // Floating over the pane rather than docked under it, so switching diff --git a/TFM/Design/DesignTokens.swift b/TFM/Design/DesignTokens.swift index 10f74b56..7312e560 100644 --- a/TFM/Design/DesignTokens.swift +++ b/TFM/Design/DesignTokens.swift @@ -444,24 +444,6 @@ private struct TermindGlassBar: ViewModifier { } } -/// The Liquid Glass surface shared by every mode of the left column. -/// -/// `NavigationSplitView` supplies the column geometry, but the custom AppKit -/// lists inside it do not inherit SwiftUI's source-list background. Hosting one -/// system glass surface behind the complete column keeps list rows and empty -/// space on one coherent material, including behind the window titlebar. -struct SidebarGlassBackground: View { - @ViewBuilder - var body: some View { - if #available(macOS 26.0, *) { - Color.clear - .glassEffect(.regular, in: .rect) - } else { - LegacySidebarMaterialBackground() - } - } -} - /// Native material fallback for systems before Liquid Glass. struct LegacySidebarMaterialBackground: NSViewRepresentable { func makeNSView(context: Context) -> NSVisualEffectView { From 00e9d776ad4edca90f17e89eb6a77ab2323cc132 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 13:23:20 +0800 Subject: [PATCH 13/30] Document Sidebar and Assistant presentation behavior --- openwiki/.claims/architecture/desktop-ui.json | 52 +++++++++++- .../architecture/engineering-invariants.json | 8 +- openwiki/.claims/architecture/module-map.json | 6 +- .../architecture/native-app-directories.json | 8 +- .../architecture/platform-runtime.json | 12 +-- .../concepts/ui-design-principles.json | 78 ++++++++++++++--- openwiki/.claims/quickstart.json | 6 +- .../behavioral-boundaries-and-fixtures.json | 6 +- .../workflows/ssh-session-lifecycle.json | 6 +- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 84 +++++++++---------- openwiki/architecture/desktop-ui.md | 13 ++- .../architecture/engineering-invariants.md | 2 +- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 2 +- openwiki/concepts/ui-design-principles.md | 28 ++++++- openwiki/quickstart.md | 2 +- .../behavioral-boundaries-and-fixtures.md | 2 +- openwiki/workflows/ssh-session-lifecycle.md | 2 +- 20 files changed, 228 insertions(+), 97 deletions(-) diff --git a/openwiki/.claims/architecture/desktop-ui.json b/openwiki/.claims/architecture/desktop-ui.json index 5a793119..1bbdfeaf 100644 --- a/openwiki/.claims/architecture/desktop-ui.json +++ b/openwiki/.claims/architecture/desktop-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:aacd9e53709881d4edac532fd23dfffe4e8ba8cf7a5b611a79bfd846f1128d8a", + "pageVersion": "sha256:a6cde418a9982f9a4a6cd86e4848c36ef88b6ce715d11715d06b6a50f9fef8d7", "claims": [ { "id": "claim_5a53c5a47b7445f7a7b6f8865297a805", @@ -22,7 +22,7 @@ "evidence": [ { "resource": "repo://TFM/App/RootView.swift#L33-L113", - "version": "repo-lines-v1:sha256:8913b022efb5a07ab706ebe6a1a7b1525c4450e3b9d74bd547e9963252c1afb9:eyJzZWxlY3RlZExpbmVDb3VudCI6ODEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMDhhOGI4NmMyMTkzOTMzMWNlMmRlNzQ4Y2MwMjQyODI5NmExNWUyOWUwMTJmOWI1YjI5YmE3NTQ5MDViOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1YjBiYjljODYyYTczYjRiNDU5MzM0ZDE4Y2UxZjFjNzk0YTQ1ZmZiMTY1YzY2MGEwOWQ1NDgwZjg1OWJiZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5OGYxYTA0YmVlNzFjM2NmYzJiNDcxOWU2ZmQ5MGM5NmY0N2E5MWU0ZDJmZGU3ODgzNTNjMDM3MDQ2NmY5ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmZGVlN2NkMWQ1ZDFjYzE2Njc3Njg5ZTc2MzcwODEyYjUxMzEyZThhMTc0YWQ0YTE1OTM0NzRjODk1ZWFmNTgifQ" + "version": "repo-lines-v1:sha256:c29952042b18ca10c3b5dcf7986b3388c2346e458f84dcf733b7ce0cc38ad708:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMDhhOGI4NmMyMTkzOTMzMWNlMmRlNzQ4Y2MwMjQyODI5NmExNWUyOWUwMTJmOWI1YjI5YmE3NTQ5MDViOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1YjBiYjljODYyYTczYjRiNDU5MzM0ZDE4Y2UxZjFjNzk0YTQ1ZmZiMTY1YzY2MGEwOWQ1NDgwZjg1OWJiZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5OGYxYTA0YmVlNzFjM2NmYzJiNDcxOWU2ZmQ5MGM5NmY0N2E5MWU0ZDJmZGU3ODgzNTNjMDM3MDQ2NmY5ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmZGVlN2NkMWQ1ZDFjYzE2Njc3Njg5ZTc2MzcwODEyYjUxMzEyZThhMTc0YWQ0YTE1OTM0NzRjODk1ZWFmNTgifQ" }, { "resource": "repo://TFM/App/RootView.swift#L338-L386", @@ -36,7 +36,7 @@ "evidence": [ { "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L10-L134", - "version": "repo-lines-v1:sha256:22601aaff9d328068b1fdf3e19c6d087be1345821e56a2dcfd89439f6a14e53b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkNjU2ODhjMWU1N2RkOGFlY2U1MDk2ZWUzNGVjNTQ1YTc2MTFkYjFkYmNmYWVmMDAwNzI2ZGFjOWU1M2YwNjdiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJjZGEzMTJmZTE1NTcwNDM5MzM0YmM5MDBkYjQ0Mjk1MzYyZjM4OWY1YzZlZDhlNDE1YjgyMmMwZjdmMDQyYzQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNzY2YWJkZGRkYWYzY2JmODc1MTU4MGY1Mjk2NDNhNDE3M2M5YWE0NzQ2MDYyNjhkOThjZDNhYWQ1YjQ2Zjc1In0" + "version": "repo-lines-v1:sha256:a80b64c725fea3488f4035cb51832f0484d31c2d86c662e6070075493d05a428:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2MmYyMTRhYWM2NzNhYTY3NjdiZDhlZGM4MDI2ZmM1YWY4OGIzODExYTc1MDM1MGZlNGNmYWY5ZmMwZTBiNzMyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJjZGEzMTJmZTE1NTcwNDM5MzM0YmM5MDBkYjQ0Mjk1MzYyZjM4OWY1YzZlZDhlNDE1YjgyMmMwZjdmMDQyYzQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyNzY2YWJkZGRkYWYzY2JmODc1MTU4MGY1Mjk2NDNhNDE3M2M5YWE0NzQ2MDYyNjhkOThjZDNhYWQ1YjQ2Zjc1In0" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift#L15-L112", @@ -203,10 +203,54 @@ "version": "repo-lines-v1:sha256:79ce25ce82f72b1ae985609690b6b009f1cf6b22de7e0e4586239fcd44763b14:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZiZjJmOTUzMTg2NDUzNDdiNjBmZWY5NDg5Y2E5MmEwNWJkNTcwYWNjYjc1ZWI4YTNkMTY2YzRhNDM3ODhmNzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5NTg0Njc2ZWZjMGU1YjI4MWYyNzViYTgwNjYwMzY0ZTRlMTI1MjkzOGZjMGRiMjZiMzVhNjE4MDgzMjM4ZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ3Njk2MzE4ODhkZGU0OWZlYWY5MmZhNzQ2MTQwMzhkMmMzOGU4MmU2NTEzMWM1Y2Y5ZjNiMjdjNmI1YTU2ZWMifQ" } ] + }, + { + "id": "claim_0452a298a2a44d8f923c416a9c425632", + "statement": "On macOS 26+ the native NavigationSplitView owns the Sidebar background with no extra glass overlay; older systems use an AppKit visual-effect fallback. The mode rail uses one glass capsule with a neutral moving highlight, selected accessibility traits, a key-window focus outline and Reduce Motion-aware animation.", + "evidence": [ + { + "resource": "repo://TFM/App/RootView.swift#L33-L47", + "version": "repo-lines-v1:sha256:7477fccf1fdb5be27730274cc5816b615e7cf9555a753112c0c99bd77fda4bc7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMDhhOGI4NmMyMTkzOTMzMWNlMmRlNzQ4Y2MwMjQyODI5NmExNWUyOWUwMTJmOWI1YjI5YmE3NTQ5MDViOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxMDM0ODFjNTdkMGYwZGMwZjc2Y2QyY2M4YjM2MDRjNGU4Mjk2ZGE0MTIyYWRkMTllODE3NDVkYzdlMzMxYTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5OGYxYTA0YmVlNzFjM2NmYzJiNDcxOWU2ZmQ5MGM5NmY0N2E5MWU0ZDJmZGU3ODgzNTNjMDM3MDQ2NmY5ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUxMmQ1NmZkOTU2YzBiNzRhYjBlMDdlZDU5ZjJlM2QwY2NkNTUwNDhkNmQyMGIwZTU5ZGQ4ODVkMGU4NjA4MzQifQ" + }, + { + "resource": "repo://TFM/Design/DesignTokens.swift#L447-L463", + "version": "repo-lines-v1:sha256:279c40cc0192345ce03939e3f215bf70c3dbfffa7b28f80fe9c31b3ce766b042:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwN2M1NjJkZTJiYWE4ZmNiNDJlYjk5ZTI0NjcyZWJiOTAyNWZiZjI3MWU5MGJmMTI1NWFkYWJiZjQwYjcyNDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmODMyZTZhMzJkOTBhNDc5MzNhYjEzZWE4MThjNWNkYjg1ZjlhY2ViMzYxODhhOWJhNzE1MGJkNzc4NDlkYzUifQ" + }, + { + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L38-L94", + "version": "repo-lines-v1:sha256:cfbd8237ba2ca4f4797b4bba5216aced08897e568ba2875816a470bcd4ae3da3:eyJzZWxlY3RlZExpbmVDb3VudCI6NTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjNzM1YTQ1NTZjMmZkNjgzMmVlZTgyY2Q4Y2MyYzM5MzA0YTAxNjdmNWZkMTVlNTgwYWI1MzRiNzQxMmUyZDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNThhYjBjNDdiN2Y5OWVkOGM1OGM1Yjg2Njc2NWU3MDQ4YTZlZWI2YmJhODExYzRmNzcxMDk1ODZlMzQ2MWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwYmUyNzE1MDFmNzVhZWZlNjgxNzFmMTU5MjZlMGUzOWFkYjM4NmQyZjMxNjU2YzM0N2UzOGJlNDhiMDY4YTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiODlkNTljMjE3MGEwZWFmOWY0NDc2YmVjMjRjOThlYjdlYTRiMWNlYTY4ZDQ5MmQ2OGNkNzAzOGZkNDE0YmQifQ" + } + ] + }, + { + "id": "claim_b0456a62ec814a4597959476eb560bef", + "statement": "Assistant full-screen and compact surfaces share one window-local Archive flag while retaining separate conversation selections; native blank-area menu checkmarks read that flag, but sidebar Show Archive preserves the foreground workspace and full-screen Show Archive changes destination. Compact open/close clears Archive, and the compact archive can explicitly open full screen.", + "evidence": [ + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L1212-L1220", + "version": "repo-lines-v1:sha256:0349ae49bfc8fdec17e0efc8f55785ca048e0292ef691b180df44958e76afe89:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2Y1YTNhN2U1ZmQ4NTUxZTgxYWQ0ZGE0YThmM2RlMzM0MzQwOWYyN2VlYTE4OWNjNTdkZjY2ZTYzODM2OWFhNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmQ5YjI3NDRlMTc0MzY5OWFiZWRiYjc1MGQ0ZGYwNTMzNjA4Y2FjNGIwZjA5OWEzNTYyZDdjMTg3ZTg5NWE4ZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjRhMzY1YTkwZWMxMWU5MDQzMjc1MmNmOTM3NmVjNWVkMzZiODBiYTVlOGU2ODU0ZmNmOTg5NzBkMzA0NDQ2OSJ9" + }, + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L16-L51", + "version": "repo-lines-v1:sha256:544e8c7302b1637b1bca13ec85eaac8a0cada814c0062dde16d23324f9ecc4bd:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjNDM5YTE4M2ZlMTJlOGM1N2I4NjFjY2FiM2E0ZWFlMzM3NDQ5OTU5NjYxODcyZjEzZGJjMzgzMjEyMGVlZWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFjMDA0ZjM3ZDg2ZDkxY2M1MmRmNWQ4YmIwMmI1NGJiNTg5OWIwNTFmM2NjNGE1OTg5NmQxYjU2Y2I0NTRjYjIifQ" + }, + { + "resource": "repo://TFM/Features/Workspaces/WindowModel.swift#L633-L660", + "version": "repo-lines-v1:sha256:c05d9d7cd2c895e83e4ba078c2135b4b99ab87fa9df4b98e0465c0f6ef17310e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhkODg0YjQzZmM5ZTY4YTA4M2IyMmY2MTY3MmZiMmIzZjY2ZTcwMTcxYmQ4N2UwYTU4NjAwZWNmZGQzY2EyYmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzNTFjNDQ4YzUzOTEzYWIyMDVkZGQwZDFmODVkNGFjM2NmOTg0NjBiODdiZDYwMTgzZmUwNGM0NTEwM2Y5MGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBiYjI4M2MwMDEyYzI5MzFjNDk4NDZhMTEwNDVjZGFkNWE1YzUzZGVhNDdiODE1ZTFmN2Q4MTU4Zjk4ZTJlZGIifQ" + }, + { + "resource": "repo://TFM/Features/Workspaces/WindowModel.swift#L91-L98", + "version": "repo-lines-v1:sha256:95c6fd8182584ff7be20043a661d8cdebaa3dd3439eb9185ed127041261bc575:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNmU2NWIyNTU5ZWZmOTJmMGEzYTA2MTgzNThiYTIzMTVjZjU5OGNkZDU5MTU2NThmM2I4ZDBjOTYyOTU1NzZkYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWE2OGZlNWRiMGUzYTU4YWE0ZmE4ZjlhMDY2OWRhNGMxM2UzM2U4YjA1NmM5YjIyNjg2N2Y5ZTY2YmM5MDUyMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjc0YjcwNzBlM2FjOGQ3OThhOWJiNDcyM2FlOWQzZDcyNjYyMjllNDkxY2IwNTRlOTJlNjc3ODU3OTQxY2YwZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzE0NzRmMDE5Y2U2MjliZWY3NzQ0ODNkODMyMzE5ZTNhYTdhNmUyZWMyNzQyMTAxZDBkYmU3MzA0ZmIzZjI3OCJ9" + }, + { + "resource": "repo://TFMTests/AssistantConversationMenuTests.swift#L9-L40", + "version": "repo-lines-v1:sha256:7196e06e9dc8a27ab84f9e882ab334752ea92d105ede6662f4caa406f3348cb3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBiZTY3NWZhODQwMDAyZjE2ODExNjhkODFkMWNhMTJjNDIzMmFjMzY2YzdjYTU1NThlNWNmOWUwMDVmOTQyY2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMzZmY0MjE3Yjc1YmQwYzMwZWVhZjlhN2NlMDczZTA3NWVmZTZlMWY2NWRhNzZkMzAzMmFmNmQ5Y2RhOGE4MDIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYxY2U4ZjAyY2Y0NzE4ZTA4OTNkNzFlNTJmNzc1YjM2NDQ5OGJhMWYwNjkwMWQxOWE1MWE2NzllYmU2ZGFkYjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index 53e427ea..34bb413e 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:902f023527c34c7a6aacb2a481737931b5f30946486a41b279723af0321cc821", + "pageVersion": "sha256:f62db2005beea84e609b860cb6d2e52e7a742faf98e3d9f088179b031465fbbe", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -52,11 +52,11 @@ }, { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:8efd1aa2da741e633993416bf4f88eff4df978ee3768a13788a1ac0419bdf837" + "version": "repo-file-v1:sha256:5284fc38208bb02af017031469b91288fedd0383d59f391622aa4c32a8450f42" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -413,6 +413,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T03:34:06.838Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 16c6dfcd..3bf4d0a8 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:4139c19be34c8fc47c74c1b05a07851c8e18bdfafe33237f7507fbc542e4b360", + "pageVersion": "sha256:cf89f7807de84434ff5a04aeed85734ce7e7f87b5fbc652e7210a60122fdd1d5", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -44,7 +44,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -151,6 +151,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index def4e55e..6135a5a9 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:f1dc10b8e646cf711a546c2b764e9d898ad017b4ef0f3bdc7f1932e54e746ae5", + "pageVersion": "sha256:1011bb9602def6c6ca1bb7bf0cde718b8479b58ac8af9526e85bae3771596073", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -20,7 +20,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -112,7 +112,7 @@ }, { "resource": "repo://TFM/Features/Assistant/AssistantView.swift", - "version": "repo-file-v1:sha256:d1b592e226f8467b8070139529d17882dcf327a3fe643392025f1ae1205d22dc" + "version": "repo-file-v1:sha256:f6845c8916416f497e3238b7da870623c2ea9636791d577ee1f0074c75be4ed5" }, { "resource": "repo://TFM/Features/Shell/WindowRoute.swift", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index 0949b4df..c0045758 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:0e61cbe711eed5c27ef6466c5ce521193641c61d25abf03298b9098834235f2f", + "pageVersion": "sha256:4dbecf9ffc4a898da594e820778cd3e693619b49c45ffc6745b7684ef00c4f46", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -40,15 +40,15 @@ "evidence": [ { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:8efd1aa2da741e633993416bf4f88eff4df978ee3768a13788a1ac0419bdf837" + "version": "repo-file-v1:sha256:5284fc38208bb02af017031469b91288fedd0383d59f391622aa4c32a8450f42" }, { "resource": "repo://TFM/App/RootView.swift", - "version": "repo-file-v1:sha256:6807c19b63ac86381892424de1971677ef4bc6ec42515fe57fee184be3951767" + "version": "repo-file-v1:sha256:25eb65f73680cfeddb1916e7c2f416828cc67abe6f80d7463fa9d84a4fcfa0c4" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -224,7 +224,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -257,6 +257,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/concepts/ui-design-principles.json b/openwiki/.claims/concepts/ui-design-principles.json index 02a51b63..dbaa7b60 100644 --- a/openwiki/.claims/concepts/ui-design-principles.json +++ b/openwiki/.claims/concepts/ui-design-principles.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:2ea45885f28729e3e1c545ea046ac5d552d5ad17465af3efc9beccb6755bc299", + "pageVersion": "sha256:92110e4a68f9373b2a5339feea69ea1a11bb1fe73727dd0e4709661aa7cb595a", "claims": [ { "id": "claim_3a403468456840acbfe0a524b90d5717", @@ -40,29 +40,37 @@ }, { "id": "claim_3f5b9e81f75049609142a3429f5255cb", - "statement": "TFM uses semantic AppKit-backed color roles and appearance-resolved native colors; actual sidebar selection distinguishes key and inactive windows while generic Highlight uses a quiet accent wash.", + "statement": "TFM uses semantic AppKit-backed colors and appearance-resolved native colors. Generic Highlight retains its accent wash, while Sidebar selection uses neutral primary fills at 10% key/6% inactive opacity, primary selected key-window text and accent leading icons, secondary inactive foregrounds and readable inline rename colors; the native menu outline remains accent-colored.", "evidence": [ { - "resource": "repo://TFM/Design/DesignTokens.swift#L320-L395", - "version": "repo-lines-v1:sha256:97cf99d4e0f76ce0d1ca96dd7e8ad269e9879b9b2f7bb1374c797bae6cddd789:eyJzZWxlY3RlZExpbmVDb3VudCI6NzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzN2U0NzhkZjRjYTk2YmU0NWVlNTIzZDBkODg2MjhjOGU5MzE4ZmJmMWMyMzNhNzQyNjhhY2E4MGJhZTJhMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzYjg0MTY2NGUxYTA4MTE5MTQyZTcxNTVjMGFjOGIyZmQ1MDA0NTdjYzcyMjExYjM4YTM5ODE5MDRkMzBjOGMifQ" + "resource": "repo://TFM/Design/DesignTokens.swift#L324-L397", + "version": "repo-lines-v1:sha256:4c3d078038e73a48f800936986620235262ea90de4604d87fe5cb0ed57322b9c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzN2U0NzhkZjRjYTk2YmU0NWVlNTIzZDBkODg2MjhjOGU5MzE4ZmJmMWMyMzNhNzQyNjhhY2E4MGJhZTJhMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" }, { - "resource": "repo://TFM/Design/DesignTokens.swift#L482-L574", - "version": "repo-lines-v1:sha256:4432b9753f4552d16c407fcecdefa6ef80687c033011ae52cb7707be6f67e2ce:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3N2FiMjhiODUxODE0MmY1ZTI1YWU3ZWFlOWJlZDVmZDg3ZGU3MmM3OTZmNGI2ZjYxYzA5YTI2NDNhYWUxNGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBjMTMzMmE0YmFmNjc1NzQ3YTM1NjcxOGZiZmQ2ZTRlNGQyY2Y0ZDQ0ZmQ4NDliMDEwODBlZTcxYjg1ZDMzZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4OTI1ZThiNTI3MmJiMGZmNTQ2YzhmNGMyZjgzNjM5MTRjNjZiOGU3ZTM1NTJhMzRjMzk1YjVkMWJmNDgzZjEifQ" + "resource": "repo://TFM/Design/DesignTokens.swift#L475-L570", + "version": "repo-lines-v1:sha256:c6317d8ac14b7f06ce6c12db8083120664d673c68de5a550e040cc8a5b7fa5bf:eyJzZWxlY3RlZExpbmVDb3VudCI6OTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZTViZjFmN2YxNjgxMjNlMzA5N2U2Nzc1YzdiOWFhNmZhNzA5M2QwNDc0YmMwNWQwNTcwZmU0YzBjOTE2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZiY2MwY2FhODg1YjI3NDliMmQ1YWIxNGUxYWRlMmJhN2JjMTRkM2QxYTVmZjc5YTdhMDliYzNmZmRlY2QwZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIxMjRlYzdmYTFjOWJkYWFiMmZkOTI0ZGY2YzFjNTU2MTliYjFhZjU0YzM0MGJhMzU5YTJlOWNmYjEyYjExMDAifQ" + }, + { + "resource": "repo://TFM/UI/Components/SidebarRow.swift#L64-L70", + "version": "repo-lines-v1:sha256:758a88d389a6172fe772f8b564dc6afb063952aa403fe429e6f10405157cc915:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTQ0NGNlYWFhOTNiY2IxNGFjZjA0ODRmZTAwN2I0OGEwNDUxMThiYzhiOTc5YThiMTQyYTg1Y2Y2ODdiNDlmMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjYyYzZlMWY4NjBiMDQzN2JkNzNhYjRiNWFhZTY3ZjY4YzhjOTgyNmM4OTNjMGNmYWJmZjk1YzYyZDMzZjQwOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTkzMTc3OGZjMWVkMGUyN2JlYmY2MWUxZDY4ZTliYzA1MzRiNWZkNTY4YzhiYzkyYTE5Njc3ODZkYjkyM2Q2ZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTA4NjUyNzk5YjUxZTc5ZTFhODc5NmVkMzM0MzlkNWY1MTA1NjNiMDk4NjVkODI4YmNhMWJkMzBhNjcxOGZiYSJ9" } ] }, { "id": "claim_9b8f98f539cc4266bb6731b13b191bab", - "statement": "TFM uses system glass on macOS 26 with native material fallbacks, while mobile cards use glass conditionally with dense-text backing and opaque/stronger-border fallbacks for transparency or contrast accessibility settings.", + "statement": "TFM explicit glass controls use system glass on macOS 26 with native material fallbacks, but the Sidebar column relies on NavigationSplitView's own background on macOS 26+ and adds only a pre-26 AppKit visual-effect fallback. Mobile cards retain conditional glass, dense-text backing and opaque/stronger-border accessibility fallbacks.", "evidence": [ { "resource": "repo://TFI/UI/Managed/MobileCards.swift#L59-L109", "version": "repo-lines-v1:sha256:2e0914d2ce2a68e255ad11b2db9afb74cedb5cfad807d3a4122d138bad3b62e9:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI0NTkwNDRkNjNmYzg4MGVlMDRhM2RhNTJlMjRkM2UyODM2Mjg3NzkwMzI4MzUzYjQ4NDJmNTcxODI4MGNjOTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhlNjAyMGUyNjc2OWE3MjVhMjg4YmY2MmI1YzY5Nzc3ZTUzM2M3N2Q4NDg4ZTE1ODZlNzY1NzY1YTNjMTBmZGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyY2IyY2M5MzZlNjA0ZGM4NDFhN2I5M2MwMWE5YTRiYWY3ZjMwNzg0M2FmYzIzMDMxMDEzOTkyNDc5ODk2MTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmNjNhOWYxYzdmMmY1MWRhZjQ2Y2RhYWIxOWVlZGEyODI0ZGQxMzlkZjc2ZDZkZWY3MWQ3ZTlkNjg0OTc2NWUifQ" }, { - "resource": "repo://TFM/Design/DesignTokens.swift#L397-L480", - "version": "repo-lines-v1:sha256:111f22df63028d345b5a301a2b9f8f0a97b4dd313819bd76a9f079c25758133f:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwNjk0ZTI5NzY5ZTg0YTk4ZjI3OWNjMmMzNmRlYjBkZDA5YzQyNDQxMzJlNzMyYzNkYTE1MWU0YzhlMDJlZWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NThhZWMxMGMyMDdjMmZmOTQ5ZDI0MmE5OTllMTQ0YzIyYTQzNGFkYjAzM2U1NTdiNGFkOTAyZTllMTBlNGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIxZjFhYmY3MWE5NDBkOTM4YjJlOGUxZTRkMzJkMmI4MTU1M2I1OWFiOTk5YjBmZmM0Nzc0MWNmNjNmZTgyNDYifQ" + "resource": "repo://TFM/App/RootView.swift#L33-L47", + "version": "repo-lines-v1:sha256:7477fccf1fdb5be27730274cc5816b615e7cf9555a753112c0c99bd77fda4bc7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMDhhOGI4NmMyMTkzOTMzMWNlMmRlNzQ4Y2MwMjQyODI5NmExNWUyOWUwMTJmOWI1YjI5YmE3NTQ5MDViOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxMDM0ODFjNTdkMGYwZGMwZjc2Y2QyY2M4YjM2MDRjNGU4Mjk2ZGE0MTIyYWRkMTllODE3NDVkYzdlMzMxYTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5OGYxYTA0YmVlNzFjM2NmYzJiNDcxOWU2ZmQ5MGM5NmY0N2E5MWU0ZDJmZGU3ODgzNTNjMDM3MDQ2NmY5ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUxMmQ1NmZkOTU2YzBiNzRhYjBlMDdlZDU5ZjJlM2QwY2NkNTUwNDhkNmQyMGIwZTU5ZGQ4ODVkMGU4NjA4MzQifQ" + }, + { + "resource": "repo://TFM/Design/DesignTokens.swift#L412-L464", + "version": "repo-lines-v1:sha256:7edc22ac946a5bf05301fcf03b6d19cf763796b4b1c1e5ede679c271cb269b79:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI3MGM4MjUxYzMxNWRlYTdjOTBmZTc0YjQ4MTNhMjUxYjM4YzhlOGRmNTkzNGI4NzcwODkxOTc1NTljNGMxZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUzN2VmMGJjMjIyOWU0MmRiOGQ3MGYxYzUyOWRmNzI2YWNiZTZjYTk4NDlhZjRmZGE4MDIyNGJlYzZkNGE3ODcifQ" } ] }, @@ -285,10 +293,60 @@ "version": "repo-file-v1:sha256:486afc2bf3721fee357282fdd83470fcd029c99f029f17b66f3c4f634305e662" } ] + }, + { + "id": "claim_b0f590a197334590b67c9404a0dd06c6", + "statement": "The Sidebar mode rail shares one glass capsule with neutral animated selection, explicit selected accessibility traits, Reduce Motion handling and key-window focus outline; the Assistant footer groups interactive New Chat/Archive glass controls on macOS 26+, using neutral Archive selection and a semibold primary glyph.", + "evidence": [ + { + "resource": "repo://TFM/Features/Assistant/AssistantView.swift#L81-L130", + "version": "repo-lines-v1:sha256:851c5a4704e41df47e9500fba2300c125f45b4e02dfb2939f29148a6e857e26f:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMjA0MzJkYzQ3NGFmN2VmY2NlYmMwZTQ1ZDAzYzIxYmM3OTY0ZWMzZWY5NDE2Zjg5NmRlMDVlNmM0ODAzMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhZjI4MmYyMDg2ZWI4ZTZmYTJiMDdmMGY3NzBmYmE2M2UzZWFmOTY2YzE0Mjk1NDg0NWFhYmQzODMxYTRiMDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0YTM2NWE5MGVjMTFlOTA0MzI3NTJjZjkzNzZlYzVlZDM2YjgwYmE1ZThlNjg1NGZjZjk4OTcwZDMwNDQ0NjkifQ" + }, + { + "resource": "repo://TFM/Features/Sidebar/WindowSidebarView.swift#L38-L94", + "version": "repo-lines-v1:sha256:cfbd8237ba2ca4f4797b4bba5216aced08897e568ba2875816a470bcd4ae3da3:eyJzZWxlY3RlZExpbmVDb3VudCI6NTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjNzM1YTQ1NTZjMmZkNjgzMmVlZTgyY2Q4Y2MyYzM5MzA0YTAxNjdmNWZkMTVlNTgwYWI1MzRiNzQxMmUyZDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFlNThhYjBjNDdiN2Y5OWVkOGM1OGM1Yjg2Njc2NWU3MDQ4YTZlZWI2YmJhODExYzRmNzcxMDk1ODZlMzQ2MWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwYmUyNzE1MDFmNzVhZWZlNjgxNzFmMTU5MjZlMGUzOWFkYjM4NmQyZjMxNjU2YzM0N2UzOGJlNDhiMDY4YTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiODlkNTljMjE3MGEwZWFmOWY0NDc2YmVjMjRjOThlYjdlYTRiMWNlYTY4ZDQ5MmQ2OGNkNzAzOGZkNDE0YmQifQ" + } + ] + }, + { + "id": "claim_42f7f8d608a249d6b24b1312c2719d20", + "statement": "Desktop single-rule forwarding menus group Address and SSH Command in a Copy submenu, while mobile exposes flat copy actions before Edit/Duplicate; both use native pasteboards, disable unavailable SSH-command copying and leave forwarding execution to separate actions.", + "evidence": [ + { + "resource": "repo://TFI/Features/Ports/MobileForwardingView.swift#L80-L96", + "version": "repo-lines-v1:sha256:eb7629f74c17d9c10936b2914f483e553754a07ac0ca69a9bcd6e00ef110c801:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY2NjgzMDMxZjE3ODk4NTBhNmU4M2UwNmEyMGM5YzFhZDNmNzM5ZjcwYWRjYTZlODQxM2M3YzBmMTgzYmJmNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNDRlMGU1NmVjNzE0ZmQzMmY3M2M0MTYyMjg4ZjY5YmMzYzc4MGIzZGM5Yzk4MGExMTUxZDBlN2M5YzEyYjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI5ODI0MWFjMGE4M2I1NDZmM2Y0Y2M0YjdmZTE5NDJlMjM4MzdlNDIwNjM1MGEyMDAzNDM5YWUzNzg2YWM2YWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE1YzhkZmE3ZjFlMGI4MTkyZGRlNmVlODc4NDc3NmFhZjYyNzk2YWI5MjM5OTU5YTE3ODE2YzQwOGE3NmM4MTgifQ" + }, + { + "resource": "repo://TFM/Features/Ports/PortForwardingView.swift#L59-L80", + "version": "repo-lines-v1:sha256:0bf89175a0a781274cada4a8fa227ae456a38774c7d7ee3701e1e9513e081cf6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwMDIyNWJmYTIwMTYwY2UyNDE2ZDJjNzU0NmZiZTFkOTNmYzJmZTM2YjUyYmNhMWIxYTBhMDg0YzhkY2VhOTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYyNmE1N2EzYzNkOGJiYmJmYWJlMzkzMjJkOWM2ZWM2OTkyMmU2MTNlMWM1YThiMTY3NTFmYWZkYWZhNmU4MDQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0ZmNmNDBlNzlkNDZkMTFjYjJkYmJkNjVjMzczZGQzYTFlODFkYTI1NjI4NTJhNDAxYmZiZDNlODNkN2ZkNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjMTgxMmU5ZmQwYWY4ZTMwMWVlMTYwYTNjOTJiOTUwYzQxMDE4YjVlYjA0ZDkyMTdjYWI2MzU1OTQ2Y2I3MGQifQ" + } + ] + }, + { + "id": "claim_179ba724e7c9488d839b0c475124be00", + "statement": "Desktop and mobile Snippet cards expose one Run action instead of a separate Execute on Hosts override affordance; target selection and destructive-script confirmation remain owner-controlled, with desktop filling missing targets and mobile choosing hosts when no valid targets remain.", + "evidence": [ + { + "resource": "repo://TFI/Features/Snippets/MobileSnippetsView.swift#L290-L303", + "version": "repo-lines-v1:sha256:82d7c71fd2cd54e76ebcee778a9240568dfb90e96a4e2a3af555591257aefe56:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY2NjgzMDMxZjE3ODk4NTBhNmU4M2UwNmEyMGM5YzFhZDNmNzM5ZjcwYWRjYTZlODQxM2M3YzBmMTgzYmJmNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZGJmOWM0NWEwODdiYTA3ZDkwODE5OTFlNGY5ZTNmYWU3ZWZlMjM5Yjk0NGViYWQwYWNlODc1MGE2NmEyYzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + }, + { + "resource": "repo://TFI/Features/Snippets/MobileSnippetsView.swift#L356-L380", + "version": "repo-lines-v1:sha256:a275fa91f09b523b4aafa93db152698586c8b779785492b6f16cabf5626e666a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE3ZTg5NDNmOTczODlhYjlhNjEwODVlYjRmNTEyNmFkMWFmNjQ0YTcxM2M0YzQ2MWU3ZDEyNDZiYmFjNjEwMGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRiOGUzOTMxYjNlNWEyZjhmNGU2NWM4NTk2YWQzMzYxNDc3NjY0YjAyYzM1MzFiYzg5NjQ3NGE3YWYxYjdjZTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE4Zjg1NDZlNDMzZDc4NjQzODA2NmJjNjllMDg1MGZlNWIyNjU3MDBmYzBlNDM4ZGU4MWRiM2ZjMTJiOWM0ZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4MmVhMGU5N2Q0OTc1NzRlNWI1ZmQ5ODQ1NDU1YWRjNTVhYWE0MWQxMGI4MzMzNGNmNGJmMjk5YjdhYTY1NzgifQ" + }, + { + "resource": "repo://TFM/Features/Snippets/SnippetsPageView.swift#L302-L326", + "version": "repo-lines-v1:sha256:6a4e35d060bf8c3af98a6bcafa7f2c3af3d2919dc77bbf40cddfec56f83474eb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMDE2OGQ4OTkwNWRjZWRlZDQ4OGFjOWE3OTg3ODA5NjMyYTMzZWZmYzE3MmZmM2UzMDQ2OTkwNmJmNDRlZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJhMDZhM2U3N2M4MzgzZTk4YmE3YTVkZDUzN2I1Mzg4MTdhMDFkNDZlOTAxMGUyNzEyNTgxZGFmZmFlZjYwOGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5M2IzNWRiMDFmMTgyYjgzY2VkZWQ0MzM1ZTY4OGZhZmVhMzAxZjBhNzJlNGIxZGYyNmMwZDU0YjU5MThkMjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNkODg0YTQxZDNiYmZhN2E0MzAxZjdjMzExZGE3OWE4YThjNmU1YmI3ZTljYWFlNjc3M2RiOTM4OTMxMjg4NWIifQ" + }, + { + "resource": "repo://TFM/Features/Snippets/SnippetsPageView.swift#L458-L466", + "version": "repo-lines-v1:sha256:9601001fe4af32f37a89a620b06175d054106430cb8d592abab3542ee6f56bc8:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2IzZWIyODgwNTgxMGU0OTgwMjE3ZGJmMmJkMjNhOWM5M2Y4ZTFiZjAyMDdkZGVlOThmZDlhNTQ1ZTYxMGRjOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMTkxMmRjZmY4ZDViM2RiNmEyNjY0NGE1ZTVlMTA0MzBmNDhkNTBmYmM0Y2U2MDYyZjEwNDY2MjQyMDEyOWMyMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZWUwY2ViM2FjNjViZDYzZTdiNTI4NjE1YzM5YzMxMTNlMTUyNWY2MzY0YjliMjg2ZGNiYjI3YjI4MWZjMzdkMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzE1MWE5ZGJhY2E5NmQxYjg4NjU2ZTM0YTgzMjVkNDRhNjAzZjhkYThlNjE3ZjNiZjdmNDMxMTY2YjZhMzIwNSJ9" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T00:14:42.075Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 1b801bc5..342ccfe7 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:89970353463e9a930e9c659614c1e55ef4107801b0721beda98be323539ac450", + "pageVersion": "sha256:5533c3350dcd3e0218aa0071621fea4658eb209ed33958d8f719a4f6752f4cc6", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -38,7 +38,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -129,6 +129,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json index 0279647d..564f5210 100644 --- a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json +++ b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:a1bfef0a62680b258aa4a9db1d84956798dfe6deb3c50df7c2aa29ba2ffa6ebb", + "pageVersion": "sha256:6bee77cc55ac7f4209d5ea3e889e5ef21b88a7e3f39118c9fa0dffe665855886", "claims": [ { "id": "claim_b7fbd64158554b8b8ca80a0a613b3bc3", @@ -220,7 +220,7 @@ }, { "resource": "repo://TFITests/MobileConnectionPickerTests.swift", - "version": "repo-file-v1:sha256:4186a0f0cee0a53fe901f751fbfd84bd30eb7f8557683f5160160c1815757003" + "version": "repo-file-v1:sha256:5fa61b5614ce53928bd77c0e7b5254a41d8acc68a7bfbde0e5a9cb2b8f4f3791" }, { "resource": "repo://TFMTests/ConnectionHistoryTests.swift", @@ -313,6 +313,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T03:34:06.838Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.claims/workflows/ssh-session-lifecycle.json b/openwiki/.claims/workflows/ssh-session-lifecycle.json index a1a78494..babc957f 100644 --- a/openwiki/.claims/workflows/ssh-session-lifecycle.json +++ b/openwiki/.claims/workflows/ssh-session-lifecycle.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:8ed164f2bf48e753c9305fac8b0f7e7754b6e223ed99d68ba71ed6c8ca191334", + "pageVersion": "sha256:d57d02019bbac973232c7889a9cd1d1192b0c70a6dd91c12a3a867b578b6fee0", "claims": [ { "id": "claim_69a9de4ce918417c8a44c6c87c9f6564", @@ -146,7 +146,7 @@ }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", - "version": "repo-file-v1:sha256:42182779b1c8b55a350b4f77b4237e05c87338d040f72530f919cbd13a474823" + "version": "repo-file-v1:sha256:2080b3e5bc75c0c08ee6414fb1034aa30ab3a4d24a58469293025ec420b202f4" } ] }, @@ -175,6 +175,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T05:17:08.701Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 858ad735..90bc1f77 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T03:41:38.907Z", + "updatedAt": "2026-09-23T05:23:00.532Z", "command": "update", - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index 46e5b881..1a21776d 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:aacd9e53709881d4edac532fd23dfffe4e8ba8cf7a5b611a79bfd846f1128d8a", + "pageVersion": "sha256:a6cde418a9982f9a4a6cd86e4848c36ef88b6ce715d11715d06b6a50f9fef8d7", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/architecture/engineering-invariants.md": { - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", - "pageVersion": "sha256:902f023527c34c7a6aacb2a481737931b5f30946486a41b279723af0321cc821", + "pageVersion": "sha256:f62db2005beea84e609b860cb6d2e52e7a742faf98e3d9f088179b031465fbbe", "completedBy": "codex", - "completedRunId": "03a5a83b-2791-4c16-87fa-a3244c996460" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/architecture/mobile-ui.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -23,25 +23,25 @@ "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/module-map.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:4139c19be34c8fc47c74c1b05a07851c8e18bdfafe33237f7507fbc542e4b360", + "pageVersion": "sha256:cf89f7807de84434ff5a04aeed85734ce7e7f87b5fbc652e7210a60122fdd1d5", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/architecture/native-app-directories.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:f1dc10b8e646cf711a546c2b764e9d898ad017b4ef0f3bdc7f1932e54e746ae5", + "pageVersion": "sha256:1011bb9602def6c6ca1bb7bf0cde718b8479b58ac8af9526e85bae3771596073", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/architecture/platform-runtime.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:0e61cbe711eed5c27ef6466c5ce521193641c61d25abf03298b9098834235f2f", + "pageVersion": "sha256:4dbecf9ffc4a898da594e820778cd3e693619b49c45ffc6745b7684ef00c4f46", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/architecture/shared-module-directories.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -65,11 +65,11 @@ "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", "pageVersion": "sha256:46c6736cc2e227449885be7dd7fbf51a7b98259f7509c85a334881cc20382e90", "completedBy": "codex", - "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9", - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827" + "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,11 +79,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { + "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", + "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", "pageVersion": "sha256:6f9cbf0a3aa5fea9637709e225bee6cf28af7cdf172609b3c903f75bade148b7", "completedBy": "codex", - "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9", - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827" + "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -107,11 +107,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/ui-design-principles.md": { - "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", - "sourceFingerprint": "sha256:5695d0d8460e1bfb0a6bfc3981f4a8012f4c28db0c06cf36df962449676935db", - "pageVersion": "sha256:2ea45885f28729e3e1c545ea046ac5d552d5ad17465af3efc9beccb6755bc299", + "pageVersion": "sha256:92110e4a68f9373b2a5339feea69ea1a11bb1fe73727dd0e4709661aa7cb595a", "completedBy": "codex", - "completedRunId": "aa140d94-d963-45b6-b682-05a1a3802ffa" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/integrations/mcp-server-and-bridge.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -156,18 +156,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:89970353463e9a930e9c659614c1e55ef4107801b0721beda98be323539ac450", + "pageVersion": "sha256:5533c3350dcd3e0218aa0071621fea4658eb209ed33958d8f719a4f6752f4cc6", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", - "pageVersion": "sha256:a1bfef0a62680b258aa4a9db1d84956798dfe6deb3c50df7c2aa29ba2ffa6ebb", + "pageVersion": "sha256:6bee77cc55ac7f4209d5ea3e889e5ef21b88a7e3f39118c9fa0dffe665855886", "completedBy": "codex", - "completedRunId": "03a5a83b-2791-4c16-87fa-a3244c996460" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:8ed164f2bf48e753c9305fac8b0f7e7754b6e223ed99d68ba71ed6c8ca191334", + "pageVersion": "sha256:d57d02019bbac973232c7889a9cd1d1192b0c70a6dd91c12a3a867b578b6fee0", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" } } } diff --git a/openwiki/architecture/desktop-ui.md b/openwiki/architecture/desktop-ui.md index fec366ed..a799c6e5 100644 --- a/openwiki/architecture/desktop-ui.md +++ b/openwiki/architecture/desktop-ui.md @@ -5,7 +5,7 @@ description: "How the macOS app composes its native window shell, persistent wor tags: [macos, tfm, ui, navigation, workspaces, swiftui, appkit, design] verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z sources: - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift @@ -15,6 +15,8 @@ sources: resource: repo://TFM/App/RootView.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-f490cb5bfbb8ce236a7f1bf4 + resource: repo://TFM/Design/DesignTokens.swift - id: openwiki-source-95f72232fb896aa95dd5f6c7 resource: repo://TFM/Features/AI/TranscriptListView.swift - id: openwiki-source-d35453512ef488d0cacdc06e @@ -33,11 +35,13 @@ sources: resource: repo://TFM/Features/Workspaces/WorkspaceContentView.swift - id: openwiki-source-f6a967b4f14bf50d1af51b8b resource: repo://TFM/UI/Managed/ManagedCardPage.swift + - id: openwiki-source-23acce6fe9173a886d433b44 + resource: repo://TFMTests/AssistantConversationMenuTests.swift - id: openwiki-source-98505b843db3ce6b8e4ca024 resource: repo://TFMTests/PersistentWorkspaceDeckLayoutTests.swift - id: openwiki-source-dceb8d7e7c5bb09359e70052 resource: repo://TFMTests/ResponsivePageLayoutTests.swift -generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } +generated: { by: "codex", at: "2026-09-23T05:17:08.701Z" } --- # TFM Desktop UI Architecture @@ -89,6 +93,8 @@ The main layers are: The sidebar is one column whose mode rail switches between Navigator, Assistant, Snippets and History. `WindowModel.activateSidebarMode` is an **intent operation**, not just a property assignment: choosing Navigator from the full Assistant restores its prior destination, while choosing Assistant inside a workspace opens the contextual sidebar rather than hiding the terminal. Direct mode assignment remains available for presentation/restoration without these navigation side effects. History is unavailable for focused Telnet/serial sessions, and reconciliation moves away from an invalid mode. +The column's material belongs to the native split view on macOS 26 and later; `RootView` does not layer another glass sheet over it. Earlier systems receive `LegacySidebarMaterialBackground`, an AppKit visual-effect fallback. Explicit glass remains on controls: the mode rail is one capsule with a neutral moving selection highlight, readable symbols, selected accessibility traits and a separate key-window keyboard-focus outline. Reduce Motion disables its selection animation. See [UI Design Principles](/openwiki/concepts/ui-design-principles.md) for row foreground and active/inactive selection roles. + Keep the following coordinates distinct: - **Destination**: Vault, a workspace, full Assistant, Metrics or Settings. @@ -166,6 +172,8 @@ The renderer resolves a pointer to a section and insertion slot; it does not nee The full Assistant destination and the workspace's compact Assistant are different presentation surfaces. Opening a conversation in the compact sidebar does not replace the foreground terminal. Its transcript region and interaction/composer region can be composed separately, and terminal context/actions are passed deliberately. +Archive presentation is one `WindowModel.isAssistantArchivePresented` flag shared by these two surfaces within the window, not a second flag on `WindowSidebarState` and not app-wide navigation state. Conversation selection remains surface-specific. `AssistantConversationMenuSurface` reads the current shared flag when constructing the native blank-area menu, so both **Show Archive** checkmarks agree. Its action still respects the invoking surface: the sidebar route clears the compact conversation and shows the archive without replacing the foreground workspace; the full-screen route clears compact selection and selects the Assistant destination. Opening or closing a compact conversation clears the shared archive flag, and the compact archive's **Open Full Screen** control explicitly promotes the archive to the main destination. + Native transcript ownership adds another boundary: - `TranscriptListCache` is window-local. @@ -201,6 +209,7 @@ Before changing a UI owner, trace both what happens when its view disappears and | Change | Focused seams | | --- | --- | | Multi-window navigation, return routes or sidebar modes | `WindowModel`, `WindowSidebarTests`, `MultiWindowTests`, `SettingsNavigationTests`. | +| Shared Assistant Archive state and surface-aware blank-area menus | `AssistantConversationMenuTests.archiveStateIsSharedWhileSidebarActionKeepsWorkspaceInFront` and `WindowStateTests` compact/full-screen navigation cases. | | Hidden workspace layout or drag geometry | `PersistentWorkspaceDeckLayoutTests`; split/pane interaction and broadcast tests. | | Narrow editor or Metrics detail layout | `ResponsivePageLayoutTests`, including native table identity and retained scroll positions. | | Managed selection/reordering | `ManagedCardDropResolver`, selection and marquee tests; resource-specific move tests. | diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index d1ce7fa6..e4105cf3 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -117,7 +117,7 @@ sources: generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T03:34:06.838Z + at: 2026-09-23T05:17:08.701Z --- # Cross-Cutting Engineering Invariants diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index eabe5561..8c9ed3ab 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index b6b7b38d..4eaac88a 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index 9e0dc4c9..5ac1d555 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z --- # Platform Runtime, App Composition, and Lifetimes diff --git a/openwiki/concepts/ui-design-principles.md b/openwiki/concepts/ui-design-principles.md index 724882fd..70c2db4c 100644 --- a/openwiki/concepts/ui-design-principles.md +++ b/openwiki/concepts/ui-design-principles.md @@ -36,8 +36,12 @@ sources: resource: repo://TFI/Features/AI/MobileAssistantConversation.swift - id: openwiki-source-fc448263e4e5ce330b8cbda1 resource: repo://TFI/Features/HostEditor/MobileHostEditor.swift + - id: openwiki-source-5503f969776cb509a7d69160 + resource: repo://TFI/Features/Ports/MobileForwardingView.swift - id: openwiki-source-02810e1f8eac4d0983f8cd44 resource: repo://TFI/Features/Settings/MobileTerminalSettingsView.swift + - id: openwiki-source-db402cad9d68dd8e66bdd7ee + resource: repo://TFI/Features/Snippets/MobileSnippetsView.swift - id: openwiki-source-e396438eb6a8a7ca888da5f4 resource: repo://TFI/Platform/TerminalFontCatalog.swift - id: openwiki-source-a4f3a690a8395e2ac9b065b9 @@ -54,16 +58,26 @@ sources: resource: repo://TFM/Design/DesignTokens.swift - id: openwiki-source-95f72232fb896aa95dd5f6c7 resource: repo://TFM/Features/AI/TranscriptListView.swift + - id: openwiki-source-d35453512ef488d0cacdc06e + resource: repo://TFM/Features/Assistant/AssistantView.swift - id: openwiki-source-1e0cf223ab8448c8eee3f345 resource: repo://TFM/Features/HostEditor/HostEditorView.swift + - id: openwiki-source-f1d91230931f6a1921af6d03 + resource: repo://TFM/Features/Ports/PortForwardingView.swift - id: openwiki-source-cfe2a6b22338023c37a5b503 resource: repo://TFM/Features/Settings/SettingsPageView.swift - id: openwiki-source-af7fbbca079c2cb6201a5151 resource: repo://TFM/Features/Settings/TerminalCustomConfigurationEditorSheet.swift + - id: openwiki-source-0854be84b166268626a3cb08 + resource: repo://TFM/Features/Sidebar/WindowSidebarView.swift + - id: openwiki-source-23281ca04cda9d7abc31173f + resource: repo://TFM/Features/Snippets/SnippetsPageView.swift - id: openwiki-source-ba160208435f3410138a3539 resource: repo://TFM/Features/Workspaces/PersistentWorkspaceDeckLayout.swift - id: openwiki-source-385c953ef1c6bf095d1c1244 resource: repo://TFM/Platform/TerminalFontCatalog.swift + - id: openwiki-source-4a6780d3dff41f37f56a094f + resource: repo://TFM/UI/Components/SidebarRow.swift - id: openwiki-source-f6a967b4f14bf50d1af51b8b resource: repo://TFM/UI/Managed/ManagedCardPage.swift - id: openwiki-source-d3d065330c730839f5cc6112 @@ -74,10 +88,10 @@ sources: resource: repo://TFMTests/TerminalFontRenderingTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } +generated: { by: "codex", at: "2026-09-23T05:17:08.701Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T00:14:42.075Z + at: 2026-09-23T05:17:08.701Z --- # UI Design Principles and Shared Presentation Boundaries @@ -138,7 +152,7 @@ Examples of deliberate geometry: `TermindColor` uses semantic AppKit colors for neutral surfaces, foregrounds, separators, accent and success/warning/danger roles. User-authored Host colors and terminal ANSI palettes are deliberate exceptions. When a native layer needs a concrete color, `resolvedNative` resolves it against the owning appearance rather than assuming a dynamic `NSColor` will remain correct after conversion to `CGColor`. -Selection is contextual, not one universal tint formula. Generic `Highlight` uses a quiet accent wash for selection and a neutral hover wash. Sidebar rows use a stronger accent fill and reversed foreground in the key window, then an accent wash/tinted foreground when inactive. Do not infer actual behavior from an older nearby comment claiming all selections ignore active-window state. +Selection is contextual, not one universal tint formula. Generic `Highlight` uses a quiet accent wash for selection and a neutral hover wash. Sidebar rows instead use a neutral `Color.primary` fill at 10% opacity in the key window and 6% otherwise. Selected key-window text stays primary rather than reversed, leading row icons remain accent-colored, and inactive row text/icons become secondary. Inline rename fields follow the same active/inactive readability rule. The native context-menu outline is still accent-colored; it is not the selection fill. Read the implementation rather than assuming an older `Highlight` comment describes every surface. ### Mobile vocabulary @@ -148,7 +162,9 @@ For example, desktop card density and mobile card density deliberately differ. T ## 3. Prefer real system materials and explicit fallbacks -TFM's glass modifiers use system Liquid Glass on macOS 26 and native material backgrounds on older systems. The shared sidebar background covers the whole column, including empty space, so custom native rows do not look pasted over a separate background. Native material is the fallback rather than a hand-painted imitation highlight. +TFM's explicit glass controls use system Liquid Glass on macOS 26 and native material backgrounds on older systems. The Sidebar column itself is different: on macOS 26 and later, `NavigationSplitView` supplies its background and `RootView` adds no second glass surface. Before macOS 26, `LegacySidebarMaterialBackground` supplies an `NSVisualEffectView` using `.windowBackground`, `.withinWindow` blending and window-active-state following. Do not restore an extra full-column glass layer merely because custom native rows sit inside the split view. + +The mode rail shares one glass capsule; selection is a neutral moving capsule behind primary symbols, with a separate keyboard-focus outline only in the key window. Its selection animation respects Reduce Motion, and selected accessibility traits remain explicit. Assistant's footer keeps New Chat as an interactive glass capsule and Archive as an interactive glass circle, grouped in `GlassEffectContainer` on macOS 26+. Archive selection uses a neutral fill and semibold primary symbol rather than accent fill with reversed content. These controls and row selections serve different roles even though both avoid an emphasized accent background. TFI's card surface similarly uses system glass where available, but keeps a nearly opaque grouped background behind dense text. Reduce Transparency or increased contrast selects an opaque fallback. Increased contrast also strengthens the border. The clipping, interaction shape and context-menu-preview shape are coordinated, so the visible card and the tappable/previewed object agree. @@ -174,6 +190,10 @@ flowchart LR Use capabilities to say what a renderer may offer; keep the operation's actual validity check with its owner. Disabled UI is useful feedback, not a replacement for business validation. +Native menu structure can differ without changing the operation. A single desktop Port Forwarding card groups **Address** and **SSH Command** under **Copy**, before the Edit/Duplicate group. Mobile keeps flat **Copy Address** and **Copy SSH Command** actions in the equivalent position. Both write through their native pasteboard adapter and disable SSH-command copying when the library cannot generate a command; copying never starts forwarding. + +Snippet library cards expose one **Run** action instead of separate Execute and Execute on Hosts actions; the mobile detail and host-selection surfaces use the same Run vocabulary. Target selection and destructive confirmation remain in the existing feature flow: desktop fills missing saved targets through its chooser, while mobile asks when no valid targets remain. Renaming the action and removing the duplicate override affordance does not remove the host validation or destructive-script confirmation boundary. + ## 5. Share form fields; keep draft and route lifetimes explicit Both Host editors reuse `HostOrganizationSection`, `HostProtocolProfileControls` and `HostConnectionFields`, but their containers are intentionally different: diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index dcf8b989..d7031fa4 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -25,7 +25,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:23:14.035Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z --- # Termind Engineering Quickstart diff --git a/openwiki/testing/behavioral-boundaries-and-fixtures.md b/openwiki/testing/behavioral-boundaries-and-fixtures.md index d27ee04c..ad6049ba 100644 --- a/openwiki/testing/behavioral-boundaries-and-fixtures.md +++ b/openwiki/testing/behavioral-boundaries-and-fixtures.md @@ -59,7 +59,7 @@ sources: generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T03:34:06.838Z + at: 2026-09-23T05:17:08.701Z --- # Testing Strategy and Behavioral Boundaries diff --git a/openwiki/workflows/ssh-session-lifecycle.md b/openwiki/workflows/ssh-session-lifecycle.md index 2835ba62..ad982278 100644 --- a/openwiki/workflows/ssh-session-lifecycle.md +++ b/openwiki/workflows/ssh-session-lifecycle.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T05:17:08.701Z --- # SSH, Telnet, Trust, and Workspace Session Lifecycle From 8dfccf7080948d6c9e2748cf7f31c28b74f4bfef Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 14:09:55 +0800 Subject: [PATCH 14/30] Fix Assistant runtime cancellation ownership --- .../AgentReplayPersistenceTests.swift | 103 ++++- FrameworkTests/AgentSteeringTests.swift | 14 +- ...AssistantConversationOperationsTests.swift | 377 +++++++++++++++++- .../Agent/AssistantExecutionController.swift | 4 +- .../Agent/ConversationModel.swift | 307 +++++++++++--- TFITests/MobileAssistantTests.swift | 7 +- TFMTests/AgentApprovalQueueTests.swift | 14 +- TFMTests/ChatComposerStateTests.swift | 68 +++- 8 files changed, 795 insertions(+), 99 deletions(-) diff --git a/FrameworkTests/AgentReplayPersistenceTests.swift b/FrameworkTests/AgentReplayPersistenceTests.swift index 6e7e9f2b..8447c688 100644 --- a/FrameworkTests/AgentReplayPersistenceTests.swift +++ b/FrameworkTests/AgentReplayPersistenceTests.swift @@ -6,7 +6,7 @@ import TermindDomain import TermindPersistence import Testing -@Suite @MainActor +@Suite(.timeLimit(.minutes(1))) @MainActor struct AgentReplayPersistenceTests { @Test func snapshotsAndFrozenResultsSurviveRestoreAndDeletion() async throws { @@ -111,6 +111,88 @@ struct AgentReplayPersistenceTests { #expect(restored.runs.first?.failure == (state == .failed ? "preparation failed" : nil)) } + @Test + func queuedCancellationAtomicallyAdoptsEditedStagingAndReloadsCapturedPayload() async throws { + let container = try TermindPersistence.makeContainer(inMemory: true) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let conversationID = UUID() + let original = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + try await repository.save( + AgentRunSnapshot( + conversationID: conversationID, state: .completed, + permissionMode: .askForApproval, messages: [original] + )) + let stored = try #require(try await repository.loadConversations().first) + let store = AssistantConversationStore() + store.restore([stored]) + let conversation = try #require(store.conversations.first) + let context = AgentTurnContextSnapshot( + sessionContext: "captured host", skillCatalog: "captured skills", + planContract: "captured plan", toolAvailability: ["terminal_suggest": false] + ) + let image = AgentImageAttachment( + label: "capture.png", mimeType: "image/png", data: Data([1, 2, 3]), + pixelWidth: 2, pixelHeight: 2 + ) + let attachment = AgentContextAttachment( + id: UUID(), kind: .file, label: "capture.txt", + contentTypeIdentifier: "public.plain-text", suggestedFilename: "capture.txt", + data: Data("captured payload".utf8) + ) + let replacement = AgentTranscriptMessage( + role: .user, text: "after", authoredText: "After authored", + images: [image], contextAttachments: [attachment], contextSnapshot: context, + createdAt: .now + ) + let operations = AssistantConversationOperations(repository: repository) + let composed = try #require( + try await operations.edit( + conversation, messageID: original.id, replacement: replacement + )) + let staging = try #require(conversation.runSummaries[replacement.id]) + let calls = ReplayModelCalls() + conversation.start( + runtime: AgentRuntime( + model: CountingReplayModel(calls: calls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository + ), + request: AgentRunRequest( + conversationID: conversation.id, promptID: composed.id, + prompt: composed.text, authoredPrompt: composed.authoredText, + promptImages: composed.images, + promptContextAttachments: composed.contextAttachments, + permissionMode: .fullAccess, contextSnapshot: context + ), + repository: repository + ) + + await conversation.stopAndWait() + + #expect(await calls.count == 0) + #expect(conversation.runSummaries[replacement.id] == nil) + let freshRepository = SwiftDataAgentRunRepository(context: ModelContext(container)) + let restored = try #require(try await freshRepository.loadConversations().first) + let run = try #require(restored.runs.first) + #expect(restored.runs.count == 1) + #expect(run.id != replacement.id) + #expect(run.state == .cancelled) + #expect(run.startedAt >= staging.startedAt) + #expect(run.finishedAt.map { $0 >= run.startedAt } == true) + #expect(restored.messageRunIDs[replacement.id] == run.id) + let message = try #require(restored.messages.first) + #expect(message.id == replacement.id) + #expect(message.text == replacement.text) + #expect(message.authoredText == replacement.authoredText) + #expect(message.images == [image]) + #expect(message.contextAttachments == [attachment]) + #expect(message.contextSnapshot == context) + #expect(message.createdAt <= (run.finishedAt ?? .distantPast)) + let runRows = try ModelContext(container).fetch(FetchDescriptor()) + #expect(runRows.count == 1) + #expect(runRows.first?.permissionMode == AgentPermissionMode.fullAccess.rawValue) + } + @Test func v15MigratesWithoutInventingHistoricalContext() async throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) @@ -140,3 +222,22 @@ struct AgentReplayPersistenceTests { try container.mainContext.save() } } + +private actor ReplayModelCalls { + private(set) var count = 0 + func called() { count += 1 } +} + +private struct CountingReplayModel: AgentModelStreaming { + let calls: ReplayModelCalls + + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + await calls.called() + continuation.yield(.finished(.completed)) + continuation.finish() + } + } + } +} diff --git a/FrameworkTests/AgentSteeringTests.swift b/FrameworkTests/AgentSteeringTests.swift index 1c37fbfe..b7bae457 100644 --- a/FrameworkTests/AgentSteeringTests.swift +++ b/FrameworkTests/AgentSteeringTests.swift @@ -193,14 +193,17 @@ struct AgentSteeringTests { sessionContext: "host A", skillCatalog: "fixed skills", planContract: "fixed mode", toolAvailability: ["terminal_suggest": false, "future_surface_tool": false], memorySessionID: memorySessionID) + let repository = InMemoryAgentRunRepository() conversation.start( runtime: AgentRuntime( model: HangingAgentModel(), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "start", permissionMode: .askForApproval, - contextSnapshot: first)) + contextSnapshot: first), + repository: repository) conversation.composer.draft = "inspect here" conversation.submitSteeringDraft( context: SessionContext(hostName: "B", workingDirectory: "/srv"), @@ -233,15 +236,18 @@ struct AgentSteeringTests { } private func startRun(_ conversation: ConversationModel) { + let repository = InMemoryAgentRunRepository() conversation.start( runtime: AgentRuntime( model: HangingAgentModel(), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "start", permissionMode: .askForApproval - ) + ), + repository: repository ) } } diff --git a/FrameworkTests/AssistantConversationOperationsTests.swift b/FrameworkTests/AssistantConversationOperationsTests.swift index 8a1d2f85..bf41d14b 100644 --- a/FrameworkTests/AssistantConversationOperationsTests.swift +++ b/FrameworkTests/AssistantConversationOperationsTests.swift @@ -3,7 +3,7 @@ import Foundation import TermindApplication import Testing -@Suite @MainActor +@Suite(.timeLimit(.minutes(1))) @MainActor struct AssistantConversationOperationsTests { private func fixture() async throws -> ( InMemoryAgentRunRepository, ConversationModel, AssistantConversationOperations @@ -203,20 +203,265 @@ struct AssistantConversationOperationsTests { } @Test - func immediateStopCannotStartAQueuedRuntime() async { + func immediateStopAndWaitPersistsCapturedCancellationWithoutCallingProviderOrTools() async throws { let calls = AssistantModelCalls() + let tools = AssistantToolCalls() + let repository = InMemoryAgentRunRepository() let conversation = ConversationModel() + let promptID = UUID() + let context = AgentTurnContextSnapshot( + sessionContext: "captured host", skillCatalog: "captured skills", + planContract: "captured plan", toolAvailability: ["terminal_suggest": false] + ) + let tool = AnyAgentTool( + descriptor: AgentToolDescriptor( + name: "never", summary: "Never runs", inputSchema: .object([:]), + target: .local, approvalPolicy: .approve + ) + ) { invocation, _ in + await tools.called() + return AgentToolResult(callID: invocation.call.id, content: "unexpected") + } let runtime = AgentRuntime( - model: CountingAssistantModel(calls: calls), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .deny("test") })) + model: CountingAssistantModel(calls: calls), tools: [tool], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .deny("test") }), + repository: repository) conversation.start( runtime: runtime, request: AgentRunRequest( - conversationID: conversation.id, prompt: "go", permissionMode: .askForApproval)) - conversation.stop() - for _ in 0..<20 { await Task.yield() } + conversationID: conversation.id, promptID: promptID, prompt: "go", + authoredPrompt: "Go now", permissionMode: .fullAccess, + contextSnapshot: context), + repository: repository) + + await conversation.stopAndWait() + #expect(await calls.count == 0) + #expect(await tools.count == 0) #expect(!conversation.isStreaming) + #expect(conversation.runState == .cancelled) + let stored = try #require(await repository.loadConversations().first) + let run = try #require(stored.runs.first) + let snapshot = try #require(await repository.snapshot(run.id)) + #expect(snapshot.state == .cancelled) + #expect(snapshot.permissionMode == .fullAccess) + #expect(snapshot.messages.last?.id == promptID) + #expect(snapshot.messages.last?.text == "go") + #expect(snapshot.messages.last?.authoredText == "Go now") + #expect(snapshot.messages.last?.contextSnapshot == context) + #expect(snapshot.messages.last.map { $0.createdAt <= (snapshot.finishedAt ?? .distantPast) } == true) + #expect(conversation.messageRunIDs[promptID] == run.id) + #expect(conversation.runSummaries[run.id]?.state == .cancelled) + } + + @Test + func stopThenStopAndWaitJoinsTheQueuedCancellationSave() async { + let calls = AssistantModelCalls() + let repository = TerminalSaveGateRepository() + let conversation = ConversationModel() + let runtime = AgentRuntime( + model: CountingAssistantModel(calls: calls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .deny("test") }), + repository: repository) + conversation.start( + runtime: runtime, + request: AgentRunRequest( + conversationID: conversation.id, prompt: "go", permissionMode: .askForApproval), + repository: repository) + + conversation.stop() + await repository.waitForTerminalSave() + let join = JoinProbe() + let waiter = Task { @MainActor in + join.markStarted() + await conversation.stopAndWait() + join.markFinished() + } + await join.waitUntilStarted() + #expect(!join.finished) + + await repository.releaseTerminalSaves() + await waiter.value + #expect(join.finished) + #expect(await calls.count == 0) + #expect(await repository.savedTerminalState == .cancelled) + } + + @Test + func queuedStopPersistsAfterConversationPresentationIsReleased() async { + let calls = AssistantModelCalls() + let repository = TerminalSaveGateRepository() + weak var released: ConversationModel? + do { + var conversation: ConversationModel? = ConversationModel() + released = conversation + let runtime = AgentRuntime( + model: CountingAssistantModel(calls: calls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository + ) + conversation?.start( + runtime: runtime, + request: AgentRunRequest( + conversationID: conversation!.id, prompt: "go", + permissionMode: .askForApproval), + repository: repository + ) + conversation?.stop() + conversation = nil + } + + #expect(released == nil) + await repository.waitForTerminalSave() + #expect(await calls.count == 0) + await repository.releaseTerminalSaves() + await repository.waitForTerminalSaved() + } + + @Test + func directStartCancelsAndPersistsTheDisplacedQueuedOwner() async { + let oldCalls = AssistantModelCalls() + let oldRepository = TerminalSaveGateRepository() + let conversation = ConversationModel() + conversation.start( + runtime: AgentRuntime( + model: CountingAssistantModel(calls: oldCalls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: oldRepository + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "old", permissionMode: .askForApproval), + repository: oldRepository + ) + + let newCalls = AssistantModelCalls() + let newRepository = InMemoryAgentRunRepository() + conversation.start( + runtime: AgentRuntime( + model: HangingCountingAssistantModel(calls: newCalls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: newRepository + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "new", permissionMode: .askForApproval), + repository: newRepository + ) + + await oldRepository.waitForTerminalSave() + await newCalls.waitUntilCalled() + #expect(await oldCalls.count == 0) + #expect(conversation.runState == .running) + await oldRepository.releaseTerminalSaves() + await oldRepository.waitForTerminalSaved() + #expect(await oldRepository.savedTerminalState == .cancelled) + await conversation.stopAndWait() + } + + @Test + func stopAndWaitCancelsPendingApprovalAndJoinsItsTerminalSave() async { + let tools = AssistantToolCalls() + let repository = TerminalSaveGateRepository() + let conversation = ConversationModel() + let tool = AnyAgentTool( + descriptor: AgentToolDescriptor( + name: "held", summary: "Held", inputSchema: .object([:]), + target: .local, approvalPolicy: .ask + ) + ) { invocation, _ in + await tools.called() + return AgentToolResult(callID: invocation.call.id, content: "unexpected") + } + conversation.start( + runtime: AgentRuntime( + model: ApprovalRequestingAssistantModel(), tools: [tool], + approval: AgentApprovalBroker( + reviewer: nil, + manualApproval: { [weak conversation] request in + guard let conversation else { return .deny("gone") } + return await conversation.requestApproval(request) + } + ), + repository: repository + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "approve", + permissionMode: .askForApproval), + repository: repository + ) + await waitUntil { conversation.pendingApproval != nil } + + let releaseTerminalSave = Task { @MainActor in + await repository.waitForTerminalSave() + #expect(conversation.pendingApprovals.isEmpty) + await repository.releaseTerminalSaves() + } + await conversation.stopAndWait() + await releaseTerminalSave.value + + #expect(conversation.pendingApprovals.isEmpty) + #expect(await tools.count == 0) + #expect(await repository.savedTerminalState == .cancelled) + #expect(conversation.runState == .cancelled) + } + + @Test + func runningStopAndWaitDrainsOldPersistenceWithoutClobberingReplacement() async { + let oldCalls = AssistantModelCalls() + let oldRepository = TerminalSaveGateRepository() + let conversation = ConversationModel() + conversation.start( + runtime: AgentRuntime( + model: HangingCountingAssistantModel(calls: oldCalls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: oldRepository + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "old", permissionMode: .askForApproval), + repository: oldRepository + ) + await oldCalls.waitUntilCalled() + await waitUntil { conversation.runSummaries.values.contains(where: { $0.state == .running }) } + let oldRunID = try! #require(conversation.runSummaries.values.first(where: { $0.state == .running })?.id) + let reviewInvocation = AgentToolInvocation( + runID: oldRunID, + call: AgentToolCall(id: "reviewed", name: "reviewed", arguments: .object([:])) + ) + let reviewDescriptor = AgentToolDescriptor( + name: "reviewed", summary: "Reviewed", inputSchema: .object([:]), + target: .local, approvalPolicy: .ask + ) + let reviewRequest = AgentApprovalRequest( + invocation: reviewInvocation, descriptor: reviewDescriptor, userIntent: "review" + ) + conversation.handle(.toolProposed(reviewInvocation, reviewDescriptor)) + conversation.handle(.reviewStarted(reviewRequest)) + #expect(conversation.runState == .reviewing) + + let replacementCalls = AssistantModelCalls() + let replacementRepository = InMemoryAgentRunRepository() + let replaceWhileOldSaveIsBlocked = Task { @MainActor in + await oldRepository.waitForTerminalSave() + conversation.start( + runtime: AgentRuntime( + model: HangingCountingAssistantModel(calls: replacementCalls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: replacementRepository + ), + request: AgentRunRequest( + conversationID: conversation.id, prompt: "new", permissionMode: .askForApproval), + repository: replacementRepository + ) + await replacementCalls.waitUntilCalled() + await waitUntil { conversation.runState == .running } + await oldRepository.releaseTerminalSaves() + } + + await conversation.stopAndWait() + await replaceWhileOldSaveIsBlocked.value + #expect(conversation.runState == .running) + #expect(conversation.toolCards.first(where: { $0.id == reviewInvocation.id })?.state == .interrupted) + #expect(await oldRepository.savedTerminalState == .cancelled) + await conversation.stopAndWait() } @Test func deletingRemovesDurableConversation() async throws { @@ -309,10 +554,98 @@ private actor RejectingConversationRepository: AgentRunPersisting { } private actor AssistantModelCalls { + var count = 0 + private var waiters: [CheckedContinuation] = [] + + func called() { + count += 1 + let pending = waiters + waiters.removeAll() + for waiter in pending { waiter.resume() } + } + + func waitUntilCalled() async { + guard count == 0 else { return } + await withCheckedContinuation { waiters.append($0) } + } +} + +private actor AssistantToolCalls { var count = 0 func called() { count += 1 } } +@MainActor +private final class JoinProbe { + private(set) var finished = false + private var started = false + private var startWaiters: [CheckedContinuation] = [] + + func markStarted() { + started = true + let pending = startWaiters + startWaiters.removeAll() + for waiter in pending { waiter.resume() } + } + + func waitUntilStarted() async { + guard !started else { return } + await withCheckedContinuation { startWaiters.append($0) } + } + + func markFinished() { finished = true } +} + +private actor TerminalSaveGateRepository: AgentRunPersisting { + private var terminalSaveEntered = false + private var terminalSaveWaiters: [CheckedContinuation] = [] + private var terminalReleaseWaiters: [CheckedContinuation] = [] + private var terminalSavedWaiters: [CheckedContinuation] = [] + private(set) var savedTerminalState: AgentRunState? + + func save(_ snapshot: AgentRunSnapshot) async { + guard snapshot.state.isTerminal else { return } + terminalSaveEntered = true + let entered = terminalSaveWaiters + terminalSaveWaiters.removeAll() + for waiter in entered { waiter.resume() } + await withCheckedContinuation { terminalReleaseWaiters.append($0) } + savedTerminalState = snapshot.state + let saved = terminalSavedWaiters + terminalSavedWaiters.removeAll() + for waiter in saved { waiter.resume() } + } + + func waitForTerminalSave() async { + guard !terminalSaveEntered else { return } + await withCheckedContinuation { terminalSaveWaiters.append($0) } + } + + func releaseTerminalSaves() { + let pending = terminalReleaseWaiters + terminalReleaseWaiters.removeAll() + for waiter in pending { waiter.resume() } + } + + func waitForTerminalSaved() async { + guard savedTerminalState == nil else { return } + await withCheckedContinuation { terminalSavedWaiters.append($0) } + } + + func record(_ event: AgentEvent, runID: UUID) {} + func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} + func loadConversations() -> [StoredAgentConversation] { [] } + func deleteConversation(_ id: UUID) {} + func setTitle(_ title: String, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} + func truncateConversation( + _ id: UUID, fromUserMessageID messageID: UUID, + replacingWith message: AgentTranscriptMessage + ) {} + func createConversation(_ conversation: StoredAgentConversation) {} +} + private struct CountingAssistantModel: AgentModelStreaming { let calls: AssistantModelCalls func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { @@ -325,3 +658,33 @@ private struct CountingAssistantModel: AgentModelStreaming { } } } + +private struct HangingCountingAssistantModel: AgentModelStreaming { + let calls: AssistantModelCalls + + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + await calls.called() + continuation.yield(.textDelta("working")) + try? await Task.sleep(for: .seconds(30)) + continuation.finish() + } + } + } +} + +private struct ApprovalRequestingAssistantModel: AgentModelStreaming { + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + continuation.yield(.toolCallDelta(id: "held", name: "held", arguments: "{}")) + continuation.yield(.finished(.toolCalls)) + continuation.finish() + } + } +} + +@MainActor +private func waitUntil(_ condition: @escaping @MainActor () -> Bool) async { + while !condition() { await Task.yield() } +} diff --git a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift index b5432284..eaea75df 100644 --- a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift +++ b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift @@ -498,7 +498,9 @@ public final class AssistantExecutionController { priorMessages: conversation.pendingPriorMessages, authorizationEvidence: GuardianEvidence.collect(from: conversation.transcript), sessionContext: frozenContext.sessionContext, contextSnapshot: frozenContext - )) + ), + repository: agentRunRepository + ) logger.info( "agent.run.started", metadata: [ diff --git a/Frameworks/TermindApplication/Agent/ConversationModel.swift b/Frameworks/TermindApplication/Agent/ConversationModel.swift index 74ea0208..f0312537 100644 --- a/Frameworks/TermindApplication/Agent/ConversationModel.swift +++ b/Frameworks/TermindApplication/Agent/ConversationModel.swift @@ -797,9 +797,48 @@ public final class ConversationModel: Identifiable { private var contextEstimateNeedsPublish = false private let contextEstimatePublishInterval: Duration public private(set) var contextEstimatePublishCount = 0 - private var runtime: AgentRuntime? - private var eventTask: Task? - private var runtimeGeneration: UInt64 = 0 + private final class AgentRunOwner { + enum Phase { + case queued + case starting + case started + case cancelledBeforeStart(AgentRunSnapshot) + case finished + } + + let generation: UInt64 + let runID: UUID + let startedAt: Date + let runtime: AgentRuntime + let request: AgentRunRequest + let repository: any AgentRunPersisting + var phase: Phase = .queued + var cancellationRequested = false + var prestartPersistence: Task? + var consumer: Task? + + init( + generation: UInt64, runtime: AgentRuntime, request: AgentRunRequest, + repository: any AgentRunPersisting + ) { + self.generation = generation + runID = UUID() + startedAt = .now + self.runtime = runtime + self.request = request + self.repository = repository + } + + deinit { + consumer?.cancel() + let runtime = runtime + Task { await runtime.cancel() } + } + } + + private var nextRunGeneration: UInt64 = 0 + private var activeRunOwner: AgentRunOwner? + private var runOwners: [UInt64: AgentRunOwner] = [:] private var agentPreparationID: UUID? private var cancelAgentPreparation: (() -> Task)? private var approvalWaiters: [UUID: CheckedContinuation] = [:] @@ -2004,36 +2043,128 @@ public final class ConversationModel: Identifiable { await cancelPendingAgentPreparation()?.value } - public func start(runtime: AgentRuntime, request: AgentRunRequest) { + public func start( + runtime: AgentRuntime, request: AgentRunRequest, + repository: any AgentRunPersisting + ) { + if let displaced = activeRunOwner { + requestCancellation(of: displaced) + stopPresentation(owner: displaced) + } activeContextSnapshot = request.contextSnapshot if let index = transcript.firstIndex(where: { $0.id == request.promptID }) { transcript[index].contextSnapshot = request.contextSnapshot contextEstimateTracker.upsert(transcript[index]) } - self.runtime = runtime reportedRunState = .running - runtimeGeneration &+= 1 - let generation = runtimeGeneration - eventTask?.cancel() - eventTask = Task { [weak self] in - guard !Task.isCancelled, self?.runtimeGeneration == generation else { return } - let events = await runtime.start(request) - guard !Task.isCancelled, self?.runtimeGeneration == generation else { - await runtime.cancel() + nextRunGeneration &+= 1 + let owner = AgentRunOwner( + generation: nextRunGeneration, runtime: runtime, request: request, + repository: repository + ) + activeRunOwner = owner + runOwners[owner.generation] = owner + let generation = owner.generation + owner.consumer = Task { [weak self, weak owner] in + guard let phase = owner?.phase else { return } + switch phase { + case .cancelledBeforeStart: + await owner?.prestartPersistence?.value + self?.finishRunOwner(generation: generation) + return + case .queued: + owner?.phase = .starting + case .starting, .started, .finished: return } - for await event in events { - guard !Task.isCancelled, let self, - self.runtimeGeneration == generation - else { - await runtime.cancel() - return - } - handle(event) + + let events = await runtime.start(request) + owner?.phase = .started + if Task.isCancelled || owner == nil || owner?.cancellationRequested == true { + await runtime.cancel() } + for await event in events { + guard let self, let owner, self.activeRunOwner === owner else { continue } + self.handle(event) + } + self?.finishRunOwner(generation: generation) } } + private func finishRunOwner(generation: UInt64) { + guard let owner = runOwners[generation] else { return } + owner.phase = .finished + owner.prestartPersistence = nil + owner.consumer = nil + runOwners[generation] = nil + if activeRunOwner === owner { activeRunOwner = nil } + } + + private func cancelledSnapshot(for owner: AgentRunOwner, finishedAt: Date) -> AgentRunSnapshot { + let request = owner.request + var messages = AgentOrphanedToolResultRepair.apply( + AgentUnansweredToolCallRepair.apply( + AgentSecretLifecycle.sanitizeHistoricalMessages(request.priorMessages) + )) + let prompt = AgentTranscriptMessage( + id: request.promptID, role: .user, text: request.prompt, + authoredText: request.authoredPrompt, images: request.promptImages, + contextAttachments: request.promptContextAttachments, + contextSnapshot: request.contextSnapshot, + createdAt: transcript.first(where: { $0.id == request.promptID })?.createdAt + ?? owner.startedAt + ) + if let index = messages.firstIndex(where: { $0.id == prompt.id }) { + if messages[index].contextSnapshot == nil { + messages[index].contextSnapshot = prompt.contextSnapshot + } + } else { + messages.append(prompt) + } + return AgentRunSnapshot( + id: owner.runID, conversationID: request.conversationID, + state: .cancelled, permissionMode: request.permissionMode, + messages: messages, startedAt: owner.startedAt, finishedAt: finishedAt + ) + } + + private func requestCancellation(of owner: AgentRunOwner) { + guard !owner.cancellationRequested else { return } + owner.cancellationRequested = true + switch owner.phase { + case .queued: + let snapshot = cancelledSnapshot(for: owner, finishedAt: .now) + owner.phase = .cancelledBeforeStart(snapshot) + let repository = owner.repository + owner.prestartPersistence = Task { + try? await repository.save(snapshot) + } + if activeRunOwner === owner { projectCancelledRun(snapshot) } + case .starting, .started: + Task { await owner.runtime.cancel() } + case .cancelledBeforeStart, .finished: + break + } + } + + private func projectCancelledRun(_ snapshot: AgentRunSnapshot) { + guard snapshot.conversationID == id, + let prompt = snapshot.messages.last(where: { $0.role == .user }) + else { return } + var summary = AgentRunSummary( + id: snapshot.id, state: .cancelled, startedAt: snapshot.startedAt, + finishedAt: snapshot.finishedAt + ) + if messageRunIDs[prompt.id] == nil + || adoptStagedReplacementRun(promptID: prompt.id, summary: &summary) + { + messageRunIDs[prompt.id] = snapshot.id + } + runSummaries[snapshot.id] = summary + reportedRunState = .cancelled + updatedAt = snapshot.finishedAt ?? .now + } + /// Records a request that could not construct a runtime, such as a missing /// model or credential, using the same durable run failure UI as a provider /// error that arrived after dispatch. @@ -2094,40 +2225,39 @@ public final class ConversationModel: Identifiable { /// Retains the ordered event consumer until the cancellation snapshot is durable. public func stopAndWait() async { - await cancelPendingAgentPreparationAndWait() - guard let runtime else { - stop() - return - } - let consumer = eventTask - let generation = runtimeGeneration - await runtime.cancel() - await consumer?.value - guard runtimeGeneration == generation else { return } - stop() + let preparation = cancelPendingAgentPreparation() + let owners = Array(runOwners.values) + let activeOwner = activeRunOwner + if let activeOwner { requestCancellation(of: activeOwner) } + stopPresentation(owner: activeOwner) + await preparation?.value + for owner in owners { await owner.consumer?.value } } public func stop() { cancelPendingAgentPreparation() - runtimeGeneration &+= 1 + let owner = activeRunOwner + if let owner { requestCancellation(of: owner) } + stopPresentation(owner: owner) + } + + private func stopPresentation(owner: AgentRunOwner?) { + let stoppedRunID = activeRunID + if let owner, activeRunOwner === owner { activeRunOwner = nil } settleStreamingPresentation(for: activeRunID) publishAllStagedTools() - for index in toolCards.indices where toolCards[index].state == .running { + if let stoppedRunID { retireStoppedRunInteractions(runID: stoppedRunID) } + for index in toolCards.indices + where toolCards[index].invocation.runID == stoppedRunID + && !toolCards[index].state.isTerminal + { toolCards[index].state = .interrupted } - eventTask?.cancel() - eventTask = nil - // Read the runtime *before* releasing it. `Task { await runtime?… }` - // resolves the property when the task runs, which is after this method - // has already set it to nil — so the run kept executing tools with - // nobody listening. - if let runtime { Task { await runtime.cancel() } } - runtime = nil if !reportedRunState.isTerminal { reportedRunState = .cancelled - // The event consumer was just cancelled, so it cannot receive the - // runtime's eventual finish event. Freeze the visible elapsed time - // here instead of letting a stopped run keep counting forever. + // Runtime terminal persistence can finish after the presentation + // stops. Freeze the visible elapsed time without cancelling its + // event drain, which is the durable join boundary. if let activeRunID, var summary = runSummaries[activeRunID] { summary.state = .cancelled summary.finishedAt = .now @@ -2138,6 +2268,32 @@ public final class ConversationModel: Identifiable { returnAllSteeringToDraft() } + private func retireStoppedRunInteractions(runID: UUID) { + let approvalIDs = pendingApprovals.compactMap { request in + request.invocation.runID == runID ? request.id : nil + } + for requestID in approvalIDs { + guard let index = pendingApprovals.firstIndex(where: { $0.id == requestID }) else { + continue + } + let request = pendingApprovals.remove(at: index) + updateToolCard(request.invocation.id) { card in + if !card.state.isTerminal { card.state = .interrupted } + } + } + + let reviewIDs = reviewing.filter { invocationID in + toolCards.first(where: { $0.id == invocationID })?.invocation.runID == runID + || stagedToolCards[invocationID]?.invocation.runID == runID + } + reviewing.subtract(reviewIDs) + for invocationID in reviewIDs { + updateToolCard(invocationID) { card in + if !card.state.isTerminal { card.state = .interrupted } + } + } + } + /// Queues the draft for the running agent instead of starting a second run. /// /// Nothing reaches `messages` or `transcript` here. A bubble posted at the @@ -2148,7 +2304,8 @@ public final class ConversationModel: Identifiable { /// really saw it. public func submitSteeringDraft(context: SessionContext = .none, terminalSuggestionsAvailable: Bool = false) { let text = composer.draft.trimmingCharacters(in: .whitespacesAndNewlines) - guard canSendDraft, let runtime else { return } + guard canSendDraft, let owner = activeRunOwner else { return } + let runtime = owner.runtime let composed = composeUserTurn(text) var snapshot = activeContextSnapshot?.withSessionContext(context.modelContext) if snapshot != nil { @@ -2170,6 +2327,7 @@ public final class ConversationModel: Identifiable { composer.draft = "" Task { [weak self] in guard await runtime.steer(pending.message) == false else { return } + guard self?.activeRunOwner === owner else { return } // The run ended between the keystroke and this hop. Everything // still queued is undeliverable, not just this one — and returning // the whole queue at once is also what keeps the words in the order @@ -2206,22 +2364,49 @@ public final class ConversationModel: Identifiable { /// moves the card's state, and enqueueing happens here — on the path that /// holds the continuation and can therefore actually answer. public func requestApproval(_ request: AgentApprovalRequest) async -> AgentApprovalDecision { - // The approval callback can arrive before the channel drains toolProposed. - // Stage the same identity now so a direct user response has a card to update. - stageTool(request.invocation, descriptor: request.descriptor) - // Inserted by when the call was *planned*, not by when it got here. A - // parallel batch races to this queue, so appending put the cards in - // whichever order the scheduler happened to resolve — the same two - // calls could be presented either way round on two runs. Planning is - // sequential, so `createdAt` is the model's own order, and asking about - // the second thing first is a needless surprise in the one place the - // user is being asked to authorize something. - let index = pendingApprovals.firstIndex { - $0.invocation.createdAt > request.invocation.createdAt + await withTaskCancellationHandler { + await withCheckedContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(returning: Self.cancelledApprovalDecision) + return + } + // The approval callback can arrive before the channel drains + // toolProposed. Stage the same identity now so a direct user + // response has a card to update. + stageTool(request.invocation, descriptor: request.descriptor) + // Inserted by when the call was planned, not by when it got + // here. Parallel calls race to this queue, while planning is + // sequential and supplies the stable user-facing order. + let index = pendingApprovals.firstIndex { + $0.invocation.createdAt > request.invocation.createdAt + } + pendingApprovals.insert(request, at: index ?? pendingApprovals.endIndex) + setToolState(request.invocation.id, .waitingForApproval) + approvalWaiters[request.id] = continuation + } + } onCancel: { + Task { @MainActor [weak self] in + self?.cancelApproval(request.id) + } } - pendingApprovals.insert(request, at: index ?? pendingApprovals.endIndex) - setToolState(request.invocation.id, .waitingForApproval) - return await withCheckedContinuation { approvalWaiters[request.id] = $0 } + } + + private static var cancelledApprovalDecision: AgentApprovalDecision { + .deny(String(localized: "The user denied this action.")) + } + + private func cancelApproval(_ requestID: UUID) { + guard let index = pendingApprovals.firstIndex(where: { $0.id == requestID }) else { + approvalWaiters.removeValue(forKey: requestID)?.resume( + returning: Self.cancelledApprovalDecision) + return + } + let request = pendingApprovals.remove(at: index) + updateToolCard(request.invocation.id) { card in + if !card.state.isTerminal { card.state = .interrupted } + } + approvalWaiters.removeValue(forKey: request.id)?.resume( + returning: Self.cancelledApprovalDecision) } public func respondToApproval(_ requestID: UUID, allow: Bool) { diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 511cce54..0b5d0ab7 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -310,17 +310,20 @@ struct MobileAssistantTests { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() assistant.library.insertAndSelect(conversation) conversation.start( runtime: AgentRuntime( model: MobileDelayedAgentModel(), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "start", permissionMode: .askForApproval, contextSnapshot: .init( - toolAvailability: ["terminal_suggest": true, "future_surface_tool": false])) + toolAvailability: ["terminal_suggest": true, "future_surface_tool": false])), + repository: repository ) conversation.composer.draft = "Inspect again" diff --git a/TFMTests/AgentApprovalQueueTests.swift b/TFMTests/AgentApprovalQueueTests.swift index c701d50d..46e8d979 100644 --- a/TFMTests/AgentApprovalQueueTests.swift +++ b/TFMTests/AgentApprovalQueueTests.swift @@ -135,6 +135,7 @@ struct AgentApprovalQueueTests { func twoApprovalsInOneTurnQueueAndAreBothAnswered() async { let log = CallLog() let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() let runtime = AgentRuntime( model: QueueScriptedModel(turnsCalling(["first", "second"])), tools: [gatedTool("first", log: log), gatedTool("second", log: log)], @@ -144,14 +145,16 @@ struct AgentApprovalQueueTests { guard let conversation else { return .deny("gone") } return await conversation.requestApproval(request) } - ) + ), + repository: repository ) conversation.start( runtime: runtime, request: AgentRunRequest( conversationID: conversation.id, prompt: "go", permissionMode: .askForApproval - )) + ), + repository: repository) await wait { conversation.pendingApprovals.count == 2 } // Both are waiting, but only the head is presented: authorizing three @@ -184,6 +187,7 @@ struct AgentApprovalQueueTests { func denyingTheHeadLeavesTheNextRequestAnswerable() async { let log = CallLog() let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() let runtime = AgentRuntime( model: QueueScriptedModel(turnsCalling(["denied", "allowed"])), tools: [gatedTool("denied", log: log), gatedTool("allowed", log: log)], @@ -193,14 +197,16 @@ struct AgentApprovalQueueTests { guard let conversation else { return .deny("gone") } return await conversation.requestApproval(request) } - ) + ), + repository: repository ) conversation.start( runtime: runtime, request: AgentRunRequest( conversationID: conversation.id, prompt: "go", permissionMode: .askForApproval - )) + ), + repository: repository) await wait { conversation.pendingApprovals.count == 2 } guard let firstID = conversation.pendingApproval?.id else { diff --git a/TFMTests/ChatComposerStateTests.swift b/TFMTests/ChatComposerStateTests.swift index d4bb3185..bc6b46d9 100644 --- a/TFMTests/ChatComposerStateTests.swift +++ b/TFMTests/ChatComposerStateTests.swift @@ -769,6 +769,7 @@ struct ChatComposerStateTests { @Test func immediatelyCompletedRuntimeStillReachesTheConversation() async { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() let runtime = AgentRuntime( model: ImmediateConversationAgentModel(turns: [ [ @@ -777,7 +778,8 @@ struct ChatComposerStateTests { ] ]), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ) conversation.start( @@ -785,7 +787,8 @@ struct ChatComposerStateTests { request: AgentRunRequest( conversationID: conversation.id, prompt: "hello", permissionMode: .askForApproval - )) + ), + repository: repository) await waitForTerminal(conversation) #expect(conversation.runState == .completed) @@ -831,9 +834,11 @@ struct ChatComposerStateTests { return AgentToolResult(callID: invocation.call.id, content: "host-a") } let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() let runtime = AgentRuntime( model: model, tools: [tool], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ) conversation.start( @@ -841,7 +846,8 @@ struct ChatComposerStateTests { request: AgentRunRequest( conversationID: conversation.id, prompt: "inspect", permissionMode: .askForApproval - )) + ), + repository: repository) await waitForTerminal(conversation) #expect(await executions.value == 1) @@ -883,6 +889,7 @@ struct ChatComposerStateTests { AgentToolResult(callID: invocation.call.id, content: "host-a") } let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("inspect") // The prompt travels on the request, so it must not also be replayed. #expect(conversation.pendingPriorMessages.isEmpty) @@ -890,12 +897,14 @@ struct ChatComposerStateTests { conversation.start( runtime: AgentRuntime( model: model, tools: [tool], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "inspect", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) @@ -932,6 +941,7 @@ struct ChatComposerStateTests { return AgentToolResult(callID: invocation.call.id, content: "never") } let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("wait for me") conversation.start( runtime: AgentRuntime( @@ -942,12 +952,14 @@ struct ChatComposerStateTests { ] ]), tools: [tool], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "wait for me", permissionMode: .askForApproval - ) + ), + repository: repository ) await probe.waitUntilStarted() #expect(await probe.started) @@ -970,6 +982,7 @@ struct ChatComposerStateTests { @Test func timelineInterleavesToolCardsWithTheMessagesTheyBelongTo() async { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("inspect") conversation.start( runtime: AgentRuntime( @@ -995,12 +1008,14 @@ struct ChatComposerStateTests { AgentToolResult(callID: invocation.call.id, content: "host-a") } ], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "inspect", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) @@ -1312,6 +1327,7 @@ struct ChatComposerStateTests { @Test func finishedReasoningOnlyAnswerLeavesNoWorkedHistory() async { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("compare") conversation.start( runtime: AgentRuntime( @@ -1323,12 +1339,14 @@ struct ChatComposerStateTests { ] ]), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "compare", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) @@ -1435,6 +1453,7 @@ struct ChatComposerStateTests { @Test func finishedReasoningDisappearsWhileToolsMergeAcrossProviderTurns() async throws { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("inspect both") conversation.start( runtime: AgentRuntime( @@ -1470,12 +1489,14 @@ struct ChatComposerStateTests { AgentToolResult(callID: invocation.call.id, content: "ok") } ], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "inspect both", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) @@ -1501,6 +1522,7 @@ struct ChatComposerStateTests { @Test func providerTurnsWithoutReasoningShareOneRunScopedToolBatch() async throws { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() conversation.appendUserTurn("inspect four") conversation.start( runtime: AgentRuntime( @@ -1531,12 +1553,14 @@ struct ChatComposerStateTests { AgentToolResult(callID: invocation.call.id, content: "ok") } ], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "inspect four", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) @@ -1898,6 +1922,7 @@ struct ChatComposerStateTests { weak var leaked: ConversationModel? do { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() leaked = conversation conversation.appendUserTurn("hello") conversation.start( @@ -1915,12 +1940,14 @@ struct ChatComposerStateTests { return await conversation.requestApproval(request) } ), + repository: repository, userInteraction: WeakAgentUserInteraction(conversation) ), request: AgentRunRequest( conversationID: conversation.id, prompt: "hello", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) } @@ -2023,6 +2050,7 @@ struct ChatComposerStateTests { @Test func aFinishedRunNotifiesItsOwnerExactlyOnce() async { let conversation = ConversationModel() + let repository = InMemoryAgentRunRepository() var finishes = 0 conversation.runDidFinish = { finishes += 1 } conversation.appendUserTurn("hello") @@ -2034,12 +2062,14 @@ struct ChatComposerStateTests { ] ]), tools: [], - approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }) + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: repository ), request: AgentRunRequest( conversationID: conversation.id, prompt: "hello", permissionMode: .askForApproval - ) + ), + repository: repository ) await waitForTerminal(conversation) await wait { finishes > 0 } From 3d53be4166c9c66d48519161d483a0d2da35c5e4 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 14:15:52 +0800 Subject: [PATCH 15/30] Document Assistant runtime cancellation ownership --- .../supporting-system-directories.json | 50 ++-- .../concepts/agent-runtime-and-assistant.json | 246 ++++++++++++------ openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 70 ++--- .../supporting-system-directories.md | 18 +- .../concepts/agent-runtime-and-assistant.md | 36 ++- 6 files changed, 256 insertions(+), 168 deletions(-) diff --git a/openwiki/.claims/architecture/supporting-system-directories.json b/openwiki/.claims/architecture/supporting-system-directories.json index 26a9203c..40491e82 100644 --- a/openwiki/.claims/architecture/supporting-system-directories.json +++ b/openwiki/.claims/architecture/supporting-system-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:2711b0e7d443185dd21a3d96c2cb80f84a5e2149a5a6fc64fda5363afcbc86be", + "pageVersion": "sha256:bc62050f5799c6e7764ff9d08a0e721dda371eb74b7b8342346c59f747abb3b5", "claims": [ { "id": "claim_8809151c43594803b83cf0451d849112", @@ -25,16 +25,28 @@ "statement": "AgentKit separates its dependency-free core product from the optional AgentKitScrubber web adapter, package tests and localized runtime resources.", "evidence": [ { - "resource": "repo://AgentKit/Package.swift", - "version": "repo-file-v1:sha256:1c36e29cab099a934aefdd13b4b69aa25d2d1112ceebe79814bf76f82a8119eb" + "resource": "repo://AGENTS.md#L16-L18", + "version": "repo-lines-v1:sha256:79d9222951afd1cde2967f4987b4aa7b4347f1510f1fc4d1141010690b9c8bff:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGI2ZWFiNGFjNWIwNmRiMjY5NmQ0ZDUzZTJlN2YwZDE3MzhmMWI3NTAyY2UyZWY1YjcwMzIzZjg3NjdjYTllNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRkZTk1MTMwNGUxYWVlNjRiM2IyYmY3NWZkMzI2YjAyZGM0OTE5YjlhODM4MGZjZTY1YTAyY2VjOTE3MzdjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWM1NjRmZjMwNzY2N2QwZTgzMGJkYjkxYmY1MDYwY2FlODNhMjc2NzRkNTU5NWNiYTU0ZGMwNmQzNmE2NWMzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTVkOTdmMmUwNzM5MTAzZjc1MTA1MjljNjA1Zjg0OTIzMTdkNDJmNDA5NzEwNGFmMmRkMjBiOGUyNmZhMmNmNyJ9" }, { - "resource": "repo://AgentKit/Scripts/build-localizations.sh", - "version": "repo-file-v1:sha256:4aa30ad6ba5f603cb6a9273caff26c795dd289b3975f073135aec9bb0dfbd7b4" + "resource": "repo://AGENTS.md#L26-L30", + "version": "repo-lines-v1:sha256:c5ceedc410ca6add0a0f1312c3bb732b150078e554692f149024536244315cbe:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGZmNDZjMmQzNzgwODk3OGE4MzcyMDg4MDIzOGEzMjEzM2U3OTlkMmJlNGQwMWQzYjUyN2IxNjg2NThiMWIxMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRiZGQxZGM1ODQ0ZmFhNTZhNGM4NmQ5ZjkyNzA2OGJmM2E4ODM3MzBiNDRjYzFiNzkxOWE5MjFmNmM5Nzc3YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjQyY2VmZTE0YWM1YTBmNTZjNDQwMzQxMjk5NWEwMjBmYmIxZmNlYjkxNzM0ZWJmOWU2MmJlYTJjNDU1Y2IxNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzMwMDY2ODE5MWExZWEzYjYyNTBkMTlhYTEwZjIxMjMwMGIxZGMwZDY2YjkxNGFkOTEyOGE2YWVlNzk1YmM3NiJ9" }, { - "resource": "repo://AgentKit/Sources/AgentKitScrubber/ScrubberWebClient.swift#L5-L42", - "version": "repo-lines-v1:sha256:52b4ba668c4d518aadf28ba19616de10ed9d3cec835ac363425edfe56c7974b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4MjQ0NjI3Yjc0YzgwZTgxZmQxNzBkMWE0Yzg2NzQ2YmUyNjlmYTU1OTliZjllNThjMGY1MzdiZmY5N2JiMWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYyMDZmNDQ4MzJkNjEzY2VkNTAzZDU1MDYyMjQ2NWI0YjkxMTE3OWUyNzc4OTZiYmQ1NzZmY2U3NjRmODdkYTgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAyNTM1ZGM2M2FjMjAzMmZkMzNjOGMzODkwYjI3ZTgzZWM1M2U3MzQzNjIyMTA4NjBjNWEzNjUyZThlOGJlNzIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyMWY5MDkxYmZiMTcyOGFhMjM1YThkODRkMDFlNGU0ODMyZDJlNzQ0NDNiMWYxZTM2NDU0ZmNhYTY5MGFkZWIifQ" + "resource": "repo://AGENTS.md#L44-L46", + "version": "repo-lines-v1:sha256:5e69d5ddf8b0181f834edfe8f463b1ddc71ce0c0c26beaa6f4dc0cb63b7f1c67:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiN2ViNmVkNjM3YmU2ODNjNzYxMmY2NzdlYTllMWE1NmI5NjU5OWU3NzJmNmQ3NjkwYjQyMzc0MGNkMTRhZmZmNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODQ1MThlZDQyNWZhZWNhNTgxNGExYzEwMTU0ZjEzMjgxOGViM2VjMGI2ZTgzOThkZjQ5NGI4YWRmN2MzYTI0NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjZhNzFhZDgxY2FlOGI3MGJiZDYwMzkxNzllYTExZmU0OGExODY0YzdmMDcwNzU2MDQwYWJlOTYxNmVhNmMyYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGVkYzQzMzI5OGFkNTgxMjllZTBhODg1MmMxM2Y1MjkyMGRhYWY5NGE4YzY0YTQ4NzFlZWVmNDZlMTMxNWVkMiJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L1-L44", + "version": "repo-lines-v1:sha256:bcbe46098da0a6bb96dde4cdb2f41c4e6254c4e961c6296ccbb54a27e25e6c82:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhYTFjM2NhNjYxZTJmMDljMTdkNDY3ZmE5ZDc0Y2UyNGYzZDkyNTVlYWRlMDQ0ODAzODU3YTU2ZTEyNzhlMmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRlMWE2YzQ3YTk2MDZmMzk4OTVhYzA3ODZjMDhiZWM2NzFjYzY0ZDljMjllNjhiZmFjMWYzNjI1ZTI4OGZlZTYifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.pbxproj#L2923-L2932", + "version": "repo-lines-v1:sha256:f22fbfa4d5570d9fe8bf74baf50551b7bfd6f451a59d19c595a7b0f212353726:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzYjQ1NmZhZTIzNTk4ZGIzZDdlMzFlMjc4YjI5MjFiMjhkNzMyNDQ0YWVhNjg3MWJhNmEzYWVlYjZhNzE2NjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2MGI4OGEwNWQ1YjUzODQ3NjAyMjliZTYwNTcyZmU2MmIxYzI5Y2RjNTdlZGI1ZTY5NTFkYWE0ZmQ0NmM4MDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFjZTY5Njc0MzFiNGQwZmEwMTVjNDJiYTc0M2JkNDljYWZlMzU0M2JiY2M2OWI4ZTZkOGZjOGFkMjFlMjQwNWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjMDQ0NzBmNTdjMjg5ZTUwOTA2ZDk5M2EwOWFhZDc2YTY1MGZiODdmYmQ2ODBiNWM0OGE4ZTRiMTM2N2MxMDgifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" } ] }, @@ -43,24 +55,20 @@ "statement": "AgentKit's core separates runtime/provider/security/MCP responsibilities and supplies built-in tool families for planning/tasks, scratch files, skills/memory, user interaction and web access; Termind host tools are supplied externally.", "evidence": [ { - "resource": "repo://AgentKit/Sources/AgentKit/MCP/MCPClient.swift", - "version": "repo-file-v1:sha256:aed93434f1924318015810fafd1b9b3ce15cad1212fcc84fc1d1e72c5ab7725c" + "resource": "repo://AGENTS.md#L16-L18", + "version": "repo-lines-v1:sha256:79d9222951afd1cde2967f4987b4aa7b4347f1510f1fc4d1141010690b9c8bff:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGI2ZWFiNGFjNWIwNmRiMjY5NmQ0ZDUzZTJlN2YwZDE3MzhmMWI3NTAyY2UyZWY1YjcwMzIzZjg3NjdjYTllNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRkZTk1MTMwNGUxYWVlNjRiM2IyYmY3NWZkMzI2YjAyZGM0OTE5YjlhODM4MGZjZTY1YTAyY2VjOTE3MzdjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWM1NjRmZjMwNzY2N2QwZTgzMGJkYjkxYmY1MDYwY2FlODNhMjc2NzRkNTU5NWNiYTU0ZGMwNmQzNmE2NWMzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTVkOTdmMmUwNzM5MTAzZjc1MTA1MjljNjA1Zjg0OTIzMTdkNDJmNDA5NzEwNGFmMmRkMjBiOGUyNmZhMmNmNyJ9" }, { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift", - "version": "repo-file-v1:sha256:dd5e6ea3dd365910d2a30a88da9029bd972a547b511503e9665857da494271c3" - }, - { - "resource": "repo://AgentKit/Sources/AgentKit/Security/AgentSecurity.swift", - "version": "repo-file-v1:sha256:efd9b3e1b6c1e46095286e26371b2a16a8873cabfc6b755f4e500541725ebe7e" - }, - { - "resource": "repo://AgentKit/Sources/AgentKit/Tools/AgentToolCatalog.swift#L3-L87", - "version": "repo-lines-v1:sha256:e60d96794abf3e3a386b20497dbf7fec55f57322d0990a4dace69a920df6e4c0:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0YzNmZmM3NDEwZmU0M2UwYTgzNDk1YTJmOTFhZDhjZDc0NzRlYjBjMDkyZmZmZjMwZTUyZWRhZjczNzZjNTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjYjc4YTBhYmU3YWE5YTA1YmY5NDJhYWZhMmQ0Y2Y5NjQ0ZTVlZDc4MjAwYTcyZTYxY2EzMDM1YmU5ZTc2ODAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExOGJmY2MwNWRhNWVkOWI1NWRjNTcyNWE0ZmNkYWVjNWMyNTc1ZmRmNGNlMjJhMTk5MDliMWIzZjRkZTgxZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjczZjlmOGMyNTYyM2ZkYzAwNTEzNGIzYzFiZmRhM2JiNWRmYjgyZTE0NzE3NjQyNTBmMDNhMzI2YTNiMWY3OGQifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L1-L68", + "version": "repo-lines-v1:sha256:8ccfafe881aacc3f7232d30cef9c97588787771dd925d726b5591efa15c92021:eyJzZWxlY3RlZExpbmVDb3VudCI6NjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift#L5-L68", - "version": "repo-lines-v1:sha256:8ec2078dc1ab7a5f6087ab497ecb18aa1b47942cd6213e8f611f6ee6b14c1d26:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMDBjMjE3MTcwZWFmOGZmOGRmMTgyYzc0Y2FkZDY2YjQ0NWFiOTZiYTVmY2YzOWYyZGE0MjM3YzdmYTA3ZDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOGJkYjA3YzQ1MDc0ZDQ1OGM1ZTE2N2I2YmNiYzNkNDQ3M2E1NzNlZTExYzQyMzI0NjBjMmIyODZmNmRiNDgifQ" + "version": "repo-lines-v1:sha256:7dcbf34ac1f3456f7329f03d3b3806e5d477bb215f9c9ec4061fa764fdea5b50:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2NTgzYWUxYWM2NTU4YTlmNGQ0MTYyYzcxZDJjMTk4OTE3OWI2MDljYWE0MDg0OTMzYjZlY2E5YWEzYWU1ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0NmY4Y2RkMDA0NDk4MWZkNzI0N2EwYzg5MzE2ODM0ZDNlZGFjOWU1MTAwNThkNGMwNGNhM2RkODczYzFhYTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0YjdlYTY2ZmJmMGQyOWM5MTFmYzIzMDY4MTRjNWRjNDQ5ZTY2NDlmM2FkYWE0ZjRlYjI5NWJmZDk2MzM3MzcifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" } ] }, @@ -237,6 +245,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T06:11:02.802Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index 1be7cea8..e431b785 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:46c6736cc2e227449885be7dd7fbf51a7b98259f7509c85a334881cc20382e90", + "pageVersion": "sha256:d3bcf7c3b342b7c7e0a24bff200d9de3fd902e66a05ac6d1f8ff1046b23948d5", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -11,8 +11,8 @@ "version": "repo-lines-v1:sha256:79d9222951afd1cde2967f4987b4aa7b4347f1510f1fc4d1141010690b9c8bff:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGI2ZWFiNGFjNWIwNmRiMjY5NmQ0ZDUzZTJlN2YwZDE3MzhmMWI3NTAyY2UyZWY1YjcwMzIzZjg3NjdjYTllNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGRkZTk1MTMwNGUxYWVlNjRiM2IyYmY3NWZkMzI2YjAyZGM0OTE5YjlhODM4MGZjZTY1YTAyY2VjOTE3MzdjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWM1NjRmZjMwNzY2N2QwZTgzMGJkYjkxYmY1MDYwY2FlODNhMjc2NzRkNTU5NWNiYTU0ZGMwNmQzNmE2NWMzYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTVkOTdmMmUwNzM5MTAzZjc1MTA1MjljNjA1Zjg0OTIzMTdkNDJmNDA5NzEwNGFmMmRkMjBiOGUyNmZhMmNmNyJ9" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L385-L501", - "version": "repo-lines-v1:sha256:261028a999073e8a0cb935c25c5226d7e3ccbb616cdf003ce8ba9d54ffb92c31:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJlYzFiOGExODllNmEyYWM4ZTM1MmJlMDMxYmIzYWY0NTI2MjM2NDU0MDI2NzIwODY5NzZkNWVhNTQyYjk5MDQ0IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIyZDVmMTIyNGM2ZDQyZTY1YWVjMzZmNmRjYjBiMTc2Y2FkMGJjYjU0M2U1ZjM3MDMzOGQ4ZjYzZjk0ZGEwZWM3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYzE1ZWYyMzVmYjkyZDFkYmQwNTRjZTlmMTFiZTRmMzA0NTVkYmRjMmU3MzkwNGY0MDYzYzA2M2M5ZjM5MzI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlZWMwMmIwMGE3NjI5NDE0ODA2ZDEyMjI3NjliODBlMGY2YjFjNTFmZmE0MGM1OWMxMTI5YjJmNDE4Y2EzZWM2In0" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L385-L504", + "version": "repo-lines-v1:sha256:0d91636ed62e262b1faebe69349c0c5b974af977be6c0af5e6b929b117ee81b7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJlYzFiOGExODllNmEyYWM4ZTM1MmJlMDMxYmIzYWY0NTI2MjM2NDU0MDI2NzIwODY5NzZkNWVhNTQyYjk5MDQ0IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmY0Y2JlMGYwMWM4NzRjOTVjMjU2YTY5ZTUyMDc1YTkyODI2YmE3YWFjMjQ0NmQ3MTczY2YyZjdmODI4YTQ1IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYzE1ZWYyMzVmYjkyZDFkYmQwNTRjZTlmMTFiZTRmMzA0NTVkYmRjMmU3MzkwNGY0MDYzYzA2M2M5ZjM5MzI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwYTgyMDE0NmQwZGY4OGNkZmJjOWI2OTc3ZDZiODNhODBiNDYxNTkzNzRkMzQzNGRkOWQxYWRmNzk3YzgyYmI2In0" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", @@ -22,41 +22,45 @@ }, { "id": "claim_28c4b1e064e94a798fe0220965a3951e", - "statement": "An accepted Termind submission synchronously captures composed content, context, model/provider/credential, reasoning, permissions, search, skills/registry, Guardian and preparation settings before compaction or memory awaits; a fresh runtime consumes that capture and persists a running snapshot before model work and transcript snapshots around tool-result rounds.", + "statement": "An accepted Termind submission synchronously captures composed content, context, model/provider/credential, reasoning, permissions, search, skills/registry, Guardian and preparation settings before compaction or memory awaits; a fresh runtime consumes that capture and awaits snapshot-save attempts before model work and around tool-result rounds, but runtime save errors are currently swallowed.", "evidence": [ - { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L213-L274", - "version": "repo-lines-v1:sha256:0dfd924390c397077812263deb5dd48a301f249f10478b62641a686cfd342a4c:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiY2NmYjg3NmVhNWUyNGFhMjgxNzFhOTdlM2NlNDFhYjIyNjE1N2VlZjcyMzhjNDMxMmVkOGQ3NmJjOGEwODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzNDE3M2M2NjQ2NDEzMTc3YjQ1NmZmZjdiM2MxY2Y4Y2Q3Mjg1Y2IzMzAxYzI0ZDM3MGU4YmZiNzNiZWVmOTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI3ZTA4ZDYzNDU5ZjAxMzEzODQ4MDgxMGIwZmI3ZjBiYTJhYWY3MmM5MzhkZGViZDEyYzRhZWZiNTEyZDQxMzYifQ" - }, - { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L303-L318", - "version": "repo-lines-v1:sha256:fd811671f5fdc47d264e5c3d26b3fb815385a39f5747a7d460403896bb8c0081:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlMmZjMTRlMmFjYmU5MjgxYmI5OTRmZjQ1NGUyNDAyMTE3MDQ1OWI5ODJjM2ExNTlkOGJlODM5NWI2NmJkNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNGFlYTM3NDhjNzUxOGU3Zjk1MWMzNGM0MTQ5M2FlYjY4OTgxYTg1YzBiY2UwYTk5ZTZmOWY1MTQ2OGQ1NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkN2RhMWFmYjFkMTc3MmUwNTY5YmU0MWZkOGQ0YTA4OTczNWNlMzczNDExNGU3OWZkYjJmMGJiMDdmN2ZmODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGI3YjgwZDA1MzcwODYxOGIzZTUzNTJmYWM1YWE3NTUxN2M5OGE4OWVjNGNkYzU0MWUzZjE4YTgxMjY1ZGEifQ" - }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L284-L304", "version": "repo-lines-v1:sha256:de29dc974cbd2c72ac1a59847fd02fc322550b4b15d0016d4ac1a76e768f07f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZhNWZmOTFkNGU1OWMxMDhlZWFkYjMzYzhiYzk3NTk1NGM4MjM1MDQ2YTcxNDRhNDFmMTBlMmI5NzgyYTc2ZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1MGE4MTlhNjgyMGMwMzcxODE2OWM0YjllYmQ3ZThkODI1NDlmMTgzZDE2ZjkxYjM4YWY5MGZkNTM4ZmQ5YTkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L474-L501", - "version": "repo-lines-v1:sha256:810bdc77e6681b23d6f9936b515073efa39a5f809eefcc66b0f6c79e571bec35:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MzNjZDg0MjZlZTc3ZDg3ZTk4YTlkYjY4YmMyNDVhYmEwYjYxYzY4NGNkZjJiZDhlNTJlY2ViYTA4YTliNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNWYxMjI0YzZkNDJlNjVhZWMzNmY2ZGNiMGIxNzZjYWQwYmNiNTQzZTVmMzcwMzM4ZDhmNjNmOTRkYTBlYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzODdlODkwYzgzZWE5ZTZkY2U1MzZhMTBhM2Q5MjBiODQzNTk3ZDMwMTQ4YjY1ZjYyYzYyMzk2OGMyM2RhYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlYzAyYjAwYTc2Mjk0MTQ4MDZkMTIyMjc2OWI4MGUwZjZiMWM1MWZmYTQwYzU5YzExMjliMmY0MThjYTNlYzYifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L474-L504", + "version": "repo-lines-v1:sha256:39b66e25ff4e817422c31291d31064ce809db630e4bc4d488ab5f1d7ec6fb48b:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MzNjZDg0MjZlZTc3ZDg3ZTk4YTlkYjY4YmMyNDVhYmEwYjYxYzY4NGNkZjJiZDhlNTJlY2ViYTA4YTliNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2ZjRjYmUwZjAxYzg3NGM5NWMyNTZhNjllNTIwNzVhOTI4MjZiYTdhYWMyNDQ2ZDcxNzNjZjJmN2Y4MjhhNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzODdlODkwYzgzZWE5ZTZkY2U1MzZhMTBhM2Q5MjBiODQzNTk3ZDMwMTQ4YjY1ZjYyYzYyMzk2OGMyM2RhYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhODIwMTQ2ZDBkZjg4Y2RmYmM5YjY5NzdkNmI4M2E4MGI0NjE1OTM3NGQzNDM0ZGQ5ZDFhZGY3OTdjODJiYjYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L630", + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L518-L632", "version": "repo-lines-v1:sha256:b1882f48ba2a0c4f0faaa010c3ad73fbbfed243a2f0f51c0d883ed8058e5a3d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" + }, + { + "resource": "repo://TFMTests/AgentRuntimeTests.swift#L141-L203", + "version": "repo-lines-v1:sha256:e33d46e012584bdfe7a8f9d7be51099aa8d95c4ff7363ff72335e66dcd9a5d98:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3OGRlZmFmZTdmZWM1ZjRiODEwNGMxZDViY2M3YWUwNWY4NDQ2MTY1MzEwZmJlZTJhZmNjNzBmNDA0MWRmNzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkN2UyOThiOTJlYzg4MDRjMjFiZmE2ZDM5YWM0YTFmNGY5ZDgyNzYxYzMwZDkzZWU3ZTYzMmU4YzAwYWIxMDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVjMGJiOTM5MTIyNTc1YzE1OWJlOTFhYjVhNmUwNzUzNTJiMGViODhkYjVmM2ZhMWFjYWIwZmQ5OGQwNWY0N2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRlNzdmMTc2N2Q2OGZlZGEwMjc5NTkxOWM5YjM5OWEwNDM2NWY0ZGQwMjFjNWI3NzUxOTI4ZTc2ODJhY2RjYjkifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" } ] }, { "id": "claim_27e003cf2fdc46a69d1b04b9b366fd67", - "statement": "ConversationModel keeps a display projection separate from the authoritative transcript because replaying display messages would lose tool calls and results; it owns the active runtime and consumes its ordered event stream.", + "statement": "ConversationModel keeps a display projection separate from authoritative tool-call/result replay history and retains per-run runtime/request/repository/start/cancellation/consumer ownership; only the active owner's ordered stream projects events, while completed owners are removed.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1935", - "version": "repo-lines-v1:sha256:29086746228df689c5ee73c74a8f470141cf46d41005fdc2804d4ee9d81f9759:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlY2VjZjZjNTcwM2QwMzM4NThjMTkzNjQyMTc5NGFhNmExYzY1MjAyMWM1ZjQzNTI4YTk1MTlkMDE3NzliYzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjU4ZTRmZmFjNDQ4ZTc3NTVkYTFlNTM3ZjU5ODgxMWFjYzY1MTAyODBjMmUyMjA1YTk5M2JhNjI2MWEwOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L474-L504", + "version": "repo-lines-v1:sha256:39b66e25ff4e817422c31291d31064ce809db630e4bc4d488ab5f1d7ec6fb48b:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MzNjZDg0MjZlZTc3ZDg3ZTk4YTlkYjY4YmMyNDVhYmEwYjYxYzY4NGNkZjJiZDhlNTJlY2ViYTA4YTliNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2ZjRjYmUwZjAxYzg3NGM5NWMyNTZhNjllNTIwNzVhOTI4MjZiYTdhYWMyNDQ2ZDcxNzNjZjJmN2Y4MjhhNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzODdlODkwYzgzZWE5ZTZkY2U1MzZhMTBhM2Q5MjBiODQzNTk3ZDMwMTQ4YjY1ZjYyYzYyMzk2OGMyM2RhYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhODIwMTQ2ZDBkZjg4Y2RmYmM5YjY5NzdkNmI4M2E4MGI0NjE1OTM3NGQzNDM0ZGQ5ZDFhZGY3OTdjODJiYjYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L785-L803", - "version": "repo-lines-v1:sha256:6f128436b4d34e1d8dff80a345e8abc178fb1737285d7cbee881bece263cf02a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljZjIxYjFiNzJjNDQxMzg5ZmYyMzE1NjcxOTNiOTgyNWY3ZjQ0YWY1MTI4YmVjNjlkNjVjNmUzOGI5NWQ0YTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA1ZjhjNjMxNWJmY2Y3MzE1OTUzYTJkYTBkZDM0ODVhNTkwMjk0OTExMzVlMGViZmFjYmI0MTkxZmE1OWVjYWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdhZjhmZjY1NDM5MTMxODVjM2UyNWNhNWQ5M2U3NjhlY2NjZTI4ZmRmNzZmN2Q1NTA2ZjRkMmViNzJiMzYwMDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdhZjMzNTQyNTM4N2U2ZjRiZTIxOTVkODMxM2YxMzhjMTM4MDk2YmYzOWIwNzE0Y2NiZDA2ZWI3NzhmZGFkOWUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2046-L2101", + "version": "repo-lines-v1:sha256:60ab2f3cf948c47a607a8b4920a7115abaa2ce0f871a7dc27bc3b3d38449c44d:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0MDUwMjQwNDIyYThmMDExMzc0NzViNjU5NGVkMjM0YjM1Y2MyYWY4NTJkM2ZkOTY2MTE2Zjc2OWYxYTUwN2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjU4ZTRmZmFjNDQ4ZTc3NTVkYTFlNTM3ZjU5ODgxMWFjYzY1MTAyODBjMmUyMjA1YTk5M2JhNjI2MWEwOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYxZTZiMzhhNjE1MjVhNjVjMWUyYTYyMTU4MTcxYjliODZlOGJlZjkxZTg3ZjZjMjY2ZDUxYmE0ZWU0MTkwNDMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L782-L845", + "version": "repo-lines-v1:sha256:df02649506e65a267b8bde0c2b2a601b2ee69457546eb7b528252a44f65f7949:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4Zjg4OTkyNTExY2VhNDk3NTZlMDY1YTg3MGY5MTczMGM3Zjk2MzlhOTQwYTU1NmNmNWRkNDQxY2JjYWU0MmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxM2Y5OWYyYWQwNTZjNmNkMzc4YjFjNTUzMzUyMzcyMjY1NGM3NzIzMTNhNzIwZWY4ZGNkMDUxMzNkMTg4ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1MTYxNWM5MWQyNDVmNjViZmNmNTZlMTRkMTRmZDI5ZWQyNzhiOGU0Nzk2Njg2OTcyMzUyNDFhNzI2YmNlMGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFjNGFlNmY1ZDA4NDIwODUyNTRiYzYyM2U5ZWIxOTUzMzhhNDQxMGU5MDhjZTljMjFiNzQwYzE4MWNkZjE3MjcifQ" } ] }, @@ -119,20 +123,28 @@ "statement": "Steering messages queue for an active runtime and become transcript history at its next model boundary; stopping requests runtime/task cancellation and secret disposal and returns undeliverable steering to the composer.", "evidence": [ { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L139-L165", - "version": "repo-lines-v1:sha256:1893129dd01a1c8a79a01cdf4625cb226ff4ebca0b1b11cdfc4a9827340a17ca:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4N2Q1NGYyZWZiNDBlN2ZjNDdkYmUzNjJmOTgzNzNmYzJkZmI3NDM3Yzc1NTlkMzEzYWFmNzAyMWZjMDkyNGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhYmQ3YjdjN2RkYTI0MTcyNTJhMDY3ZmY2YjU5NWQyMDllNGI5OTc1ZGRhYmJkODc2M2ZkYzFkOWQyMDQ0ZDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyZmQ2NTFmYjQ2OWY4YjVmMTQ0NjRiY2M2ZGMwZDZhYjJkZTM1Njg4MDhhZDcyNzYxYjhiMGNlNTFlNDZjNjgifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2131-L2148", + "version": "repo-lines-v1:sha256:53cde92a0dd7ce24d3eafb0ca4262fee109bcc9a648ce000d52edc980f14fe37:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxZjE5MzcwNTE4YzZmN2QwYTk3YmU0ZTFkMThiMTliMzk1MzMwM2E2ZjA1YWY1NTVhZjYzYTg5NTZhYzQ0NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzNzAxYTBmNThmMmZmZGJjMDFlMTFmMDkxNjRjYWIyMWEzZjY5YWMwNGNmODM2NTkyZGFmZWZmM2M4YTk0NGIifQ" }, { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L249-L265", - "version": "repo-lines-v1:sha256:dd7262281b0133a9a56570381c1b52df5dededbbb751d9e81871e8fd08bb10b4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjMjU4YjdiNGY1YjRhZjg4NjE4ZDI4NGJkODg1Y2VmYzNiMmQxOWRhMTk5OTlmN2ExNDI0YmQxMDliMjNjZjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmMGE5Mjg5N2Q0YzU4ODhhNzJmMDY5NDJjNmZkYmViM2NiNDAyMzBlY2U1ZmZjMThiNWJhYzU2ZTM0Mjk1YzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWQ2MDRlNmM4MjFhMGNkZDdjNjhjMGQ5MjM2ZGYzNGQzYWY1NjM5ZDI2YzAxNDRiMDQ5MDQzZTgxMjBmZWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1N2YwZDhiZTM1MGE0NGY1YjRhYjlmZmE1MDUyMjc5NTBjODk3YmI4MGVhZjI3NzNkMTAxODNkZTljNjE1ZWQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2227-L2269", + "version": "repo-lines-v1:sha256:9e949f60f54c571a639c5a456a5906bf512500d591ccc295b0e1e027a0bc125d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmN2VjZjg2MjQ3ZmRjYTUwNThhY2VmOGMzNGE3ZTNjYzA2ZmU3ZDA1MGU1YWE0OGNjNDNkNmE0ZWRjZmViYTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhZjI5MzY5MjdhZjMzZjcwYmFlYWRiZDZiZTZlYjY5NmE4ZTljOTMyNjE1NGU3NGNhNzMyMjJjNmE3YWVhYzgifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2040", - "version": "repo-lines-v1:sha256:f0190ce17ccd16ba2db4de8b4ee133516fd1cbf030f89f914b0643ab0477deb4:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmMThmMGI1OGIyNjAwNzUwYzBkMjdlZDlmMDA2ZGRhNjZiMjBmYWM4NDg1NzFlYTVlODUwOWFlZDQ3NWJmNjkifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2297-L2357", + "version": "repo-lines-v1:sha256:0a5ad8c4a3fe134628a03e3b8d548e258861ccf1f8458dd730d3890e0bdca5aa:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkNTY0NDY4ZDk1M2I4OTI1YWFkOWM1ODQ1MDQ2MGZkMzgxOTIyZWQzNjgyMDhmOWJiMTZjZTQ1YTBiOTVmZGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ4NDZhZjY2Yjc2MzVjMDg1OThlZWY0NmJkZjRmNDMzNTkzN2RiZjIzOGY3ZTZjMWMyYTFhNmY5YzFkYmYxODIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", - "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2888-L2914", + "version": "repo-lines-v1:sha256:72c2263feef69c6760c020b60734ead36df18bfafb24d039c4018de2e93d8497:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkYzBkNmVhZTk0NDljNDU4ODdiYTNkYmI5YTAzYzllMjY2MzkzODZmODVjYzVmY2UzZGZiOGJkZWM3NGFjNWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5ZjE3ZGE3Zjk5YzQ5YzdkZDdiMGU4MDMyODQ3OTY2OTc2MGZjYzE4OTZlNjFiNDQ4OTUxY2QwZjQ2MGJmZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" + }, + { + "resource": "repo://TFMTests/AgentRuntimeTests.swift#L335-L385", + "version": "repo-lines-v1:sha256:3621c0ff470cdb581e9ad2b7f2c0bb7566969f54b5ab1cb0c71936d5ee9c9975:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwMjkyODk3MTUxMzczZWVmNjRhYjMzMDhlM2YyZDc1YjRmNzNhZTkyMGNhMGE1NmFhMTNjODU0MGFlMGIyOGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFlZTk5ZmQ4ZTM3YzIzM2JmODEyNTExNWEwNjBiYjhkNmEwOWU5ZjE3YWNkMTBlZDkyMDQ4ZGFjMzM1MTJiZTIifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" } ] }, @@ -331,16 +343,16 @@ "statement": "Conversation steering changes the sending session and only the terminal_suggest availability key, preserves other overrides and skill/plan/memory context, leaves a legacy absent snapshot unknown, and updates activeContextSnapshot only when delivery occurs.", "evidence": [ { - "resource": "repo://FrameworkTests/AgentSteeringTests.swift#L187-L233", - "version": "repo-lines-v1:sha256:c063ee9ee073185632ec59778a870a9595ae3c988770a96bd2dfee10f2ba5338:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyNTlmOTQwOWQzOTg3ODRhY2EzOWEzMGU1M2VmY2E0MjlkZDdhNzExZWY1NTZhMzhmYTZlNGRlY2VmYzIwNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAxZjljNDg3Y2RiYjcyYTAyZDg2OWMyNmNmMGM5ZDYwYTdmZDg4ZTBkMjcxYzM1MGM5MTgyNDM4YjYzY2E3YmIifQ" + "resource": "repo://FrameworkTests/AgentSteeringTests.swift#L188-L235", + "version": "repo-lines-v1:sha256:82e37f28f07224b1253e75fde8fd6808f3e01cbade798094bd330d600c4976f3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NDEzMjRiMzhlNGJkZDg3OTE0NjVmM2FjMjM1NDQ3N2U1Mzc3YmNhZWY1OGU1OWJhODdjNmIwNWM2MmZjYzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2ODdhY2E5OWM5MDdlNjRmYjRmYWI2ZDZlNzRlYWRjMWRmNTdmZWU3MmM2Y2E3MjU0ZTBjM2NhNjAzNTg2YWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmJlNzk4MTc0YTE4NTIyNzM3MmU0NDQ2MWNkNThmYTA0MjdjMGE0YzExMzM2YmExZGIwNWQwMTYzYTBiNjAifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2010-L2027", - "version": "repo-lines-v1:sha256:458a3fd6f7bd7ffcce08a4a412975c949076d429ce17f65d44c3c2ccb774bcb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5MzFhZDdmZGZiNjJkOTVjY2U1NTA2NDE1NmIxYmM1Nzg5ZTU3ZjhiZDc5MDQ4OGM3MzU1ZWM4MDhkZTJlNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YjgzYmUzYzE2ZmJiMzE3MTQ1NzRmYjUyZDFhYjU4MWI3YTFiZmI2MTI2ZTI5YmI5ZjkxMWUxMzNiN2QzZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlhMjk1ZTVhYjdlZTNmODg1ZDE5YjdlYzE0ODJlOTI2NjJmZDcxZDdjZWM0MWZlMzUyYzE4MGFlNWRmZjFlZmUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2305-L2338", + "version": "repo-lines-v1:sha256:cb9f8b9416f603c127600f41efa158d6115f19b462933ba808ef06f65b04fa65:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5MzFhZDdmZGZiNjJkOTVjY2U1NTA2NDE1NmIxYmM1Nzg5ZTU3ZjhiZDc5MDQ4OGM3MzU1ZWM4MDhkZTJlNzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE3YjgzYmUzYzE2ZmJiMzE3MTQ1NzRmYjUyZDFhYjU4MWI3YTFiZmI2MTI2ZTI5YmI5ZjkxMWUxMzNiN2QzZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1MmNmN2I3YTBhNWViN2ViYTQwMzM5ZGQ2NGQxMWNiMTYxNTAzODY1MjRkNTQ4YTE1NTA4ZjVkNzA1NzA0YjkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2556-L2581", - "version": "repo-lines-v1:sha256:bda10632960326b0f4e14ddac898407fdd0f3a8325eb63e712a04206f75ef600:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MDc5OTUxYmYwMGZiMjNmYzBkZDhiMGIxMDk4NzFmMzQ5MzJiYjNmZTEyZTA4ZWZhZDdkYjllMzQwMjY2NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRiMTZkMmIxYWNkOWMwZTFiMzVhZmMwMzVhZWM4YmRkZjMyODcyZWJkNWQ1YzM2OGNiNDI5MGI3ZDU3MGJlMGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2888-L2914", + "version": "repo-lines-v1:sha256:72c2263feef69c6760c020b60734ead36df18bfafb24d039c4018de2e93d8497:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJkYzBkNmVhZTk0NDljNDU4ODdiYTNkYmI5YTAzYzllMjY2MzkzODZmODVjYzVmY2UzZGZiOGJkZWM3NGFjNWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTQxZjY1OGUxNDM2ZWUwZGNkY2I1NDRhYzllNGQ5ZTZhZjI4ZmYxODdlNTkwZTRhODljYjA3NGEzMzY1ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5ZjE3ZGE3Zjk5YzQ5YzdkZDdiMGU4MDMyODQ3OTY2OTc2MGZjYzE4OTZlNjFiNDQ4OTUxY2QwZjQ2MGJmZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYWFiMmE0NTg0NWUzZDAyOWM1NDI0ODBmMGY4NThiZDMwMmU3OTdmNjNkZjFjNzIzNzMzYmFjZGY1YTgwYTMifQ" } ] }, @@ -488,27 +500,27 @@ "evidence": [ { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L88-L228", - "version": "repo-lines-v1:sha256:26af95b584c6f8c1e65cebc40fade84fabaed69fc999ad156818375892763d7e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjYjA4ZDM4ZjgxOTRlMTEyMGIyOTYzN2Q4MmZhM2QzNzc3MTk5ODlhNWQwODc4MWI5ZTQyY2I4NjgwNDYyZTczIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0MTUxMDgxNzdlYjA0YjhkYjk2YTZiNGFhNGEzNzE2YjAwZGRmNjYxZmNhZDlmODcwOWI3NDdkNmI2MTBlZWM2In0" + "version": "repo-lines-v1:sha256:0034c170196c42d318ce328d8ce9ee1dd89409998248de0843e6a3e36d0aa5de:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NTU1MzI0NWZhOWIzYTg5Nzc5YTQ0MzliN2M3OTNjYzhjZDc2MjM3ZTIxMGI3YjI5ZDEyYmQ0OGEyYjNhOTkzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhOTdmOTEwNGNiNzExYWZlYzlkMDQ4ZDZlNzA4OWU4YzFkMTIzMzRiN2Y0YjA2ODQzM2JjNjkxZDZmZTUxYmI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIn0" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L760", - "version": "repo-lines-v1:sha256:7a08479880142384554fef4c4143ed597856d65be0c552a76400a967ada09df3:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" + "version": "repo-lines-v1:sha256:c80221e2f4c3085f440c78419a9e28c93800587fc504c207638be94c60943f8e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc5MWU5ZTRiNmRkNTg0ODk5NmNmOTE2ZDQ1OWJiOGI1MTBkMzBjMWNmN2ZlYmYyYWI0N2MyOWY5MzU3MjI4ZWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3OTg4YmRiMDg1OTc2MDA1ZWEyYjgxNzNjYmYxMjFjOTkwZjIzYWQzOWU0YzkxYmI1NDAyMDU3MWE2MTdiYWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZjgyYTA0NWVkNzg5MzI5YzJmZDc1N2ExNTA0OTdkZTJhYzI3ZmZhZWQzNmE0MWI4NGRmZjIzZmI0ZTRjMmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkYzRmZjQwN2IwN2IxNDA1OTllZjEyNTQ3MzcxNTBjZjc2Mzk4ZTNkMDg0MTkwMWViZTE2ZDJiM2VhZGY1MTgifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L69-L83", "version": "repo-lines-v1:sha256:0d5f7cf554fd4cb9799565481fd35f87cf6deabc07509813f9f40d913426acea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3MzJiN2I3NTFlYmQ1MmM4OWQxOGYwNzU4M2EwYjQ1MDY4OWNlMjkyYTIyNzJmNDVlNGZkNTY5ZGZhYjg1YmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImExMjMyNjI1MGIyOGY3Yzg0NmRmODVkODZhMWM1MmM1MjBhNjM1Y2Y5ZGIzMTY3MjFhNTE1MDdjY2E2NjE4NGYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1872-L1905", - "version": "repo-lines-v1:sha256:6a975f7fa61da5d616fc6f10e1f9423d2116389da4c055f19e477ed0fc6ab95f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkNDg1YmY3ZTFmMTQ5NDczNzFiZDY3NGZiMzAzMTJmNmNhOGU2YjU1MThjOTliNGI1YzRhMmE1NWYyOGI2OGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0YzBjNjJkMDZjYTNlYmQ3NDRjZGU2YmE0M2M5Njc2ZmIwYzQ2ZjdmZmNhZDZkNzRjZTFjN2M5NGNjYmI4YWYifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2002-L2044", + "version": "repo-lines-v1:sha256:5f7c207116c45771d4001646f206431e25efa58c2af14a14e1a3dd246214827a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJhNmU1M2Y5MmUyYzc2YmFhNWU3ZTY0NWE2YzczODVmZTU1NTYxMTkxOTczMWY3ZTczMTIwZTMwYjQxY2IxMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1YTY2MjU5MDc5M2ZlMzYwNDc3ZTJkNzYzMWEyNTdjYzk2YmY3OWI5OWE5MzliZDJhNjZkOTBmNDM0MzY1ZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiMWY2NWVjZTVlNWRlN2I1MGM5YmUwZWI4Yzg5NjZlMWRlYTliNzI4ZGQ3YTI5ZDIzZjNmN2U1NjM2MWM2ZjUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2000", - "version": "repo-lines-v1:sha256:def37b6b9d6634fc8205db0d3ab3eb671124b0e95c4e482e65b1b0ec66ce731a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNGE5Mjc1NzY2ZjZmOWRlODJkNTdmOTM5ZDg3YTk5ZDhhYmNjYWYxMWI1ZTczNDhiOTA2M2FkZmM5YmFiYWMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2227-L2242", + "version": "repo-lines-v1:sha256:0877731fa1e8352dfc7c8b1bdfee9b3fc14252c3499600a5e0969727f7c89879:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmN2VjZjg2MjQ3ZmRjYTUwNThhY2VmOGMzNGE3ZTNjYzA2ZmU3ZDA1MGU1YWE0OGNjNDNkNmE0ZWRjZmViYTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NGMwNjM0YWI1Nzk0YTg2OTJlN2U2ZDI0M2FhYzEyYmEyOTM2ZDc2ODY5NTdkZDcxYWI0NGUzNDYyNzc1OGUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L843-L848", - "version": "repo-lines-v1:sha256:69c8d0eebc87d56ae3c9b142d02d0db22d37583c5461baed4ef0bcc384c91037:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjIzNTI1YTY4OWNjZTMxOGNjNWYyYmRmNzM5YmEzY2JhN2UzZjk3MDlkZGJjMmM0ZDVmZTU2M2RhZmMxNDcwZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZjE5YjZmYmIyYWMxYzk2NmFiYjNiNDcyYzQzYjI5NmEwN2JiOTU3ZDY0OWY0NTU2MDYzMzVjY2M3MDE3OTUzZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYmFiMWFiMzA0Mzk4NjMzMmYyMWM2MTUyNWUyNjI4YzE2NGU1ZWRlMzYwMzhjNGMxYmM0MjgyYTVkMGZmMGFkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNmFlNWQ4OWJmZGUxNzU1ZTZmMDE4MTkzYzdlYzQ1YzMzZDkwNTgyOTI3MWIzZmI5N2QwYzk4YTUzNzI3NjFmNCJ9" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L895-L900", + "version": "repo-lines-v1:sha256:9fcf92bd03be2ed38be9f053105c95d1fc4299bd68565fde983ed6b044b787c2:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGJjZDc5Njg3ODBjY2VlZDAwMzExZWE1MmE3MDdiZTk2NWYxODZlYTJhNGM4NGEwZWUxMGQ2Y2M1YWM2NTdlOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzZiMTI5ODBjNTljNDNiNTBkZDIxYmJlZDczMzZiMzZjNjQ0OTdhYzg5ZDlhMWU0ZWQxNDIyOGFjZjIxMzFjZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjg0YTExYWVkOTE4MTlhZGYxNjA4MjNjNzZjMjhkNzRjMjc1OGE1ZWIxOWQxNmY5ZTI5OWRhYTg0YTBhYmJmZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDgxOTI4NGM5ZmE5NWJhNzRkYmZiNjBhNTA0ZjI5Njc4OTU1NTQ4NzJmMjYxYjZjODA1YmNhMzMwNGM0Y2Q1OSJ9" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L153", @@ -546,32 +558,6 @@ } ] }, - { - "id": "claim_c32cd0e1ce114b36bb3a375a2f210902", - "statement": "Preparation cancellation does not guarantee durability across the separate queued-runtime handoff: ConversationModel.start defers AgentRuntime.start to its event task, and immediate stop can cancel that task before runtime snapshot persistence begins; the immediate-stop operations test checks model calls, not persistence.", - "evidence": [ - { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L123-L136", - "version": "repo-lines-v1:sha256:4d5a3a936c8b5150faef581457b3b776c9d18f4efe621f40a45a72c60ef2a59a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3MGM4YmE2NzBjNmVkZmMzYjk4N2VhOTcyODM5OGY5ZDI5MTQ1NTUzODA1ZDQwZDVjZGNmZGE1YTk1ODk2MWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjYzFjNmE0OTAwMzg1NzI5Mzc4YTYwZDdhNWJjMjU2MDdhZGMzMDBjNDFlYjg1NTBlYmQ0MjYyMDdiYmViNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxZmIwZDU5NGEyZGZmNmM5M2FlMmNkNDZkMjg0MGJhZTRkOWFlOTJlM2JkNzMwNGQ0YzYxMjY2OTQwOWRhYWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzYzg1YTk5NjdhZTQwM2JlODJjYTg1Nzg2NTUxMTVlYTkzNjIxZDRkM2I1MTVkODcxYTljNTc1MGU2NjczMDMifQ" - }, - { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L220-L226", - "version": "repo-lines-v1:sha256:cc952d0f1cbf33f306da48d33bcf8bf2242b55cfca2867498da7ce2a4f243d56:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjU3NDBkMDVhYjczOTc0NDVlYWU1ZWRhYWY5YTVjODk4NDQwMDk0NzUyYjRkY2Q3ZjI4MjFmNTQ2ZTliNTNjNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzllZjY4ZTg0MTUzNDc2MTZmMDcwYTU2MjhjYWQ0MmYzMDZmNTRjNjE4MjA0OGU2YmU1ZDgyNjI4YzZhYjU1YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjMxZWUwZWMyMTRhNDZmZTcyZmQzNWNjOTEzODUxNjVkODAwNGIzNzc4MDI1NDRmOWIwODY5MzE1MTQ5MmU3MiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTE2ZTU4Y2JiMDlkYmM1NTdlMTAwOGU2M2U2YTU3NjU3N2Y0MTFiYWM5NmRlMmFhYjVhODJhYzcyNzNlMzUzYyJ9" - }, - { - "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L205-L220", - "version": "repo-lines-v1:sha256:774f5d0ae6842b8664276641f46b6f253e19709bd44872b96739c6326b567f6b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3ZmIyNGM5NGIzYjY1ZDg2Y2I5MmE2YmI3NGNhZjg3OTA1Y2U1OThjZDMwOGUxYTg0ZWQ1Y2ExMDU1MDdiYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjYWRiMjlmOGU4MGU2MDkwOGNiNTczMGFhZmVkYmIwNjI1MzkxOTYyMjYyMWVmNDI4MDM5ZDQ3MTc1ODU4NTAifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1907-L1935", - "version": "repo-lines-v1:sha256:29086746228df689c5ee73c74a8f470141cf46d41005fdc2804d4ee9d81f9759:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlY2VjZjZjNTcwM2QwMzM4NThjMTkzNjQyMTc5NGFhNmExYzY1MjAyMWM1ZjQzNTI4YTk1MTlkMDE3NzliYzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYjU4ZTRmZmFjNDQ4ZTc3NTVkYTFlNTM3ZjU5ODgxMWFjYzY1MTAyODBjMmUyMjA1YTk5M2JhNjI2MWEwOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1956-L2000", - "version": "repo-lines-v1:sha256:def37b6b9d6634fc8205db0d3ab3eb671124b0e95c4e482e65b1b0ec66ce731a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjZjMmYzYTBiZWM4ZmU1ZTRlYjY3M2NlODAwMmMwODM0YWJiYTU1ZDM2MjhkOTMzNzUxYzA3MmUwYTc4NGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNGE5Mjc1NzY2ZjZmOWRlODJkNTdmOTM5ZDg3YTk5ZDhhYmNjYWYxMWI1ZTczNDhiOTA2M2FkZmM5YmFiYWMifQ" - } - ] - }, { "id": "claim_f8eef38f66f149f98e83d2ba03a64db4", "statement": "Implementing a pending plan captures a new acting submission with planContract cleared before resolving the plan, folds composer attachments and armed skill once, and leaves the pending plan and composer payloads untouched when refused.", @@ -598,20 +584,16 @@ "id": "claim_9b9c512baaff4cb68099608384f1755e", "statement": "Explicit startAgentRun reuses the last user turn's stored context snapshot or a neutral unknown legacy snapshot, while capturing current runtime configuration rather than reconstructing historical model settings, credentials or skill-package bytes; this context path is not a manual Retry/Resume UI or automatic recovery execution, and provider retry remains AgentKit-owned.", "evidence": [ - { - "resource": "repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift#L228-L232", - "version": "repo-lines-v1:sha256:eed277fa50bd81bf4981cfc5ca33ee0040f9f31c8c789675787c5b21125c5a3c:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZWFjOWQ5NmYxZjNjZWJiNDM5ZWM1ZWU1OTBhNzcyMDU4YzBkZmMwY2E3NTBhMGM0MGVjNzI3NDU5MjU4ZmY0NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTkwOWE1M2E2MThkMDI4ZTFkMzAwMGMwYTdhYmNlZWNiZTVmYjM3ZjRlNTExZWZlNDY2NDNmYWQ2ZTY2ODcyZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWZkNmZkMjc0OTZiYTU4NmFjZGJiODIzYTBhNjQxYTUzMTU0NTc4OTAzOWM5MjU2NWUwYmQ5Zjc3OWFiMjhiZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYmNiODI3NzU2YjA1Y2M5Y2NmNDljNjg5ZGExNTM1ODlmNDA5MjYyNzY1Nzg2Njg0YTllMDVhNDY2ZGIwZjQ2ZiJ9" - }, { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L430-L445", - "version": "repo-lines-v1:sha256:3f7bdc58bf3927133f06eea2e8e00a3133869ed3462af5055d22c12e05480f3d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzMmE1MGMyZTM5ZDNkYmY3MWUwOGMzNmMwNDZjZDQ1YTc0NDU3ZWE1YzA0NzlmYjYzZDQwNDFmYTk2YmEwMGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM3ODk5Y2YxYzlmODQzNzQ2Mjk3MTM5YjliNDZhOTNiZjExN2IyMTE1ZWE5OGQzMDcyMmI1NzMyYjQxODc2OGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" + "version": "repo-lines-v1:sha256:c85a089aa307b4c3a806ce4e64d7241cd43a19cb659be886d833034b77133e61:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3ZDRkZDA5ZDQ4MDNmZWI3OTVjMjBhN2Q5MzA1ZWQ5ZTgyNTdiOWUyNmEyNDM0NjEyNDMzNGY1OGVhYjhkZjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkxMTA3Y2Y4MzI4ZWVkMDM1ZGViOTdhYTU3ZmQ1NmZkNjUwNTA2ODliMTA2NTEyNzk4ZjM1MDI3YjIyYjg2MDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRhODMzYjcwMjVmN2VkMTA0YmFiMzE1MWI3MjhiOTgwYmJkZTA4N2MwZDYxNDU4NjVmOGE3NWUzNmJkZDU4YTYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L306-L336", - "version": "repo-lines-v1:sha256:bf88b55c1f3fecac21ea3c56a99a2176e6666c75cde733f0cf4aef2414a158d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJhNzE4ZTA0YThkYjIyMWZkY2ViZjYyMzlkMzkyM2ZiZThiNzMwOTE0ZmI0ODgxYmQ0ZDcyMDNkM2ViNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjYTlmYWYyZWJmODlkNzUyZmY5ZDg0MzNhMTU1ODQ5YzIyMTA4ODA1OTA2OWU5ZGI0ODU5ODkxZDcxNDUwY2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ0OGJmNTE3NmViOWRmZDcxNmVlZjIzYjMwMTU4ZTE1ZWVjYWEwZDM4NGMzMTczOTA4YjJiZGJhMjA2MjU0ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlhMWQ3MWNlZjc4YzMxNTAyYjU3ZGVjZmIyOWNjYzFiNDY0OTZhYTdkMDBkM2M2YzZlZjkyZDc1NDM3NDgwODgifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L306-L337", + "version": "repo-lines-v1:sha256:8bc27bce337b3765b83435d39c44e8894d978165ca7c6a7d50c2936f14378941:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJhNzE4ZTA0YThkYjIyMWZkY2ViZjYyMzlkMzkyM2ZiZThiNzMwOTE0ZmI0ODgxYmQ0ZDcyMDNkM2ViNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ0OGJmNTE3NmViOWRmZDcxNmVlZjIzYjMwMTU4ZTE1ZWVjYWEwZDM4NGMzMTczOTA4YjJiZGJhMjA2MjU0ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFhNTljMjgyYjg5MDlkMDczNzkwMTdjZjAwZGJkM2IwOTJkYjBlMDRlNmMwZWRkZWQxODliMzY1Zjg0MDE4ZjkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L566", + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L518-L568", "version": "repo-lines-v1:sha256:edb46d8373b999cee1120d8ef9ef0cc3027c386590cbc8cbe77eed3d1d2d9086:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3ZTZjOTQxMjBkZTM2NTQ4YzFmNzAxOTNjMzFjODQ1NDU4MjQ4ZDQzYTg3ZTZlNTQyNjkxMGZjZWRiYmU4YmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmMjIwYzhiMTI3ODQ1YTBiNDdlOGI4ZDY5OWQ0YTZhZmQ1OTVjNmQwOWFjYjVmODliNzI2N2Y4MmQzMDc2ZTkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0OGI1M2NhMjYxZmM5ZWYxNWMzODlhZWRhMDRmODYxMWM3YjQ3ZTE4ZTYyMDk1ZDExZGY4OTBhY2MxZDhmNGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzZjQ3NzRkYmY1NmE0MTNhNmY3ZTUzNWRlYzdhYTU1NjRjZjcxNDIyZTJhMGI4ZjU5YWQ2NWMzYWZlYzE5MTUifQ" }, { @@ -621,6 +603,10 @@ { "resource": "repo://TFM/Features/AI/ConversationTranscriptView.swift", "version": "repo-file-v1:sha256:e25a6400fe10738ea7881664f6f1fc2465d7696d470dce522ef5222a3bb83fe4" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" } ] }, @@ -758,23 +744,31 @@ }, { "id": "claim_eb12b2d099c145dba48004e4797b19a7", - "statement": "Live run-started, captured-failure and preparation-interrupted paths adopt the staged replacement's real run provenance and chronology. Cancellation awaits save before adopting after an identity check, whereas missing-model/credential failure projects live before asynchronous save, so the end of preparation is not a durability barrier.", + "statement": "Live run-started, captured-failure, preparation-interrupted and queued-runtime-cancelled paths adopt the staged replacement's actual run provenance and chronology. Preparation cancellation awaits save and rechecks identity before adoption, whereas queued-runtime cancellation and missing-model/credential failure project live before asynchronous save; only the queued cancellation's stopAndWait joins its save attempt.", "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L114-L195", + "version": "repo-lines-v1:sha256:e04b4abd57b7b220f9a0e87a9be347cc76190405efb19f7ffeda06c847620bdc:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRhMTMwMDNhMmEyMDc0NmI2ZGY4NjAxNjVlZDkwNjg4OTZlNjUyODA4Y2Q3YTM2MDdlNjhjMjRhYTlkMmFmOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + }, { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L476-L534", "version": "repo-lines-v1:sha256:435b7b43d6dfe28c7d197656e31af82f72b308168427674bccfeb12777d04735:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwZWU0MDJjOGFkMzY5M2YwZjFjNzY1MzZlZTk5OTVmZWMxYjkzNTBkNzU5YTVkOGUyMDY0YmRhYjRlN2Y1MzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", - "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L797", + "version": "repo-lines-v1:sha256:264e0504f5139486947350a5ec872d5440593d50d499f73e6156a8fc3b7a6ffc:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyNjU3ODFmNGU1ZDY5ZTk4OWU5YjFkOWQ5YzRlMTgyMDZmMTI1ZGUwNmU0OTVjOWUyOGE1OGMzYTEwOWY3ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMzZDYyMjM4YjljY2EzZTg2ZTlmMDM1ZWM0YzFhMmJkNDI0ZWIxYmEwMzUzYzU4ZjhkMGVjMjMzYWE0MDMyOGQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", - "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2131-L2166", + "version": "repo-lines-v1:sha256:20935b8e4d562e7318f72909f63c38e93c8c3eea78fc9e624b0dd6507ccb26fc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxZjE5MzcwNTE4YzZmN2QwYTk3YmU0ZTFkMThiMTliMzk1MzMwM2E2ZjA1YWY1NTVhZjYzYTg5NTZhYzQ0NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2625-L2646", - "version": "repo-lines-v1:sha256:078528287f4cac7401a61342ac1db3154e36f5566407f8bb9332cd63c543e273:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjZWUwZWU1M2M2NjUxODNlOTE0NjU5MDViZWY3ZDQyMTI0ZDRmM2JkYTJkNDg5MzlkNWIwNWQ1Yzc1MzIyODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODMwOTk4YzhiZmMyY2EwMGQ0MGM5NDlmZTgxOTQ5ZmQwZDg4MTg1ZWUzOGNlMDNjMjAzNzg4MDZhZWQ4YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2YzUyZmU0ZTgxMjIzMWU2NGIyMGFmODc0MmI0ZGJjZWNhMjlkMDFjMTVjYzk1NDNjYzc0ZGFhNDAzNjlkZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmZGI5NTg3M2IxNmNiMTEwMGJjNGVmMGZmYWFiMzZmODc1Y2EzYmVjODY2MmIwMDA5YzEwY2JlNWE2YWVjNTMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2171-L2224", + "version": "repo-lines-v1:sha256:02cd0d582029e215a49f5473ec19e433dda398fb1bf3526919d5b4d87cad0e2b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyMDJkMWVjMzI2MWJhMDI3M2Y4M2Y4NzVjZDYyMDE0NGVlYjdmNmE4OGQ3NjRjZjUwMDFkNGY3MzI2ZGE4ZjAifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2790-L2820", + "version": "repo-lines-v1:sha256:9f1fd5a5eabda463aa83e327bcdf01a23021582c819550c870538be441fa237d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkOWI4N2E4OTdlYjVhOTk0MmY4ZTliNjFhMTk0ZThlZDQzODg3MzE3YTlkMmZmZWUxYzI2NzgyYWRjYmUwOWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkMGJhMmVmZGVlZTg3MTExODdlMWRlMDc5ZTZkNmYzMTVkYzUyMTY1MzdmMWMzZjc4NzFlMjE4ODljYjY0YzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4ZDUxOWQ4ZGMzYjIyZmUxOGIzMDY2MjU0OTU1NGI1Zjc2NDMwM2MyOWI0NTZjNWNhYzdiZjUzMDM5NTdlNDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNWNmZjQ1MWYyZTI3MmQwOTc3OWQwYmExODM3NzU2NzcxYzIzMzA5YjQzMmQ4OWMzNTIyYmI3YWM0ZmJlMWUifQ" } ] }, @@ -799,10 +793,94 @@ "version": "repo-lines-v1:sha256:f5fa05397f6c5f103da1b22e51552d54d9f197d9f1cd73b34142a57e11b9d9cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYxZjE3YzM4MDE4ZjhkMDliMDEwMWYzMDI4ZTlmZDk1MGJmZWVhMjljYjAzMTkxYWVlNDNlNzBmODU2MTViMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhZGUyYTZhM2JkNmI1M2Q1MWI3ZGZjMzk1MjQ2MDBiM2I0ZDQ2OGMzMWM1Y2ZjNWM5MThiNjgyMDExOGY0ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0Y2FmM2FiYTk4ODdmMWYwNWQ0OTgyM2ViOGQwNWE3YjhhMDkwMzhlNDQxNWE2ZmYzMDg3ZDQxZWRhOTQ2YWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI2ZjkxNzkxMGMyOWI0MDdmMThlZjI1MDJiNmU1NWE5NDc1M2NlYjVhNDIyMzY0NDUwODgyODBiZjg2M2E1YzUifQ" } ] + }, + { + "id": "claim_99dde5b2506d42f2a00dda12d6b4a8db", + "statement": "Cancelling a queued ConversationModel run skips AgentRuntime.start and provider/tool work, independently saves a cancelled repaired/sanitized snapshot after presentation release, preserves captured prompt identity/payload/explicit context/permission, and fixes synthetic run identity/start at acceptance and finish at cancellation.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L205-L358", + "version": "repo-lines-v1:sha256:2df60a0c45284c8251de68357623e2d236053b73287753667a16e096f091f824:eyJzZWxlY3RlZExpbmVDb3VudCI6MTU0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2N2ZiMjRjOTRiM2I2NWQ4NmNiOTJhNmJiNzRjYWY4NzkwNWNlNTk4Y2QzMDhlMWE4NGVkNWNhMTA1NTA3YmFiIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIzMzUzODBmZDI4MTUxZjMwMmFmMzgxMTExYTM1YzMxZTljZTVmZTk4NTkzYTU3OGMzMzJlZTU1ZWNhYzFjMWU5In0" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2046-L2148", + "version": "repo-lines-v1:sha256:78f9249c18e1471218a3a9368ebdc8c891d7c834443fd9546ca12c4c2f2769e2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwNDA1MDI0MDQyMmE4ZjAxMTM3NDc1YjY1OTRlZDIzNGIzNWNjMmFmODUyZDNmZDk2NjExNmY3NjlmMWE1MDdhIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIwM2I1OGU0ZmZhYzQ0OGU3NzU1ZGExZTUzN2Y1OTg4MTFhY2M2NTEwMjgwYzJlMjIwNWE5OTNiYTYyNjFhMDlkIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI5MzcwMWEwZjU4ZjJmZmRiYzAxZTExZjA5MTY0Y2FiMjFhM2Y2OWFjMDRjZjgzNjU5MmRhZmVmZjNjOGE5NDRiIn0" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L800-L839", + "version": "repo-lines-v1:sha256:2c3a8b605ccf92d763131c5306cfadbe41b558bd8ca518fb1c34cf2c562c1477:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2ZGEwZjEzMzRkMTliNTA1ZDRmM2FkODBjYTUyZjBmYjFlYWUxMTYxNGZhMTgxOTQxZTRkZDJjNDU2MmRlZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxYTU1Njk1MDNhNmUzNGM1MTk5NzQ5OTY1MTAxMzgxYTczNjJiZjYxOTkzZmYxYTc1MThlODE4YTg1MzYzNDkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljNTlmMGFjNjIxZWU2ZmQ0Y2FjNTgzZTNhMjA5OGFhMzRjZTMwZmM3YzM2ODFkZDE3ZmZlMTIyNzkxYzVmYjciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkZTNhZGJhNzE3YmFjMzc1ZGIyYmVlMWZhNWIwODhlYjc3OTc3YmNiMDdkMTE5NGE3OTViNDk4NjJiZDBkYjcifQ" + } + ] + }, + { + "id": "claim_e6ae0b08ad7d4b79a6afabfa2bf5a107", + "statement": "stopAndWait captures preparation cancellation and retained run owners before awaiting, joins earlier stopped owners through snapshot-save attempt completion, and excludes later replacements; detached owner events and completion cannot retire the replacement. Terminal events precede this join, which does not promise storage success or join the separate durable-event recorder and secret-disposal tasks.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L258-L288", + "version": "repo-lines-v1:sha256:ce67e2ef361a5e212f6542afcef72a76dcd2adf1b5aec4a96bf93ef9c70cbb09:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkMWQ0MDBjNjQ3ZWM4YzlkYWIxM2IyMmJlZjUzOTJkMjRjMDI1N2M4NmYzNzk3NDFlYzI0MDdlYjk1ODY2MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjllOTlhOTg5YjljNjIxMjY0NjFlNTk5NzM3ODA3OTQ5OWIzMTdlNjlkNjk1NmQ4Mzk0ZDcxYTE3MmRiYzcwNTEifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L408-L468", + "version": "repo-lines-v1:sha256:b7653b07c27d97951ef1ff2e860483c8774a6281bedddb2049bb368cfcb7a2f5:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4NzY2NjhiZmJlNjM1NGJjNGVmNjQ4NjBlMjVmOTkzY2ZjMWJmZWM4NGVmMjFhMmE1ZWY4OTM5YzBlZjdiMWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3NmM1MmY0NWEyMTRkNmRiYmJmMTIwMDRlYmMwNTE1MjkyNzVhZmE2OTlmZGEwNjcxYmFhNmFjYzZlNzVkNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2NmU4ZjFjYzllNzRkNTllYzgxYjhiZjBmNmY5ZWQ4YjRjNjI2YjRlNjg4MmZjMDBhZGU3MTgyMzk3Y2Q4YmUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2068-L2101", + "version": "repo-lines-v1:sha256:1a621375cf68943bd2684bce7315c5396d65e0f42afea50e303a938f95687431:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyYmVhNTFiNTgyYmE0YjgxNzEzZDdjMGI3ZTViNmNjZjY4ZWM3ZTU5YzJhMTNlMzFmZDE1NzM2ZjMxZTNkNDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwMGJiOTZkNTRhZGJmZDQzODUwZTkxNWNiOTM5OWFlNTFjYzdmYWE5NjYxODRjNTU2ZDVmM2E2NDQ1YjY0MDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYxZTZiMzhhNjE1MjVhNjVjMWUyYTYyMTU4MTcxYjliODZlOGJlZjkxZTg3ZjZjMjY2ZDUxYmE0ZWU0MTkwNDMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2227-L2269", + "version": "repo-lines-v1:sha256:9e949f60f54c571a639c5a456a5906bf512500d591ccc295b0e1e027a0bc125d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmN2VjZjg2MjQ3ZmRjYTUwNThhY2VmOGMzNGE3ZTNjYzA2ZmU3ZDA1MGU1YWE0OGNjNDNkNmE0ZWRjZmViYTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhZjI5MzY5MjdhZjMzZjcwYmFlYWRiZDZiZTZlYjY5NmE4ZTljOTMyNjE1NGU3NGNhNzMyMjJjNmE3YWVhYzgifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L156", + "version": "repo-lines-v1:sha256:948a190f2472d04ec334b75189ed92f9e5ee43e56f9d414625e299e5e9b5915b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + }, + { + "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", + "version": "repo-lines-v1:sha256:d9f1bf35259a47439fd3dada1b509d5839ffa29d7e4a44afbbd876c5ab95c628:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmMGNkYjk3MmE3YmZiN2VkMjQ2ZjlmZjY3MjM4NWU1NWY0OTY4ZmMxMTA0M2RjNWI2ODRkNTFkZmEzMWRiY2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2OTQ3NDIzZWZiNmI1NTAwYTRjZDg5NDM2NWUzOGUyZGIwYmQ2NDA0MjJiZWFlMjQxMDljY2NkOWI4ZjAxNTEifQ" + } + ] + }, + { + "id": "claim_912ffe25fa844b33ac49187950448d26", + "statement": "Manual approval waits reject already-cancelled tasks and remove/deny the matching continuation on cancellation; stopped-run approval and review presentation is retired by run identity so cancellation can finish without a decision and stale review state does not carry into a replacement.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L361-L468", + "version": "repo-lines-v1:sha256:afda334297333df002c28a1a43408e2da88e4f0f50621045c23cf334d51b3fa1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI4MjIxY2U4NGQ5MGYwYmI5ODlhZGFhZGQzMzUzNzE0YmQ0YWVlZDFkMTQ3ZDgxNmVhYjUxNTc3ZDk0YTUxYTkyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI3NzZjNTJmNDVhMjE0ZDZkYmJiZjEyMDA0ZWJjMDUxNTI5Mjc1YWZhNjk5ZmRhMDY3MWJhYTZhY2M2ZTc1ZDVmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0NjZlOGYxY2M5ZTc0ZDU5ZWM4MWI4YmYwZjZmOWVkOGI0YzYyNmI0ZTY4ODJmYzAwYWRlNzE4MjM5N2NkOGJlIn0" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2271-L2295", + "version": "repo-lines-v1:sha256:a877b7d22f7f4b07ffeddb6b97bf4c15c583d73fe5071ea176e8f929601c140c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1YmMxM2U5ZGI3NjE5NjI1Mjc5NWI2MzBjNmQ2N2FiYmMxYTcwNDZlMzNkYmI2ZmMxZjlmMTdhNDNkZjY0MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYzYzk4YWY5Yzg1OTgzN2ZhYjM5YTdhNmM3N2M4NmIxNTM3YWRmNTRkMWE4YTYwNTZkZDdhNWZjMGI0Zjg4ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNGE5Mjc1NzY2ZjZmOWRlODJkNTdmOTM5ZDg3YTk5ZDhhYmNjYWYxMWI1ZTczNDhiOTA2M2FkZmM5YmFiYWMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2366-L2419", + "version": "repo-lines-v1:sha256:9ecc482f43f35d4cf1bc75b4aa661be7fea0a889099e38cf2b31275acd07d81f:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhZGM1MmVhNmU3MGE0ZDdlZmE0MTgzZjc5OTY0ZjZiYmNmMWNlMjMwY2RhYjVjOTZkOThhNTlhMDA1ZGE0OWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2YzU4OWUzZjMzZmE2YmUxYjA2MDkxNzZmMTJmOWEyNDFkZDJkNGRhZTczMmI0NzJlYmJiMTA2MzA0ZDNkNWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0NGJhOTNjM2QxNzkxMjk3MTQ0M2M4MTkzNDE3ODZmY2I2YzRlOWQ5ZDEyNGIzZDEyOTBkOTBlZTcyNTJmZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY4MGE2ZjdjMWViOTE5Yjc2N2VjYzlmMDY1MDlmZjljMjUwZjE1Y2E2NDVlMzhjOWM3NTRhM2E3NGE3ZmY1YWYifQ" + } + ] + }, + { + "id": "claim_9d439c6c259043b680edda5a831f92a0", + "statement": "The queued-cancellation persistence regression adopts an edited staging message into one cancelled SwiftData run and verifies payload, explicit context, permission and chronology using a fresh ModelContext in an in-memory container; save-barrier and injected-review fixtures establish ordering and presentation isolation rather than disk-crash, storage-failure or live Guardian behavior.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L114-L195", + "version": "repo-lines-v1:sha256:e04b4abd57b7b220f9a0e87a9be347cc76190405efb19f7ffeda06c847620bdc:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRhMTMwMDNhMmEyMDc0NmI2ZGY4NjAxNjVlZDkwNjg4OTZlNjUyODA4Y2Q3YTM2MDdlNjhjMjRhYTlkMmFmOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L258-L288", + "version": "repo-lines-v1:sha256:ce67e2ef361a5e212f6542afcef72a76dcd2adf1b5aec4a96bf93ef9c70cbb09:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkMWQ0MDBjNjQ3ZWM4YzlkYWIxM2IyMmJlZjUzOTJkMjRjMDI1N2M4NmYzNzk3NDFlYzI0MDdlYjk1ODY2MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjllOTlhOTg5YjljNjIxMjY0NjFlNTk5NzM3ODA3OTQ5OWIzMTdlNjlkNjk1NmQ4Mzk0ZDcxYTE3MmRiYzcwNTEifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L408-L468", + "version": "repo-lines-v1:sha256:b7653b07c27d97951ef1ff2e860483c8774a6281bedddb2049bb368cfcb7a2f5:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4NzY2NjhiZmJlNjM1NGJjNGVmNjQ4NjBlMjVmOTkzY2ZjMWJmZWM4NGVmMjFhMmE1ZWY4OTM5YzBlZjdiMWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3NmM1MmY0NWEyMTRkNmRiYmJmMTIwMDRlYmMwNTE1MjkyNzVhZmE2OTlmZGEwNjcxYmFhNmFjYzZlNzVkNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2NmU4ZjFjYzllNzRkNTllYzgxYjhiZjBmNmY5ZWQ4YjRjNjI2YjRlNjg4MmZjMDBhZGU3MTgyMzk3Y2Q4YmUifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T03:40:50.823Z" + "at": "2026-09-23T06:11:02.802Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 90bc1f77..15520fa4 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T05:23:00.532Z", + "updatedAt": "2026-09-23T06:15:26.424Z", "command": "update", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index 1a21776d..04cb2f23 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:a6cde418a9982f9a4a6cd86e4848c36ef88b6ce715d11715d06b6a50f9fef8d7", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/engineering-invariants.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:f62db2005beea84e609b860cb6d2e52e7a742faf98e3d9f088179b031465fbbe", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/mobile-ui.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -23,25 +23,25 @@ "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/module-map.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:cf89f7807de84434ff5a04aeed85734ce7e7f87b5fbc652e7210a60122fdd1d5", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/native-app-directories.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:1011bb9602def6c6ca1bb7bf0cde718b8479b58ac8af9526e85bae3771596073", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/platform-runtime.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:4dbecf9ffc4a898da594e820778cd3e693619b49c45ffc6745b7684ef00c4f46", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/shared-module-directories.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -51,11 +51,11 @@ "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/supporting-system-directories.md": { - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", - "pageVersion": "sha256:2711b0e7d443185dd21a3d96c2cb80f84a5e2149a5a6fc64fda5363afcbc86be", + "pageVersion": "sha256:bc62050f5799c6e7764ff9d08a0e721dda371eb74b7b8342346c59f747abb3b5", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" + "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64", + "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", + "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d" }, "/openwiki/architecture/system-boundaries.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -65,11 +65,11 @@ "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", - "pageVersion": "sha256:46c6736cc2e227449885be7dd7fbf51a7b98259f7509c85a334881cc20382e90", + "pageVersion": "sha256:d3bcf7c3b342b7c7e0a24bff200d9de3fd902e66a05ac6d1f8ff1046b23948d5", "completedBy": "codex", - "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9" + "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64", + "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", + "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -107,11 +107,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/ui-design-principles.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:92110e4a68f9373b2a5339feea69ea1a11bb1fe73727dd0e4709661aa7cb595a", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/integrations/mcp-server-and-bridge.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -156,18 +156,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:5533c3350dcd3e0218aa0071621fea4658eb209ed33958d8f719a4f6752f4cc6", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:6bee77cc55ac7f4209d5ea3e889e5ef21b88a7e3f39118c9fa0dffe665855886", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { + "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", + "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", "pageVersion": "sha256:d57d02019bbac973232c7889a9cd1d1192b0c70a6dd91c12a3a867b578b6fee0", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9", - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc" + "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" } } } diff --git a/openwiki/architecture/supporting-system-directories.md b/openwiki/architecture/supporting-system-directories.md index 23521070..9ba12ccf 100644 --- a/openwiki/architecture/supporting-system-directories.md +++ b/openwiki/architecture/supporting-system-directories.md @@ -8,26 +8,14 @@ sources: resource: repo://.github/workflows/openwiki-update.yml - id: openwiki-source-ed77bc20b48d193bace3dde6 resource: repo://.gitmodules - - id: openwiki-source-82dc2a330fa464405febe5a2 - resource: repo://AgentKit/Package.swift - - id: openwiki-source-7d407889e0fdf98a8da45d51 - resource: repo://AgentKit/Scripts/build-localizations.sh - - id: openwiki-source-a8f2957ebda6de083a5e705b - resource: repo://AgentKit/Sources/AgentKit/MCP/MCPClient.swift - - id: openwiki-source-0e8cfe7d74554d8a17af60b7 - resource: repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift - - id: openwiki-source-1b7f4621a0a6d022a3b65b1b - resource: repo://AgentKit/Sources/AgentKit/Security/AgentSecurity.swift - - id: openwiki-source-b3c829b218957268073c0f43 - resource: repo://AgentKit/Sources/AgentKit/Tools/AgentToolCatalog.swift - - id: openwiki-source-437ae5db92ccf61cdbffc8e9 - resource: repo://AgentKit/Sources/AgentKitScrubber/ScrubberWebClient.swift - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md - id: openwiki-source-7f5c1cb6f348742514987bf8 resource: repo://Benchmarks/PortForwarding/main.swift - id: openwiki-source-ea04d3d10088e2d554646adf resource: repo://Design/Brand/README.md + - id: openwiki-source-1c845e5af67c400566be83bd + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift - id: openwiki-source-d2d4c06f734241db3badc1f5 resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift - id: openwiki-source-581eed02dfaf161420cc4d9d @@ -81,7 +69,7 @@ sources: generated: { by: "omp", at: "2026-09-22T03:45:03.442Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T06:11:02.802Z --- # Supporting Products, Packages, and Tooling diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index 12544a4e..eef1b671 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -4,8 +4,6 @@ title: Assistant, Agent Runtime, Skills, and Memory description: How Termind assembles AgentKit into durable assistant conversations, applies approval and host-security boundaries, and manages skills, scratch workspaces, and device-local Hermes memory. tags: [assistant, agent-runtime, approval, persistence, skills, memory, ssh, sftp] sources: - - id: openwiki-source-0e8cfe7d74554d8a17af60b7 - resource: repo://AgentKit/Sources/AgentKit/Runtime/AgentRuntime.swift - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md - id: openwiki-source-685b7f8e7dbcf91b4e2a50e2 @@ -38,12 +36,16 @@ sources: resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/PlanningSSHTools.swift - id: openwiki-source-d2d4c06f734241db3badc1f5 resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/TermindAgentTools.swift + - id: openwiki-source-017f5e2abe9b13df755a5322 + resource: repo://FrameworkTests/AgentReplayPersistenceTests.swift - id: openwiki-source-6d759b4cb61f68a067dae42c resource: repo://FrameworkTests/AgentSteeringTests.swift - id: openwiki-source-247755d873c5d94b0a364217 resource: repo://FrameworkTests/AssistantConversationOperationsTests.swift - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift + - id: openwiki-source-105d05d558f14996921a83f6 + resource: repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved - id: openwiki-source-a62bfbbc7de13a894444718b resource: repo://TermindMCPBridge/main.swift - id: openwiki-source-906678adb91c2fce2d3fcaed @@ -78,10 +80,10 @@ sources: resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T03:40:50.823Z" } +generated: { by: "codex", at: "2026-09-23T06:11:02.802Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T03:40:50.823Z + at: 2026-09-23T06:11:02.802Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -94,7 +96,7 @@ This split is a security boundary as well as a modularity boundary. A model prop `AssistantExecutionController` is the application entry point for a submitted turn. Before recording a new turn it rejects overlapping starts, archived/history-changing conversations, active compaction, unfinished attachments, and unauthorized use. Accepted submission synchronously captures the composed prompt, images and context attachments; session, plan and surface context; chat model/provider/credential; reasoning and permission mode; search choice; skills and base registry; Guardian configuration; and memory/compaction preparation settings. Only then can asynchronous compaction or memory loading begin. Runtime assembly consumes this capture instead of re-reading mutable composer or configuration state after an await. A missing model or credential remains a captured failure even if Settings changes during preparation. -A runtime is deliberately single-run. It owns one event stream and one event consumer. `ConversationModel` owns the live runtime and projects its events into visible messages, tool cards, approval and input queues, run summaries, and the authoritative model transcript. The visible message list is not replay history: tool calls and results are separately rendered as cards, so replay must use `transcript`. +A runtime is deliberately single-run. `ConversationModel` retains a per-run owner containing the runtime, immutable request, repository, start phase, cancellation intent, and ordered event consumer. The controller supplies the same repository to the runtime and this owner. Only the active owner's events update visible messages, tool cards, approval and input queues, run summaries, and the authoritative model transcript; stopped owners remain retained only until their drain finishes. The visible message list is not replay history: tool calls and results are separately rendered as cards, so replay must use `transcript`. The tool catalog and plan posture are captured at submission; AgentKit selects that captured catalog's executable mode-specific subset when the runtime starts. A tool-capable model receives the captured skill catalog, AgentKit built-ins, and Termind tools; a model without tool-call capability does not receive the skill/tool catalog. `AgentTurnContextSnapshot` is the persistable context portion of the capture: session facts, skill catalog text, plan posture, surface availability, and memory-session identity. It is not a durable copy of provider credentials or all runtime configuration. Surface availability follows the latest delivered user snapshot rather than mutable UI focus. @@ -127,7 +129,7 @@ Desktop sidebar, standalone Assistant, and mobile first sends use the same contr Edits have a separate two-stage entry point: desktop and mobile UI call `prepareAgentEdit` synchronously before creating their UI `Task`. It preserves the original attachment payload and model-facing suffix while capturing fresh context and configuration for the replacement submission. `performAgentEdit` checks the capture identity before starting a tracked history task; persistence succeeds before the live edit is applied, and another identity check precedes runtime preparation. A cancelled or superseded prepared edit cannot later enter truncation or start a runtime. -Once the runtime begins, the submitted prompt is persisted as part of its running snapshot before model work. AgentKit streams a provider turn, emits ordered UI and durable events through a shared `AgentEventChannel`, validates any complete tool call, then schedules execution. Tool results are appended to the model transcript and persisted before the following model round. This preserves a grammatically answerable transcript even for refusal, cancellation, or failure paths. +Once the runtime begins, AgentKit awaits a save attempt for the running snapshot containing the submitted prompt before model work. It streams a provider turn, emits ordered UI and durable events through a shared `AgentEventChannel`, validates any complete tool call, then schedules execution. Tool results are appended to the model transcript and their snapshot save is attempted before the following model round. This preserves a grammatically answerable transcript for refusal, cancellation, or failure paths when persistence succeeds; runtime snapshot-save errors are currently swallowed. ```mermaid sequenceDiagram @@ -184,9 +186,17 @@ While a run is live, a further user message is queued as steering rather than st Steering captures the sending session context and updates only the `terminal_suggest` availability entry. It preserves other tool overrides, the fixed skill catalog and plan contract, and the memory-session identity. Queuing does not change `ConversationModel.activeContextSnapshot`; delivery does. If the conversation has no legacy snapshot, steering keeps that context unknown instead of filling it from current UI state. The persisted availability key remains `terminal_suggest`. -For an already-started runtime, stopping requests cancellation of runtime/tool work, discards run-scoped secrets, marks running cards interrupted in the presentation, and returns undelivered steering text. `stopAndWait()` joins preparation cancellation first, then requests runtime cancellation and waits for the event consumer before clearing live ownership. +### Runtime handoff and cancellation -The preparation guarantees do not eliminate the separate runtime handoff edge case: `ConversationModel.start` schedules its event consumer, and an immediate stop before that task calls `AgentRuntime.start` can prevent runtime startup without persisting a runtime snapshot. The ordering between `stopAndWait()` and this queued start remains a distinct boundary; do not infer universal cancellation durability from preparation tests. +`start` takes ownership synchronously before queuing runtime startup. If Stop arrives while that owner is still queued, it records cancellation intent synchronously and never calls `AgentRuntime.start`, so neither a provider nor a tool runs. Instead it creates a cancelled snapshot from the captured request, applying AgentKit's historical-secret sanitization and unanswered/orphaned-tool repairs. The snapshot retains the prompt identity, authored/model text, images, context attachments, explicit `request.contextSnapshot`, and permission mode without duplicating an existing prompt ID. This path does not reconstruct a missing context snapshot from runtime-only configuration; production assembly supplies its frozen context explicitly. + +The queued owner's synthetic run ID and start time are fixed at acceptance, and its finish time is captured when cancellation is requested. The prompt keeps its existing transcript timestamp or uses that acceptance time. An independent persistence task owns the snapshot and repository, so an explicit Stop still attempts the save after the presentation model is released. Live cancellation and staging adoption are projected synchronously; visible cancelled state alone is not evidence that the save has completed. + +Once startup has begun, Stop requests runtime cancellation; the consumer checks cancellation again after the awaited `AgentRuntime.start` handoff, covering cancellation that reached the actor before its run task existed. Stop immediately detaches the old owner's presentation, settles streaming text and nonterminal cards for that run, retires its approval/review presentation, and returns undelivered steering. Its event consumer remains uncancelled and drains to stream completion while ignoring events from the detached owner. A direct replacement start first cancels its predecessor. Owner identity also guards completion and late steering rejection, so old work cannot clear or stop a newer run. + +`stopAndWait()` captures preparation cancellation and all retained run owners before its first await, requests cancellation of the owner active at entry, then joins preparation and those consumers. Consequently `stop()` followed by `stopAndWait()` still joins the stopped run; a replacement accepted during the wait is outside that call's cancellation scope. Completed owners are removed from the retained collection. + +The join guarantees that the captured snapshot-save attempt has returned, not that storage succeeded. In pinned AgentKit, terminal events precede the terminal snapshot save, while stream completion follows it; a terminal event alone is therefore an earlier boundary. The separate durable-event recorder and run-secret disposal tasks are not joined by this consumer. These contracts do not promise recovery from a storage failure or process death before a save. Mobile background expiration uses the join before closing its background commands and Agent connections; desktop Stop keeps its immediate presentation behavior without adopting the mobile background lifecycle. ## Approval, secrets, and trust boundaries @@ -198,6 +208,8 @@ Approval is evaluated after schema validation and tool preflight, before executi `fullAccess` does not bypass schema validation, host-key trust, SSH authentication, secret binding, scratch containment, network/fetch restrictions, or tool-specific capability checks. Guardian review is only configured in `approveForMe`; it has a bounded timeout and failures, malformed output, inconsistent decisions, and critical-risk approvals fail closed. If the conversation disappears before a manual decision, the approval broker denies the request. +Manual approval waits check cancellation before enqueueing and install a cancellation handler that removes and denies only the matching request continuation. Stop removes the stopped run's approval/review presentation immediately; request-specific continuation cleanup then lets the runtime finish its cancellation snapshot without requiring a user decision. Other requests and a replacement run retain their own identities. + SSH authentication and host-key prompts are Termind interactions, not model-visible tool data. `AgentHostResolver` exposes public host metadata to `list_hosts`, but resolves Keychain-backed credentials into connection configuration without serializing them into tool results. Authentication context is shared only across overlapping attempts for the same destination; after successful authentication it clears staged submissions and cached secret-bearing configuration. Stale generations reject late callbacks and host-key decisions. `request_user_secret` is only for command standard input, not SSH login. Its handle is scoped to run, tool, host, and stated purpose, single-use, and is removed from replay and durable history. Plaintext secrets must not enter model requests, Guardian review, events, logs, SwiftData, or Hermes memory. @@ -238,7 +250,7 @@ Tool cleanup follows ownership before time. Known source-message and run-local c The replacement is durable before a runtime exists: its message ID also identifies an independent, completed, failure-free staging run containing that one user row and no tool graph. Its start time is placed after the latest retained run if clocks tie or move backward, including when the earlier retained run has no messages. On the first real snapshot containing that staged final user turn, `save` transactionally moves only the staged message to the real run, assigns its snapshot sequence, retains its attachments/replay backing, deletes the staging run and retires its ID. Historical messages replayed into the snapshot keep their existing owners and are not duplicated. Adoption preserves the staging start-time lower bound; subsequent snapshots cannot move it backward, and terminal time is clamped to that start when necessary. -`ConversationModel` mirrors adoption on `runStarted`, captured failure and preparation interruption; `runFinished` retains the adjusted chronology. A failed or interrupted replacement therefore has the real run's provenance rather than an abandoned staging summary. The ordering of publication still depends on the path: preparation cancellation awaits the interrupted save and rechecks identity before adopting, while `recordFailedRun` for a missing model/credential projects live first and starts a separate asynchronous save. Neither `!isPreparingAgentRun` nor a visible failed state proves that this save finished. The persist-before-live contract above is specifically the history mutation boundary. +`ConversationModel` mirrors adoption on `runStarted`, captured failure, preparation interruption, and queued-runtime cancellation; `runFinished` retains the adjusted chronology. Failed, interrupted, and cancelled replacements therefore use their actual run provenance rather than an abandoned staging summary. Publication order still depends on the path: preparation cancellation awaits the interrupted save and rechecks identity before adopting, while queued-runtime cancellation and `recordFailedRun` for a missing model/credential project live before their asynchronous save. Neither the end of preparation nor visible terminal state alone proves that persistence finished. For queued cancellation, `stopAndWait()` joins that save attempt. The persist-before-live contract above is specifically the history mutation boundary. The repository instance retires the edited target run, removed later runs and adopted staging run so late snapshots or durable tool events cannot resurrect the discarded suffix. Truncation and snapshot save roll back SwiftData changes and restore the previous in-memory retired-ID set on error. These rules use the existing schema and Termind's Application/Persistence adapters, without changing AgentKit or its package pin. See [Persistent Data](persistence-and-secrets.md) for the storage contract. @@ -296,14 +308,16 @@ The focused tests establish behavior that is important when changing this area: - `TFMTests/AgentRuntimeTests.swift` covers schema-before-approval/execution, approval mode routing, Guardian timeout/failure fail-closed behavior, single-use secret handles, steering delivery, tool cancellation, registry shadowing, and durable-event filtering. - `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, graph deletion, same-run edit prefix/tool preservation, canonical equal-time ordering, empty retained runs, staging adoption, late-write rejection, fork persistence and compaction restoration. `ChatComposerStateTests.swift` checks the compacted live prefix, tool cards, title, plans and adopted run summaries. -- `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption and reloads through a fresh `ModelContext` and repository to verify durable provenance and replay backing. +- `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption and queued cancellation of an edited replacement. The cancellation fixture reloads through a fresh `ModelContext` and repository, checking one adopted run, captured text/images/attachments/context, permission mode, and staging/terminal chronology. - `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, failed/interrupted replacement provenance, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. Repository snapshot waiters distinguish async save completion from a live failed state. -- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first archive/rename/order/edit mutations, invalid edit rejection before repository calls, failed edits leaving the live prefix unchanged, activity guards, and that immediate stop prevents a queued runtime's model call. That last test does not establish snapshot persistence for the runtime handoff gap. +- `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first history mutations and activity guards, plus queued Stop/Stop-and-Wait with zero provider/tool calls, captured cancelled snapshots, Stop followed by a blocked-save join, persistence after presentation release, direct queued-owner displacement, pending approval cancellation, and an old persistence drain finishing while a replacement remains running. Injected review events verify that the stopped run's review state and card do not contaminate that replacement. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. - `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. - `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. Mobile's SwiftData restore/edit fixture also verifies same-run prefix preservation and replacement provenance. - `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. +The queued-cancellation SwiftData regression uses an in-memory container with a fresh read context, not a process restart or an on-disk crash test. Barrier fixtures establish save-attempt ordering; they do not exercise storage failure. The review-state fixture injects presentation events rather than contacting a Guardian provider. Preserve these distinctions when choosing additional validation. + ## Change guidance When adding a tool, define a strict schema, choose an explicit tool policy and planning registration, validate/normalize inputs in preflight, and ensure the tool returns bounded/redacted data. Host mutations must remain behind the same authorization and trust checks whether invoked directly, through MCP, a skill, or a scratch workflow. Do not add a shortcut that serializes SSH credentials, secret input, raw scratch/remote file contents, or unredacted provider data to a transcript, event, log, SwiftData, or Hermes index. From 9c6a88a6a8c0787e818f5076985a9c45dbbb709e Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 14:18:56 +0800 Subject: [PATCH 16/30] Make StoreKit tests opt-in --- AGENTS.md | 2 ++ TFMTests/StoreKitTrialTests.swift | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 82899966..f9d64cba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,8 @@ Use Swift Testing (`@Suite`, `@Test`, `#expect`) in the three test directories. macOS tests that present windows or activate the app must use `.requiresVisibleWindow` on their suite or individual test. They are skipped by default; keep this opt-in off for routine verification. To opt in from the command line, prefix the test command with `TEST_RUNNER_TERMIND_WINDOW_TESTS=1` (Xcode forwards it as `TERMIND_WINDOW_TESTS=1` to the test host), preferably with `-only-testing:TFMTests/` to limit the run. In Xcode, set `TERMIND_WINDOW_TESTS=1` in the test action's environment. Screenshot tests with existing dedicated opt-in variables keep those gates. Offscreen layout and logic tests remain enabled by default. +StoreKit tests require manual authorization and are also skipped by default. Run them explicitly with `TERMIND_STOREKIT_TESTS=1`, preferably with `-only-testing:TFMTests/StoreKitTrialTests`. + ## Localization Guidelines Put user-facing app text in `Frameworks/Resources/Localizable.xcstrings` and bundle metadata in `InfoPlist.xcstrings`. Text owned by the agent runtime lives in `../AgentKit/Localizations/Localizable.xcstrings`; after editing it run `../AgentKit/Scripts/build-localizations.sh`, because `swift build` copies resources verbatim instead of compiling string catalogs. New or changed English strings require finished `zh-Hans` and `zh-Hant` translations in the same commit. Preserve placeholders and Chinese punctuation conventions. Before committing, run `Scripts/check-zh-localization.sh` (requires `jq`); it rejects invalid catalogs, missing or unfinished translations, and spacing errors. Run `AppLanguageTests` for localization behavior changes. diff --git a/TFMTests/StoreKitTrialTests.swift b/TFMTests/StoreKitTrialTests.swift index 9568b3a3..70892e85 100644 --- a/TFMTests/StoreKitTrialTests.swift +++ b/TFMTests/StoreKitTrialTests.swift @@ -11,7 +11,11 @@ import Testing @testable import Termind -@Suite(.serialized) +@Suite( + .enabled( + if: ProcessInfo.processInfo.environment["TERMIND_STOREKIT_TESTS"] == "1", + "Set TERMIND_STOREKIT_TESTS=1 to run StoreKit tests that require manual authorization"), + .serialized) @MainActor struct StoreKitTrialTests { @Test From 80505ccc0e0d72d8509b8d0d636f3c949f95c8a9 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 14:37:16 +0800 Subject: [PATCH 17/30] Select Workspace name when renaming --- .../Workspaces/WorkspaceNavigatorView.swift | 2 +- TFMTests/WorkspaceNavigatorRenameTests.swift | 131 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 TFMTests/WorkspaceNavigatorRenameTests.swift diff --git a/TFM/Features/Workspaces/WorkspaceNavigatorView.swift b/TFM/Features/Workspaces/WorkspaceNavigatorView.swift index 437ad7dd..82e507a1 100644 --- a/TFM/Features/Workspaces/WorkspaceNavigatorView.swift +++ b/TFM/Features/Workspaces/WorkspaceNavigatorView.swift @@ -323,8 +323,8 @@ struct WorkspaceNavigatorView: View { private func beginWorkspaceRename(_ workspace: Workspace) { guard workspace.isRenameable else { return } - renamingWorkspaceID = workspace.id workspaceRenameTitle = workspace.title(in: app.hostLibrary, locale: locale) + renamingWorkspaceID = workspace.id } private func commitWorkspaceRename(_ workspace: Workspace) { diff --git a/TFMTests/WorkspaceNavigatorRenameTests.swift b/TFMTests/WorkspaceNavigatorRenameTests.swift new file mode 100644 index 00000000..d0196015 --- /dev/null +++ b/TFMTests/WorkspaceNavigatorRenameTests.swift @@ -0,0 +1,131 @@ +import AppKit +import SwiftUI +import TermindApplication +import TermindDomain +import Testing + +@testable import Termind + +@Suite(.requiresVisibleWindow, .serialized) @MainActor +struct WorkspaceNavigatorRenameTests { + @Test func contextMenuRenameStartsWithTheCompleteWorkspaceTitle() async throws { + let fixture = WorkspaceNavigatorRenameFixture() + defer { fixture.close() } + try await fixture.prepare() + + let (_, menu) = try fixture.workspaceMenu() + let renameIndex = try #require(menu.items.firstIndex { $0.title == "Rename" }) + menu.performActionForItem(at: renameIndex) + + try await fixture.expectCompleteTitle() + } + + @Test func doubleClickRenameStartsWithTheCompleteWorkspaceTitle() async throws { + let fixture = WorkspaceNavigatorRenameFixture() + defer { fixture.close() } + try await fixture.prepare() + + let (menuView, _) = try fixture.workspaceMenu() + fixture.doubleClick(menuView) + + try await fixture.expectCompleteTitle() + } +} + +@MainActor +private final class WorkspaceNavigatorRenameFixture { + private let title = "Production Workspace" + private let app: AppModel + private let controller: NSHostingController + private let window: NSWindow + + init() { + let host = TermindDomain.Host(name: title, host: "workspace.invalid") + app = AppModel(hosts: HostLibraryModel(hosts: [host])) + let navigation = WindowModel(workspaces: [.sshSession(host: host)]) + controller = NSHostingController( + rootView: AnyView( + WorkspaceNavigatorView() + .environment(app) + .environment(navigation) + .environment(\.locale, Locale(identifier: "en")) + .frame(width: 280, height: 640) + )) + window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 280, height: 640), + styleMask: [.titled], backing: .buffered, defer: false) + window.isReleasedWhenClosed = false + controller.sizingOptions = [] + window.contentViewController = controller + controller.view.frame = window.contentView?.bounds ?? .zero + } + + func prepare() async throws { + window.orderFrontRegardless() + try await Task.sleep(for: .milliseconds(250)) + controller.view.layoutSubtreeIfNeeded() + } + + func workspaceMenu() throws -> (NativeRowContextMenu.RowMenuView, NSMenu) { + for menuView in descendants(of: controller.view).compactMap({ $0 as? NativeRowContextMenu.RowMenuView }) { + guard let menu = menuView.makeMenu() else { continue } + let titles = Set(menu.items.map(\.title)) + if titles.contains("Save Workspace Template"), titles.contains("Rename") { + return (menuView, menu) + } + } + Issue.record("Workspace row context menu was not found") + throw WorkspaceNavigatorRenameFixtureError.missingWorkspaceMenu + } + + func doubleClick(_ view: NSView) { + let location = view.convert(NSPoint(x: view.bounds.midX, y: view.bounds.midY), to: nil) + sendMouseEvent(.leftMouseDown, at: location, clickCount: 1, timestamp: 1) + sendMouseEvent(.leftMouseUp, at: location, clickCount: 1, timestamp: 1.05) + sendMouseEvent(.leftMouseDown, at: location, clickCount: 2, timestamp: 1.1) + sendMouseEvent(.leftMouseUp, at: location, clickCount: 2, timestamp: 1.15) + } + + func expectCompleteTitle() async throws { + try await Task.sleep(for: .milliseconds(150)) + controller.view.layoutSubtreeIfNeeded() + // SidebarInlineRenameField selects its initial value when it receives focus. The + // Workspace regression was constructing that field before this full title arrived. + _ = try #require( + descendants(of: controller.view).compactMap { $0 as? NSTextField } + .first { $0.isEditable && $0.stringValue == title }) + } + + func close() { + app.shutdownForTermination() + window.orderOut(nil) + window.contentViewController = nil + window.close() + } + + private func sendMouseEvent( + _ type: NSEvent.EventType, + at location: NSPoint, + clickCount: Int, + timestamp: TimeInterval + ) { + guard + let event = NSEvent.mouseEvent( + with: type, location: location, modifierFlags: [], timestamp: timestamp, + windowNumber: window.windowNumber, context: nil, eventNumber: 0, + clickCount: clickCount, pressure: type == .leftMouseDown ? 1 : 0) + else { + Issue.record("Could not create a synthetic mouse event") + return + } + window.sendEvent(event) + } + + private func descendants(of view: NSView) -> [NSView] { + [view] + view.subviews.flatMap(descendants) + } +} + +private enum WorkspaceNavigatorRenameFixtureError: Error { + case missingWorkspaceMenu +} From 7e8427d94de0687fddfba5b0d4571ff3966ee960 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 14:54:19 +0800 Subject: [PATCH 18/30] Join captured Assistant failure persistence --- .../AgentReplayPersistenceTests.swift | 74 +++++ .../AssistantExecutionControllerTests.swift | 292 +++++++++++++++++- .../Agent/AssistantExecutionController.swift | 101 +++--- .../Agent/ConversationModel.swift | 19 +- 4 files changed, 435 insertions(+), 51 deletions(-) diff --git a/FrameworkTests/AgentReplayPersistenceTests.swift b/FrameworkTests/AgentReplayPersistenceTests.swift index 8447c688..547720ce 100644 --- a/FrameworkTests/AgentReplayPersistenceTests.swift +++ b/FrameworkTests/AgentReplayPersistenceTests.swift @@ -193,6 +193,80 @@ struct AgentReplayPersistenceTests { #expect(runRows.first?.permissionMode == AgentPermissionMode.fullAccess.rawValue) } + @Test + func capturedConfigurationFailureAdoptsEditedStagingOnlyAfterDurableSave() async throws { + let container = try TermindPersistence.makeContainer(inMemory: true) + let repository = SwiftDataAgentRunRepository(context: container.mainContext) + let conversationID = UUID() + let image = AgentImageAttachment( + label: "capture.png", mimeType: "image/png", data: Data([4, 5, 6]), + pixelWidth: 2, pixelHeight: 2 + ) + let attachment = AgentContextAttachment( + id: UUID(), kind: .file, label: "capture.txt", + contentTypeIdentifier: "public.plain-text", suggestedFilename: "capture.txt", + data: Data("captured payload".utf8) + ) + let original = AgentTranscriptMessage( + role: .user, text: "before", authoredText: "before", + images: [image], contextAttachments: [attachment] + ) + try await repository.save( + AgentRunSnapshot( + conversationID: conversationID, state: .completed, + permissionMode: .askForApproval, messages: [original] + )) + let stored = try #require(try await repository.loadConversations().first) + let store = AssistantConversationStore() + store.restore([stored]) + let conversation = try #require(store.conversations.first) + conversation.permissionMode = .fullAccess + let controller = AssistantExecutionController( + ai: AIConfigurationModel(), skills: SkillLibraryModel(), repository: repository, + authorize: { true }, + makeRegistry: { _, _, _, _, _ in AgentToolRegistry([]) }, + title: { _ in "Title" } + ) + let operations = AssistantConversationOperations(repository: repository) + let prepared = try #require( + controller.prepareAgentEdit( + conversation, messageID: original.id, text: "after", + context: SessionContext(hostName: "captured-host", workingDirectory: "/captured"), + terminalSuggestionsAvailable: true + )) + + #expect(try await controller.performAgentEdit(prepared, in: conversation, operations: operations)) + let replacement = try #require(conversation.transcript.first) + let staging = try #require(conversation.runSummaries[replacement.id]) + await conversation.stopAndWait() + + #expect(conversation.runState == .failed) + #expect(conversation.runSummaries[replacement.id] == nil) + + let freshRepository = SwiftDataAgentRunRepository(context: ModelContext(container)) + let restored = try #require(try await freshRepository.loadConversations().first) + let run = try #require(restored.runs.first) + let message = try #require(restored.messages.first) + #expect(restored.runs.count == 1) + #expect(run.id != replacement.id) + #expect(run.state == .failed) + #expect(run.failure == String(localized: "No Chat model is configured.")) + #expect(run.startedAt >= staging.startedAt) + #expect(run.finishedAt.map { $0 >= run.startedAt } == true) + #expect(restored.messageRunIDs[replacement.id] == run.id) + #expect(message.id == replacement.id) + #expect(message.text == replacement.text) + #expect(message.authoredText == "after") + #expect(message.images == [image]) + #expect(message.contextAttachments == [attachment]) + #expect(message.contextSnapshot?.sessionContext?.contains("captured-host") == true) + #expect(message.contextSnapshot?.toolAvailability?["terminal_suggest"] == true) + #expect(message.createdAt <= (run.finishedAt ?? .distantPast)) + let runRows = try ModelContext(container).fetch(FetchDescriptor()) + #expect(runRows.count == 1) + #expect(runRows.first?.permissionMode == AgentPermissionMode.fullAccess.rawValue) + } + @Test func v15MigratesWithoutInventingHistoricalContext() async throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) diff --git a/FrameworkTests/AssistantExecutionControllerTests.swift b/FrameworkTests/AssistantExecutionControllerTests.swift index fef4d96e..bbcace85 100644 --- a/FrameworkTests/AssistantExecutionControllerTests.swift +++ b/FrameworkTests/AssistantExecutionControllerTests.swift @@ -85,6 +85,151 @@ struct AssistantExecutionControllerTests { #expect(await repository.snapshots.last?.state == .failed) } + @Test + func missingModelFailureStaysOwnedUntilSaveAndAllCancellationPathsJoinIt() async throws { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let controller = makeController(repository: repository) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await repository.waitUntilSaveEntered() + + #expect(conversation.isPreparingAgentRun) + #expect(conversation.isPersistingAgentPreparationFailure) + #expect(conversation.runState != .failed) + #expect(conversation.runSummaries.isEmpty) + + conversation.stop() + let probe = JoinProbe(expectedStarts: 2) + let joinedStop = Task { @MainActor in + probe.started() + await conversation.stopAndWait() + probe.completed() + } + let joinedControllerCancellation = Task { @MainActor in + probe.started() + await controller.cancelPendingRuns().value + probe.completed() + } + try await waitUntil { probe.startedCount == probe.expectedStarts } + + #expect(conversation.isPreparingAgentRun) + #expect(await repository.snapshots.map(\.state) == [.failed]) + #expect(await repository.completedSnapshots.isEmpty) + #expect(probe.completedCount == 0) + + await repository.releaseSave() + await joinedStop.value + await joinedControllerCancellation.value + + #expect(!conversation.isPreparingAgentRun) + #expect(!conversation.isPersistingAgentPreparationFailure) + #expect(conversation.runState == .failed) + #expect(await repository.snapshots.map(\.state) == [.failed]) + #expect(await repository.completedSnapshots.map(\.state) == [.failed]) + #expect(probe.completedCount == 2) + } + + @Test + func capturedFailureSaveOutlivesReleasedControllerAndConversationHandles() async throws { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + var controller: AssistantExecutionController? = makeController(repository: repository) + var conversation: ConversationModel? = ConversationModel() + conversation?.composer.draft = "persist me" + let releasedController = WeakReference(controller) + let releasedConversation = WeakReference(conversation) + + #expect(controller?.sendAgentDraft(try #require(conversation)) == true) + await repository.waitUntilSaveEntered() + controller = nil + conversation = nil + + #expect(releasedController.value != nil) + #expect(releasedConversation.value != nil) + + await repository.releaseSave() + await repository.waitUntilSaveCompleted() + try await waitUntil { + releasedController.value == nil && releasedConversation.value == nil + } + #expect(await repository.completedSnapshots.map(\.state) == [.failed]) + } + + @Test + func missingCredentialAfterMemoryPreparationProjectsOnlyAfterSaveCompletes() async throws { + let memoryStore = SnapshotBarrierMemoryStore() + let memory = MemoryModel(storage: memoryStore) + await memory.setEnabled(true) + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let (ai, _) = configuredAI(secret: nil) + let controller = makeController(ai: ai, repository: repository, memory: memory) + let conversation = ConversationModel() + conversation.composer.draft = "inspect" + + #expect(controller.sendAgentDraft(conversation)) + await memoryStore.waitUntilSnapshotEntered() + await memoryStore.releaseSnapshot() + await repository.waitUntilSaveEntered() + + #expect(conversation.isPreparingAgentRun) + #expect(conversation.isPersistingAgentPreparationFailure) + #expect(conversation.runState != .failed) + + await repository.releaseSave() + try await waitUntil { !conversation.isPreparingAgentRun } + + #expect(conversation.runState == .failed) + #expect(await repository.completedSnapshots.map(\.state) == [.failed]) + } + + @Test + func archiveAndDeleteWaitForCapturedFailurePersistence() async throws { + do { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let conversation = ConversationModel() + conversation.composer.draft = "archive me" + + #expect(controller.sendAgentDraft(conversation)) + await repository.waitUntilSaveEntered() + let archive = Task { + try await operations.setArchived(true, conversation: conversation) + } + await Task.yield() + + #expect(await repository.metadataMutationCount == 0) + #expect(conversation.archivedAt == nil) + + await repository.releaseSave() + try await archive.value + #expect(await repository.metadataMutationCount == 1) + #expect(await repository.events == ["save-attempt", "save-completed", "metadata"]) + #expect(conversation.archivedAt != nil) + } + + do { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let conversation = ConversationModel() + conversation.composer.draft = "delete me" + + #expect(controller.sendAgentDraft(conversation)) + await repository.waitUntilSaveEntered() + let deletion = Task { try await operations.delete(conversation) } + await Task.yield() + + #expect(await repository.deletionCount == 0) + + await repository.releaseSave() + #expect(try await deletion.value.deleted) + #expect(await repository.deletionCount == 1) + #expect(await repository.events == ["save-attempt", "save-completed", "delete"]) + } + } + @Test func immediateStopInvalidatesPreparationBeforeTheMemoryBarrierIsReleased() async throws { let memoryStore = SnapshotBarrierMemoryStore() @@ -493,6 +638,7 @@ struct AssistantExecutionControllerTests { #expect(edited.authoredText == "after") #expect(edited.contextSnapshot?.sessionContext?.contains("captured-host") == true) #expect(edited.contextSnapshot?.planContract == TermindAgentPolicy.planContract) + await conversation.stopAndWait() #expect(conversation.runState == .failed) let failed = try #require(conversation.runSummaries.values.first) #expect(conversation.runSummaries.count == 1) @@ -502,6 +648,73 @@ struct AssistantExecutionControllerTests { #expect(conversation.runSummaries[edited.id] == nil) } + @Test + func failedReplacementSaveCannotBeSupersededOrMutatedBeforeItCompletes() async throws { + let repository = RecordingAgentRunRepository(blocksFirstSave: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let original = AgentTranscriptMessage(role: .user, text: "opening", authoredText: "opening") + let answer = AgentTranscriptMessage(role: .assistant, text: "answer") + let later = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ + ChatMessage(id: original.id, role: .user, text: original.text), + ChatMessage(id: answer.id, role: .assistant, text: answer.text), + ChatMessage(id: later.id, role: .user, text: later.text), + ], transcript: [original, answer, later]) + let prepared = try #require( + controller.prepareAgentEdit(conversation, messageID: later.id, text: "after")) + + #expect(try await controller.performAgentEdit(prepared, in: conversation, operations: operations)) + await repository.waitUntilSaveEntered() + let edited = try #require(conversation.transcript.last) + + #expect(conversation.messageRunIDs[edited.id] == edited.id) + #expect(conversation.transcript.contains(where: { $0.id == answer.id })) + #expect(controller.prepareAgentEdit(conversation, messageID: edited.id, text: "newer") == nil) + #expect( + try await operations.edit(conversation, messageID: edited.id, text: "bypass") == nil) + #expect(try await operations.fork(conversation, at: answer.id, title: "Bypass") == nil) + #expect(await repository.truncationCount == 1) + + await repository.releaseSave() + await conversation.stopAndWait() + + #expect(conversation.runState == .failed) + #expect(conversation.messageRunIDs[edited.id] != edited.id) + #expect(conversation.runSummaries[edited.id] == nil) + } + + @Test + func failedSnapshotSaveRetiresPreparationWithoutAdoptingStaging() async throws { + let repository = RecordingAgentRunRepository(blocksFirstSave: true, failsFirstSave: true) + let controller = makeController(repository: repository) + let operations = AssistantConversationOperations(repository: repository) + let original = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let conversation = ConversationModel( + messages: [ChatMessage(id: original.id, role: .user, text: original.text)], + transcript: [original]) + let prepared = try #require( + controller.prepareAgentEdit(conversation, messageID: original.id, text: "after")) + + #expect(try await controller.performAgentEdit(prepared, in: conversation, operations: operations)) + await repository.waitUntilSaveEntered() + let edited = try #require(conversation.transcript.first) + let staging = try #require(conversation.runSummaries[edited.id]) + #expect(conversation.messageRunIDs[edited.id] == edited.id) + + await repository.releaseSave() + try await waitUntil { !conversation.isPreparingAgentRun } + + #expect(!conversation.isPersistingAgentPreparationFailure) + #expect(conversation.messageRunIDs[edited.id] == edited.id) + #expect(conversation.runSummaries[edited.id] == staging) + #expect(conversation.runSummaries.values.allSatisfy { $0.state != .failed }) + #expect(conversation.messages.last?.role == .assistant) + #expect(conversation.messages.last?.text == CocoaError(.fileWriteNoPermission).localizedDescription) + #expect(await repository.completedSnapshots.isEmpty) + } + @Test func cancellationAfterSuccessfulEditAdoptsTheInterruptedRunProvenance() async throws { let memoryStore = SnapshotBarrierMemoryStore() @@ -573,6 +786,33 @@ struct AssistantExecutionControllerTests { private struct TestTimeoutError: Error {} +private final class WeakReference { + weak var value: Value? + + init(_ value: Value?) { + self.value = value + } +} + +@MainActor +private final class JoinProbe { + let expectedStarts: Int + private(set) var startedCount = 0 + private(set) var completedCount = 0 + + init(expectedStarts: Int) { + self.expectedStarts = expectedStarts + } + + func started() { + startedCount += 1 + } + + func completed() { + completedCount += 1 + } +} + @MainActor private final class RegistryCapture { var modelID: String? @@ -635,14 +875,21 @@ private actor SnapshotBarrierMemoryStore: MemoryPersisting { private actor RecordingAgentRunRepository: AgentRunPersisting { private(set) var snapshots: [AgentRunSnapshot] = [] + private(set) var completedSnapshots: [AgentRunSnapshot] = [] + private(set) var events: [String] = [] private(set) var truncationReplacement: AgentTranscriptMessage? + private(set) var truncationCount = 0 + private(set) var metadataMutationCount = 0 + private(set) var deletionCount = 0 private let failTruncation: Bool private let blocksFirstSave: Bool + private let failsFirstSave: Bool private let blocksTruncation: Bool private var saveEntered = false private var saveReleased = false private var saveEntryWaiters: [CheckedContinuation] = [] private var saveReleaseWaiters: [CheckedContinuation] = [] + private var saveCompletionWaiters: [CheckedContinuation] = [] private var snapshotWaiters: [(state: AgentRunState, continuation: CheckedContinuation)] = [] private var truncationEntered = false private var truncationReleased = false @@ -651,15 +898,17 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { init( failTruncation: Bool = false, blocksFirstSave: Bool = false, - blocksTruncation: Bool = false + blocksTruncation: Bool = false, failsFirstSave: Bool = false ) { self.failTruncation = failTruncation self.blocksFirstSave = blocksFirstSave self.blocksTruncation = blocksTruncation + self.failsFirstSave = failsFirstSave } - func save(_ snapshot: AgentRunSnapshot) async { + func save(_ snapshot: AgentRunSnapshot) async throws { snapshots.append(snapshot) + events.append("save-attempt") var pendingSnapshotWaiters: [(state: AgentRunState, continuation: CheckedContinuation)] = [] for waiter in snapshotWaiters { if waiter.state == snapshot.state { @@ -669,14 +918,23 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { } } snapshotWaiters = pendingSnapshotWaiters - guard blocksFirstSave, snapshots.count == 1 else { return } - saveEntered = true - let entryWaiters = saveEntryWaiters - saveEntryWaiters.removeAll() - for waiter in entryWaiters { waiter.resume() } - if !saveReleased { - await withCheckedContinuation { saveReleaseWaiters.append($0) } + if blocksFirstSave, snapshots.count == 1 { + saveEntered = true + let entryWaiters = saveEntryWaiters + saveEntryWaiters.removeAll() + for waiter in entryWaiters { waiter.resume() } + if !saveReleased { + await withCheckedContinuation { saveReleaseWaiters.append($0) } + } } + if failsFirstSave, snapshots.count == 1 { + throw CocoaError(.fileWriteNoPermission) + } + completedSnapshots.append(snapshot) + events.append("save-completed") + let completionWaiters = saveCompletionWaiters + saveCompletionWaiters.removeAll() + for waiter in completionWaiters { waiter.resume() } } func waitUntilSaveEntered() async { @@ -689,6 +947,11 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { await withCheckedContinuation { snapshotWaiters.append((state, $0)) } } + func waitUntilSaveCompleted() async { + if !completedSnapshots.isEmpty { return } + await withCheckedContinuation { saveCompletionWaiters.append($0) } + } + func releaseSave() { saveReleased = true let waiters = saveReleaseWaiters @@ -711,14 +974,21 @@ private actor RecordingAgentRunRepository: AgentRunPersisting { func record(_ event: AgentEvent, runID: UUID) {} func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} func loadConversations() -> [StoredAgentConversation] { [] } - func deleteConversation(_ id: UUID) {} + func deleteConversation(_ id: UUID) { + deletionCount += 1 + events.append("delete") + } func setTitle(_ title: String, conversationID: UUID) {} - func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) { + metadataMutationCount += 1 + events.append("metadata") + } func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} func truncateConversation( _ id: UUID, fromUserMessageID messageID: UUID, replacingWith message: AgentTranscriptMessage ) async throws { + truncationCount += 1 if blocksTruncation { truncationEntered = true let waiters = truncationEntryWaiters diff --git a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift index eaea75df..33bb71bf 100644 --- a/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift +++ b/Frameworks/TermindApplication/Agent/AssistantExecutionController.swift @@ -50,9 +50,15 @@ public final class AssistantExecutionController { } private struct PreparationState { + enum Phase { + case preparing + case savingFailure + } + var id: UUID var conversation: ConversationModel var permissionMode: AgentPermissionMode + var phase = Phase.preparing var task: Task? var historyTask: Task? var cancellationTask: Task? @@ -60,6 +66,12 @@ public final class AssistantExecutionController { var cancellationRequested = false } + private enum StartResult { + case runtimeStarted + case failurePersistenceStarted + case abandoned + } + public struct PreparedAgentEdit { fileprivate var capture: RunCapture fileprivate var replacement: AgentTranscriptMessage @@ -339,10 +351,10 @@ public final class AssistantExecutionController { private func performStartAgentRun( _ conversation: ConversationModel, capture: RunCapture, memorySession: (context: AgentMemoryContext, access: any AgentMemoryAccessing)? = nil - ) { + ) -> StartResult { guard isCurrent(capture, for: conversation), !conversation.isStreaming, !conversation.isArchived, !conversation.isHistoryChanging - else { return } + else { return .abandoned } let operationID = UUID().uuidString.lowercased() let conversationID = conversation.id.uuidString.lowercased() guard let chat = capture.chat else { @@ -352,15 +364,11 @@ public final class AssistantExecutionController { "conversation_id": "\(conversationID)", "error_code": "missing_model", "operation_id": "\(operationID)", "outcome": "failure", "stage": "preparing", ]) - recordFailedRun( - conversation, - promptID: capture.prompt.id, prompt: capture.prompt.text, - authoredPrompt: capture.prompt.authoredText, images: capture.prompt.images, - contextAttachments: capture.prompt.contextAttachments, - permissionMode: capture.permissionMode, + beginFailedRunPersistence( + conversation, capture: capture, reason: String(localized: "No Chat model is configured.") ) - return + return .failurePersistenceStarted } guard let secret = capture.secret else { logger.error( @@ -369,15 +377,11 @@ public final class AssistantExecutionController { "conversation_id": "\(conversationID)", "error_code": "missing_credential", "operation_id": "\(operationID)", "outcome": "failure", "stage": "preparing", ]) - recordFailedRun( - conversation, - promptID: capture.prompt.id, prompt: capture.prompt.text, - authoredPrompt: capture.prompt.authoredText, images: capture.prompt.images, - contextAttachments: capture.prompt.contextAttachments, - permissionMode: capture.permissionMode, + beginFailedRunPersistence( + conversation, capture: capture, reason: String(localized: "The Chat model has no available credential.") ) - return + return .failurePersistenceStarted } let model = AgentProviderClient( @@ -391,7 +395,7 @@ public final class AssistantExecutionController { // events had no defined order against the tool events they belong // between, and its durable writes had no defined order against // anything. See `AgentEventChannel`. - guard let channel = capture.channel, let baseRegistry = capture.baseRegistry else { return } + guard let channel = capture.channel, let baseRegistry = capture.baseRegistry else { return .abandoned } let skillCatalog = capture.skillCatalog let skillRegistry = AgentToolCatalog.registry( builtIn: .init( @@ -508,6 +512,7 @@ public final class AssistantExecutionController { "planning": "\(isPlanning)", "stage": "running", "tool_count": "\(registry.registeredTools.count)", ]) scheduleTitle(for: conversation, isPlanning: isPlanning, operationID: operationID) + return .runtimeStarted } private func canSubmit(to conversation: ConversationModel) -> Bool { @@ -574,6 +579,7 @@ public final class AssistantExecutionController { } if supersedePreparation { if let current = preparations[conversation.id] { + guard current.phase != .savingFailure else { return nil } _ = requestCancellation( for: conversation, expectedID: current.id, recordsInterruption: false) @@ -597,8 +603,12 @@ public final class AssistantExecutionController { guard isCurrent(capture, for: conversation) else { return } let needsAsync = (compactBeforeRun && capture.shouldCompact) || capture.memorySessionID != nil guard needsAsync else { - performStartAgentRun(conversation, capture: capture) - retirePreparation(capture.id, for: conversation) + switch performStartAgentRun(conversation, capture: capture) { + case .runtimeStarted, .abandoned: + retirePreparation(capture.id, for: conversation) + case .failurePersistenceStarted: + break + } return } let task = Task { [weak self, weak conversation] in @@ -620,8 +630,12 @@ public final class AssistantExecutionController { await finishCancelledPreparation(capture, for: conversation) return } - performStartAgentRun(conversation, capture: capture, memorySession: memorySession) - retirePreparation(capture.id, for: conversation) + switch performStartAgentRun(conversation, capture: capture, memorySession: memorySession) { + case .runtimeStarted, .abandoned: + retirePreparation(capture.id, for: conversation) + case .failurePersistenceStarted: + break + } } guard var state = preparations[conversation.id], state.id == capture.id else { task.cancel() @@ -690,6 +704,9 @@ public final class AssistantExecutionController { guard var state = preparations[conversation.id], expectedID == nil || state.id == expectedID else { return Task {} } + if state.phase == .savingFailure { + return state.task ?? Task {} + } state.recordsInterruption = state.recordsInterruption || recordsInterruption state.cancellationRequested = true state.task?.cancel() @@ -764,35 +781,43 @@ public final class AssistantExecutionController { retirePreparation(preparationID, for: conversation) } - private func recordFailedRun( - _ conversation: ConversationModel, - promptID: UUID, - prompt: String, - authoredPrompt: String?, - images: [AgentImageAttachment], - contextAttachments: [AgentContextAttachment], - permissionMode: AgentPermissionMode, - reason: String + private func beginFailedRunPersistence( + _ conversation: ConversationModel, capture: RunCapture, reason: String ) { + guard var state = preparations[conversation.id], state.id == capture.id else { return } var messages = conversation.pendingPriorMessages - if !messages.contains(where: { $0.id == promptID }) { + if !messages.contains(where: { $0.id == capture.prompt.id }) { messages.append( AgentTranscriptMessage( - id: promptID, role: .user, text: prompt, - authoredText: authoredPrompt, images: images, - contextAttachments: contextAttachments, - contextSnapshot: conversation.transcript.first(where: { $0.id == promptID })?.contextSnapshot + id: capture.prompt.id, role: .user, text: capture.prompt.text, + authoredText: capture.prompt.authoredText, images: capture.prompt.images, + contextAttachments: capture.prompt.contextAttachments, + contextSnapshot: capture.contextSnapshot )) } let now = Date.now let snapshot = AgentRunSnapshot( conversationID: conversation.id, state: .failed, - permissionMode: permissionMode, + permissionMode: capture.permissionMode, messages: messages, startedAt: now, finishedAt: now, failure: reason ) - conversation.recordFailedRun(snapshot) - Task { [agentRunRepository] in try? await agentRunRepository.save(snapshot) } + conversation.beginAgentPreparationFailurePersistence(id: capture.id) + state.phase = .savingFailure + let preparationID = capture.id + let task = Task { @MainActor [self, conversation, agentRunRepository] in + do { + try await agentRunRepository.save(snapshot) + guard preparations[conversation.id]?.id == preparationID else { return } + conversation.recordFailedRun(snapshot) + } catch { + guard preparations[conversation.id]?.id == preparationID else { return } + conversation.messages.append(ChatMessage(role: .assistant, text: error.localizedDescription)) + } + retirePreparation(preparationID, for: conversation) + } + state.task = task + preparations[conversation.id] = state } public func contextSnapshot( diff --git a/Frameworks/TermindApplication/Agent/ConversationModel.swift b/Frameworks/TermindApplication/Agent/ConversationModel.swift index f0312537..027e6263 100644 --- a/Frameworks/TermindApplication/Agent/ConversationModel.swift +++ b/Frameworks/TermindApplication/Agent/ConversationModel.swift @@ -884,6 +884,11 @@ public final class ConversationModel: Identifiable { /// there is no runtime to accept steering until preparation completes. public private(set) var isPreparingAgentRun = false + /// True only after a captured configuration failure has committed to its + /// terminal snapshot save. Ordinary prepared edits remain supersedable, + /// while history mutations must not overtake this durable boundary. + public private(set) var isPersistingAgentPreparationFailure = false + public var isAgentRunActive: Bool { isStreaming || isPreparingAgentRun } /// The current task list, whole. Replaced rather than patched — see @@ -1081,12 +1086,16 @@ public final class ConversationModel: Identifiable { } public func canEditUserMessage(_ id: UUID) -> Bool { - guard !isStreaming, !isCompacting, !isArchived, !isHistoryChanging else { return false } + guard !isStreaming, !isCompacting, !isArchived, !isHistoryChanging, + !isPersistingAgentPreparationFailure + else { return false } return transcript.contains { $0.id == id && $0.role == .user && !$0.isCompaction } } public func canForkAssistantMessage(_ id: UUID) -> Bool { - guard !isStreaming, !isCompacting, !isArchived, !isHistoryChanging else { return false } + guard !isStreaming, !isCompacting, !isArchived, !isHistoryChanging, + !isPersistingAgentPreparationFailure + else { return false } return transcript.contains { $0.id == id && $0.role == .assistant } } @@ -2032,6 +2041,12 @@ public final class ConversationModel: Identifiable { agentPreparationID = nil cancelAgentPreparation = nil isPreparingAgentRun = false + isPersistingAgentPreparationFailure = false + } + + func beginAgentPreparationFailurePersistence(id: UUID) { + guard agentPreparationID == id else { return } + isPersistingAgentPreparationFailure = true } @discardableResult From 10079bd576e124f67b8399f6ff52bebd778e78c3 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 15:00:53 +0800 Subject: [PATCH 19/30] Document captured Assistant failure persistence --- .../architecture/engineering-invariants.json | 60 ++++--- .../architecture/system-boundaries.json | 6 +- .../concepts/agent-runtime-and-assistant.json | 160 +++++++++++------- .../concepts/persistence-and-secrets.json | 40 ++++- openwiki/.claims/quickstart.json | 20 ++- .../behavioral-boundaries-and-fixtures.json | 94 ++++++++-- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 54 +++--- .../architecture/engineering-invariants.md | 15 +- openwiki/architecture/system-boundaries.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 22 ++- openwiki/concepts/persistence-and-secrets.md | 10 +- openwiki/quickstart.md | 8 +- .../behavioral-boundaries-and-fixtures.md | 21 ++- 14 files changed, 345 insertions(+), 171 deletions(-) diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index 34bb413e..9a57af47 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:f62db2005beea84e609b860cb6d2e52e7a742faf98e3d9f088179b031465fbbe", + "pageVersion": "sha256:c8ad1f11ac788af965dd38b668950c45dda95ab336ac725cd1d9ab3e8f7ce8a8", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -332,37 +332,37 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L516-L630", - "version": "repo-lines-v1:sha256:b1882f48ba2a0c4f0faaa010c3ad73fbbfed243a2f0f51c0d883ed8058e5a3d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" + "version": "repo-lines-v1:sha256:6f0586148546ed005af984580253f2dc7d5de8689160a50bb5889c18111981cb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" } ] }, { "id": "claim_bbae8e4110c5473b83d5d2803745fea5", - "statement": "Preparation has a separate identity from streaming: cancellation invalidates it synchronously and joins tracked history/preparation work before interrupted persistence; stale edits are rejected, archive/delete cancel preparation, manual compaction refuses it, and changing selection alone does not cancel it.", + "statement": "Preparation has a separate identity from streaming: ordinary cancellation invalidates it synchronously and joins tracked history/preparation work before interrupted persistence; savingFailure instead joins its existing terminal save without cancellation or an added interrupted write. Archive/delete await preparation, edits/forks cannot overtake failure persistence, manual compaction refuses preparation, and changing selection alone does not cancel it.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", - "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L377-L399", + "version": "repo-lines-v1:sha256:57fc8a5056ef2daacc45c49b9c7531b6e09dc48efa4c91167e87e481dc29f343:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0NDQ1ZGQ3NzZkZGY3MmE2OGI5ODIzMjUxMjgyYzFlNTI3NmIwNTQwYWVlMzQ3YmViZjEwMWQyODlkZGNmZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJhNTdlYjBlMmFlMGQwZTFjNGJkOGY0MTFjZTc4NjU4ZjIzZjJmZWFmZTU3YTg5ZTUzN2E5YmNlNjljM2JhN2QiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L21", - "version": "repo-lines-v1:sha256:e2ba6b40bc2c5a1843a0a570f28c471ba3a74682cf31c79865a8472029912a21:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNWFkOTQ4ZjVhN2JlOWJlMTVkNTRhY2EwNzlmZWI4MDYwMjNlMjVjNjIzNmVmMWI0NTM0YzQ0NDA1ZTk1NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDU0MDY3MDk1MTM2ZmQyMDViNmNhM2JkMGFjYjk5N2M0MDg5M2JjNGI4OWFjMzMxN2YxZmNhYzFiMzc5ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4ZGJjMzU4MWFjZTk4MTNjMmE3ZmQ5OWI2YjNmN2I2ZTBlNmE5ZjJiMGJlZDUzNmUwOTJiZjI2NWMzOWNmNGIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L78", + "version": "repo-lines-v1:sha256:47f9649159474c18daf04dc654d59d4abe46412f888301de329dc1fac7d065db:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNWFkOTQ4ZjVhN2JlOWJlMTVkNTRhY2EwNzlmZWI4MDYwMjNlMjVjNjIzNmVmMWI0NTM0YzQ0NDA1ZTk1NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliYzBlMDVjYjgzMDgyMGE3NzRjYmQxM2IyZDNjZTU1ZjgyNTExNTJmNDExM2IxZGYwYzc1YzBjYTMxZmMxMGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDU0MDY3MDk1MTM2ZmQyMDViNmNhM2JkMGFjYjk5N2M0MDg5M2JjNGI4OWFjMzMxN2YxZmNhYzFiMzc5ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVmZmJlYWQ0YmM2ZjEzMjI0MzRkMDE2OWNiMGFkOGM1MTQwOGExNDdmN2IyODA4ZmI5NjA4MDFkYmRhODc1ZjYifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L63-L75", - "version": "repo-lines-v1:sha256:c49e78dca59df1fa504084f70e5870b13c95199d99512f713215e4d963559c1c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlZGZjY2UzODdkY2Y5MTEzNWQ4YjcyMzViYjIzZGU4ZDM4OGVkOWY0YjU5MzhkODdlMTM1ZjA3OGU5MzBlNjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmNTU2NmQyMjg3Mjk1MmM0NDc3MmQ1NDEyZTgwZjE2MzI4ZWM1ZGM5YmIxM2YyNjVkODFmNTg0MGFhYWM1ZjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZjZjMxNzMzMTFlZjYxMGRlNTllOTE2MjM5ZDBiZDQ3YjA5OTU3MTNjYThiYWY1NGZmYWVkY2Y2ZmJmZTQxZjUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L599-L645", + "version": "repo-lines-v1:sha256:8201388ed1b0d58cbc3f131eb4c51402d7be4e097f7707cc64ff4e21ed71e12e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmYTJhNDJkMzQyNjUwODBkNDg5OTA0NDI0MjlkZTcxODEwYzkwMjA5ODQxYTRkZTgzOTFjOTIxODNkNjc0OGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ0OGJmNTE3NmViOWRmZDcxNmVlZjIzYjMwMTU4ZTE1ZWVjYWEwZDM4NGMzMTczOTA4YjJiZGJhMjA2MjU0ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI5ZDI3MjNlYzczZWMzZTlhODdmM2QzNmI0ZTU2Y2NkZGYxZGI0MDM4OTk2YjQ4NTczODEwM2UxOWFkMjdhODEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L241-L280", - "version": "repo-lines-v1:sha256:10dde8f96383fc4e7c331b4e927c1c5e61a7173356ef4b5b49d67c68ba29e2e3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExMGUxZDhkNTQ2OGFiNjQ4MzUxNzE3MWM1ZDQ0NzZiNjUzNjIwNDJlYzVlNzZhYjNlZWQ2NTM4YjhmZmRiN2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiZWNkMDQ0NWJmNGEzYTg5NTcwNTczNGE0YmEyYjFjYzQxMjRiNzM1NzYyYWY0ZWYxYzQyODcyNTkyMThlNzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiNDkxY2E1OGFjYmFiYWNhOTBhZDBkYmQyNjgxODk3NzFhMjY2YTYyM2FhZmE3NzRhYzY1MTg5OTk3YjU1YWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L697-L820", + "version": "repo-lines-v1:sha256:573ad87c0b11a4de84485b6efb502dec46851957f042f8e9ec82bd6e97473bd1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5NjAyZDY4M2JmOTNkNjM2MTgwMmU2Nzg5Y2UzODc1Yjc4MDJhYzk3MWU2YmRjNzhkNGJjNmJiZDRiN2FmN2ViIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NzVkZDcxOTdjZGMzM2M2MDAwMDg2NzJjNjYxMmRhZjVlMjJjMzMwMjQ3YTNhZjBiZjgyZTQ4MDJmMzZlZTIzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2YjBkM2YxNGUzNjMyYmE5ZjBlM2I5YTJhYTM4M2UwNjVhYTBhY2UxOWM1MTU4NTA2YTliMWJjNzQ1MjExYWU4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0YzZjN2NkYzhjNGVjMGJjOWRlOGU2OTE4ZmFlMGFhOTg0NDEyYjQyY2U1YzM2OWVhMWUzMzI4NDdmOGYyMjliIn0" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L762", - "version": "repo-lines-v1:sha256:7a08479880142384554fef4c4143ed597856d65be0c552a76400a967ada09df3:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxMGEwMjJhZTMxNzI3ZmIzMmUxNTFiMmY3NzA0ZmE4ODVkYTZjZTc0NTdhMjU1OTNiMWIxY2M3YTA3ZGY5NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5YWI2ZThkYTBmYzkwMDY0YjdjNGEzNTI3YTVlNWFiZDI1NWI2YjBmZTE3NTc5YWFiNDI1Nzg1YjA2NmZmNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiZTFlNjJiNzdiZmE0MWYyZjNjYzlhMDU1MDI1MzY4MWI3Y2VhN2M2ZDZhMTk5MzljYjVkNTEyMGFjNTI4M2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBkOTk4NGRkMWE0Y2M1Y2ZlMTkyYzE1NzRkZWMzYTk1MmFmZDgyMWVjNzZjMGJmZTAxM2Y3OWMzNjJmODlkODIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L854-L862", + "version": "repo-lines-v1:sha256:504cda2464237420ec63c343898675c595d777aaaa1d5a37b00a8723ac6f734d:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzI5ZjgxOTIzYTdiY2NkMzMzYWQwNDZiMTgzYmIyZGQzODllZDViYzJhMDZkNjk4NTliZWQ2YWU1YmI3NjgzNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiM2Y3Yjk4MGEyMGJhMTc5NGU2YjM1NmU2YTZlOTAyNjZmYTU4MmNkYWRkN2I5NWM5ZWM5MjdmNWY5ZjAzMzYzYiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDVlYjdlNGI2YTk2YmJlNWNiYTMxZDY4ZDdhYWRiNzAxNWJhMGIzYWM1YzU5ZTNmY2I4M2QwMWE4OTdlOGVlMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODFlYWE3ZWIzN2Q3NzY1N2QyZjU3NjE4NzZhZmE5MDU2MGY3NjcyYTQ3NzEwZTQ1MWRiZTU1ZWRkZDQzZjYyZiJ9" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L830-L839", - "version": "repo-lines-v1:sha256:ce3f227083ebc0587a705272d2c18cfa110d702714310ad736937b34384653a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmNzA5MTI3NmEyNGM5MTU5YzRjZGUzZWZiY2RlYWM3YmQ4ZWM3ZjQxYzVkYjNhNmIyOWM3MTE1OGZkZDg5Y2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlOGQzZmM2YzYwOTVmMmM4OTlhMmM0MTRlMTg4Zjg4MjY3MTA1ZjQ0ZTQ3ZTZhODQ4NjE3MTM1NGE1ZmYzYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY5MTNmNzA3ZWY5NzdkM2FlMDUxOTgzMGM2YTc2NmYxYmI1OTdjZjJlMDgxOTQ3MDM0MzU0ZWU0MTQ1ZjA0NmUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1088-L1127", + "version": "repo-lines-v1:sha256:b17c24ee237eab5393c8275af0750d4c90435d607878c72ba65fb3664e1f1664:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1MmFjMDJlY2QwNWE5M2M1NGI3NDdjNDIxM2IxZDdmMjY5OGM4ZTRlMjBiYjNhMGI2ZGZiYjZjMTc4MDE0OGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBmMDMzM2U2ZTFlZjc5ZTY1MDliN2ZkNTA5MmZiMDFmMzY5NDQxODk0YzNmZGVjMTliOTU3NTY4NmJjNzc5ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1YjAwYjk5MjY2NDI4ZmU1NTI1NDYwZDE0YzNhMjZlNDI5Yzc3Yjk1ODUyZmEzNzEyYzEzYmNiMTMwM2I3MTAifQ" } ] }, @@ -386,19 +386,15 @@ }, { "id": "claim_fd359e43825344cb87abb3a4938e1663", - "statement": "An edited message first owns its same-ID staging run; the first real snapshot atomically reassigns only the staged row and removes staging, while live running/failed/interrupted adoption preserves provenance and chronology. Repository-instance retired IDs reject late saves/events; missing-model/credential failures still publish live before asynchronous save.", + "statement": "An edited message first owns its same-ID staging run; the first real snapshot atomically reassigns only the staged row and removes staging while preserving provenance and chronology. Repository-instance retired IDs reject late saves/events. Captured configuration failures project and adopt only after save success and an identity check; rejection reports an error and retires preparation without adoption. Queued-runtime cancellation remains live-before-save, and joining a save attempt does not guarantee storage success.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", - "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L784-L820", + "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", - "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2625-L2646", - "version": "repo-lines-v1:sha256:078528287f4cac7401a61342ac1db3154e36f5566407f8bb9332cd63c543e273:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjZWUwZWU1M2M2NjUxODNlOTE0NjU5MDViZWY3ZDQyMTI0ZDRmM2JkYTJkNDg5MzlkNWIwNWQ1Yzc1MzIyODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmODMwOTk4YzhiZmMyY2EwMGQ0MGM5NDlmZTgxOTQ5ZmQwZDg4MTg1ZWUzOGNlMDNjMjAzNzg4MDZhZWQ4YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2YzUyZmU0ZTgxMjIzMWU2NGIyMGFmODc0MmI0ZGJjZWNhMjlkMDFjMTVjYzk1NDNjYzc0ZGFhNDAzNjlkZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmZGI5NTg3M2IxNmNiMTEwMGJjNGVmMGZmYWFiMzZmODc1Y2EzYmVjODY2MmIwMDA5YzEwY2JlNWE2YWVjNTMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2147-L2240", + "version": "repo-lines-v1:sha256:aeadd4638c2467afe946e610f83cedce67e3e7eea89f5e2100233a4f9b700abd:eyJzZWxlY3RlZExpbmVDb3VudCI6OTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmMDExYjE4NWI1ZWM1M2UwMGY0YmIzZGMwNWFkOTNkMGVkOWQ3YTU4NDA5Y2FkYTc0OTBmYWU2NGY4M2ZiOWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRhYTU1YTdhMTYxOTZiOTEzNDVhNDI3NTMyZWYyNDdhM2NkMzIzMjY5YWVkOGJmZDI0NzMwZTQxMzdlOTkzMDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyYTlhNzI0OTZlMWQ2NWUyMWYxMzg2MmYwNzU1YjJkYWZjMjE3MmI1MTM3NmRiYTExYmIxYjI4YmE4MGRiNjUifQ" }, { "resource": "repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift#L156-L165", @@ -409,10 +405,24 @@ "version": "repo-lines-v1:sha256:c1a4a9f265c86d77937e83acae5e2b69a2523713431a89d470dad62099d6b376:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3MmJiNmQ1NzgzMTJmNjJmZGQ3OWM4ODdhMGNjNmVkMDRjZDkwOTQwZjFhYWE3ZTlhNTAyMGVmNjkyOTFhOTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkY2UzN2IzN2YwNGIxOGQwODk0NmFmYTY3M2UwYzIzNjcyMWEwMzkwNTQ2YjM4NTUyYzI0YWY3MDc4ZTdhYjMyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlY2U5NjA2M2ViYmNhNjY1M2Q0YTYwYTViNjBjZWE4OTRkOTVmOWExNjQyZTQxNDEwMjFkNDZlNjQ4MjgyNGVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNTcxZWY2ZjVlOTYzMTE4ZDM3MTdiNGQ4MmI0YjBiOTliMmI0OTI3NjYzNGFjNWZiNmZhMGE3ZTVjNDBlZjhjIn0" } ] + }, + { + "id": "claim_c56eb71da18644078b4f56c48144c2c8", + "statement": "The captured configuration-failure save task strongly retains its controller and conversation until the attempt returns and preparation retires, including after callers release their handles.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L134-L157", + "version": "repo-lines-v1:sha256:b98f89f38a02619fe24af1c5df67726ef33c1f3737119fc09f407ec57de37c4d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNmYjUwYTQxZDQ1MjY0ZjQyZDRiZGJkYzA1ZmQxODQ5NDdlODQ1MGRkNmJlNjFjOWRiN2ZiYThmYjA5ZDRiMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRmY2MxYmZiNTgzZDUyMDA0NjUwMTdlZTE3YzBlNzE5MWQwOTZjYWQ1MDVlYzcxOGY3NTQ3ODEzMTFlZjcyODQifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L784-L820", + "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T06:54:45.592Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index 5383f219..e551363e 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3cbf1be9eb94f0eb87d83be0a1277c9801cb4df9a3da898ef95b63b20b72a785", + "pageVersion": "sha256:47037593b68f4328e80cb60aadad6bb3290eb90e38ef9005c31e46440925be24", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://AGENTS.md", - "version": "repo-file-v1:sha256:7cd9b9e940106ce07b1fc987586fc9c6ca1c4f9489cabb02791b627a05a1eedb" + "version": "repo-file-v1:sha256:d9c994a1f4dde96fe39505281f40e992ce4760d2d6752de29f59060250bbdca9" }, { "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", @@ -191,6 +191,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T06:54:45.592Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index e431b785..81bf642a 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:d3bcf7c3b342b7c7e0a24bff200d9de3fd902e66a05ac6d1f8ff1046b23948d5", + "pageVersion": "sha256:7b1c2e0d961fa1fba7e88eb2273136e7a6950c3a06f80900d23aab9694031e69", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -12,7 +12,7 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L385-L504", - "version": "repo-lines-v1:sha256:0d91636ed62e262b1faebe69349c0c5b974af977be6c0af5e6b929b117ee81b7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJlYzFiOGExODllNmEyYWM4ZTM1MmJlMDMxYmIzYWY0NTI2MjM2NDU0MDI2NzIwODY5NzZkNWVhNTQyYjk5MDQ0IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmY0Y2JlMGYwMWM4NzRjOTVjMjU2YTY5ZTUyMDc1YTkyODI2YmE3YWFjMjQ0NmQ3MTczY2YyZjdmODI4YTQ1IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYzE1ZWYyMzVmYjkyZDFkYmQwNTRjZTlmMTFiZTRmMzA0NTVkYmRjMmU3MzkwNGY0MDYzYzA2M2M5ZjM5MzI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwYTgyMDE0NmQwZGY4OGNkZmJjOWI2OTc3ZDZiODNhODBiNDYxNTkzNzRkMzQzNGRkOWQxYWRmNzk3YzgyYmI2In0" + "version": "repo-lines-v1:sha256:038f1d1dcab608d9de5e052060d6f9d7784a0fc9367aaf40c2fd00f978552040:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJlYzFiOGExODllNmEyYWM4ZTM1MmJlMDMxYmIzYWY0NTI2MjM2NDU0MDI2NzIwODY5NzZkNWVhNTQyYjk5MDQ0IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmY0Y2JlMGYwMWM4NzRjOTVjMjU2YTY5ZTUyMDc1YTkyODI2YmE3YWFjMjQ0NmQ3MTczY2YyZjdmODI4YTQ1IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYzE1ZWYyMzVmYjkyZDFkYmQwNTRjZTlmMTFiZTRmMzA0NTVkYmRjMmU3MzkwNGY0MDYzYzA2M2M5ZjM5MzI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwYTgyMDE0NmQwZGY4OGNkZmJjOWI2OTc3ZDZiODNhODBiNDYxNTkzNzRkMzQzNGRkOWQxYWRmNzk3YzgyYmI2In0" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift#L42-L68", @@ -34,7 +34,7 @@ }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L518-L632", - "version": "repo-lines-v1:sha256:b1882f48ba2a0c4f0faaa010c3ad73fbbfed243a2f0f51c0d883ed8058e5a3d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" + "version": "repo-lines-v1:sha256:6f0586148546ed005af984580253f2dc7d5de8689160a50bb5889c18111981cb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzN2U2Yzk0MTIwZGUzNjU0OGMxZjcwMTkzYzMxYzg0NTQ1ODI0OGQ0M2E4N2U2ZTU0MjY5MTBmY2VkYmJlOGJiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3NDhiNTNjYTI2MWZjOWVmMTVjMzg5YWVkYTA0Zjg2MTFjN2I0N2UxOGU2MjA5NWQxMWRmODkwYWNjMWQ4ZjRjIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwZWE5ZjA1OWViZGRiM2M1YTA1MTQwYTNhNTJjMTM2MmU4ZGVhZDhlNmU1MjllZWNhNWMyNjkxOGZhNTMyNTY1In0" }, { "resource": "repo://TFMTests/AgentRuntimeTests.swift#L141-L203", @@ -212,7 +212,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L394-L404", - "version": "repo-lines-v1:sha256:3bcf15a3ce14b9ed88dd81ab4acccf0d8910eee98b7e5e350a7647991cea8ba8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4YjYyNmIxMGUzNGMyYmYxMTE1OTg5NGYyYjA1MTU4Y2I0NzVjYjJmODYyMjRiNjg2NjA2Nzk5M2JjNGEyZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5MDJhM2QzZDU4YjYwMmY0YjZlNjhkMTAxMjRjZjE1YThmMTMwOGYyYThlMzdhY2RhNzQ1NDkzOWEyOTllOWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0MDU4ZjhjMjAyYjNmM2Q5YTEzOGY2ZTMxOTAwZjc1YTVlZmM4N2Y3OWM1OGZmMzQ4Mzc2ODJkNzkyMTVkYmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjNGM2YzQwNjcxOGIzOWFjYzE3Zjk5ZGE2NDhjZWJkNmRmODZiZWUwMmFkZjAzNDUxZTQ5YTRkZDExYmIwZjUifQ" + "version": "repo-lines-v1:sha256:66d30b530a00dffe56c652406a651bf0830bb3ee8b41fd7eb1e9143040eae97f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM2MGY0MjE3OTNjOGRmNzZlY2NhNjg2MDM4NzBlNzU3OWMxYzAwMGU2ZmMwZWVmOTJkZGY4OTBjNzU1OWEyN2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5MDJhM2QzZDU4YjYwMmY0YjZlNjhkMTAxMjRjZjE1YThmMTMwOGYyYThlMzdhY2RhNzQ1NDkzOWEyOTllOWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0MDU4ZjhjMjAyYjNmM2Q5YTEzOGY2ZTMxOTAwZjc1YTVlZmM4N2Y3OWM1OGZmMzQ4Mzc2ODJkNzkyMTVkYmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjNGM2YzQwNjcxOGIzOWFjYzE3Zjk5ZGE2NDhjZWJkNmRmODZiZWUwMmFkZjAzNDUxZTQ5YTRkZDExYmIwZjUifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L532-L566", @@ -469,20 +469,20 @@ "statement": "Desktop and mobile prepare edits synchronously before creating a UI task, preserving original attachment payload/suffix but capturing fresh submission context; performAgentEdit checks identity before tracked persistence and again before runtime preparation, and failed persistence does not apply the live edit.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L450-L503", - "version": "repo-lines-v1:sha256:1c6b9ba0438e47aa7828a8f0a41cdc7625ac586fd9baee676a531b04a954769a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhOTU4ZTIzMDAzNTJiNWU3NmQ0YzYyZWM3ODUyYjY4NjViZTQ4ZDgxODg3NDg2Nzk2ZTkyMTI2MmZkNGRmNTcifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L595-L649", + "version": "repo-lines-v1:sha256:3dd6107e821741808139e76b0838e0c52fbdfc6508eb6a1b57767ca1182fe492:eyJzZWxlY3RlZExpbmVDb3VudCI6NTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY0NjE2MTU1NzMzYmU0ZWIxN2MxYTlmODAxYWIyNmJjMWNmNDEyNzQyOWQwMzA0NzdlZGYzOWNkODVhN2YwMGIifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L216-L281", - "version": "repo-lines-v1:sha256:0b8c25408aec78da9722af3b9358ea9a949a5bf115e9d69ec12834362b0fbdee:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3MTI1MTA3NjE5NmE2NjI3OWU2MDFjYjExNTRjYjNkYTQyNmM0NmUxYzVkMjBmZDJiYmYyMWM3Yjg2MzE5MDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAwNDYxNmUzZjI2ZjhhZWRkMzU4YmNjODQ3OTZkZDA1MThhOTIzOWYxODRhMTAyMTY2ZDFkZTRmOGRmOTRmODIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L228-L294", + "version": "repo-lines-v1:sha256:63f3e77065abfa4bf5f6b024bfcb930004c1dd89497d0ea4dfb16f4cffbb2689:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3MTI1MTA3NjE5NmE2NjI3OWU2MDFjYjExNTRjYjNkYTQyNmM0NmUxYzVkMjBmZDJiYmYyMWM3Yjg2MzE5MDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmNGEzZTYwN2U5Y2EwYTM1NTkxZmM2ZTIxNjZhNWUyMGU1MzNmZjNjYjY3Yjc5NWVlMTc1ZTE2NDM3ODgzOTEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1054-L1067", - "version": "repo-lines-v1:sha256:4f484d7cfc22662766e35da9dbd2bbae1e661c48fb02f3e454ed67e39e0efe50:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNDEwYjZlNzRkZThiNjdmYWNlOGI5MDE5M2EzYWNiMzg2Y2U2OTk1YjQzMGUwZTVlNThhZmFiZWE0NDFmYTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5MDlhNTNhNjE4ZDAyOGUxZDMwMDBjMGE3YWJjZWVjYmU1ZmIzN2Y0ZTUxMWVmZTQ2NjQzZmFkNmU2Njg3MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmOWI5MjU0ZmFlZTRiMGFkZWU1YmRlMDZjYjY3MjI2ZGI3MDQ2NzUwYjU3NDVkZWQxNzI4YjQ2OTMyODEwZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxODE2NWIyNGI3ZDdhMjdiNTZlZTIxZDVmNzk5NTFmODRkNTk5MDY1YmY3YzZlMzZkYzE5ZjBkZjM2ZWYyZGMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1101-L1116", + "version": "repo-lines-v1:sha256:70eb20eff459aff69f606cacc64e2820ff4dfef497ac0aa9b4d16242c3ebe5e9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5Yjk2NjZhZTAyNTVlNjdiODYwZTUxNmU2NTNjNDg2NThhZmYxN2Y3NjdhNTk2MjIxMDI4ZjQ3ZTcwMjJiM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlNTU4YzYzYTI3MmJmMmFmZmE5YTc2NGViNDBjOWIzMWU3NzUyMjBlOWI3ZTg0YTk3ZWJhNzk1YjIwNGQ0ODIifQ" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L215-L227", @@ -496,45 +496,41 @@ }, { "id": "claim_52455054e49840aca30138327ea628e3", - "statement": "Preparation is separate from streaming, with combined activity guards; stop, global cancellation and mobile background expiration synchronously invalidate preparation identity, cancel and join tracked history/preparation work, and recheck identity after the interrupted save so stale work cannot retire a newer capture.", + "statement": "Preparation is separate from streaming, with combined activity guards. Ordinary cancellation synchronously invalidates identity, cancels and joins tracked history/preparation work, and rechecks identity after interrupted persistence. Once preparation enters savingFailure, cancellation instead returns its one existing terminal save task without cancelling it or adding interruption; successful save and identity recheck precede failure publication, while rejection reports an error and retires preparation without staging adoption.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L88-L228", - "version": "repo-lines-v1:sha256:0034c170196c42d318ce328d8ce9ee1dd89409998248de0843e6a3e36d0aa5de:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NTU1MzI0NWZhOWIzYTg5Nzc5YTQ0MzliN2M3OTNjYzhjZDc2MjM3ZTIxMGI3YjI5ZDEyYmQ0OGEyYjNhOTkzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhOTdmOTEwNGNiNzExYWZlYzlkMDQ4ZDZlNzA4OWU4YzFkMTIzMzRiN2Y0YjA2ODQzM2JjNjkxZDZmZTUxYmI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIn0" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L689-L717", + "version": "repo-lines-v1:sha256:bbb94ddd10755f8cf1b1844ea133c2896ffb57c69057eddb56ec03c7c3ea745f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2MDA0NjNkOTgwYTgxZjIxMzI3OTc0MTJmY2M1M2RlODJkNTg0YWNjNGJhMTFjODdkN2NmNzNmZTg0MmRkY2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiM2M1MzE3OGIwNjU0NDdkYTk1NjJlYjVhMTRjNDMzNDVlYmQyZWRkYjlkMmQ5OTFkZGIxNTQzNjkyNWYyMGQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L684-L760", - "version": "repo-lines-v1:sha256:c80221e2f4c3085f440c78419a9e28c93800587fc504c207638be94c60943f8e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc5MWU5ZTRiNmRkNTg0ODk5NmNmOTE2ZDQ1OWJiOGI1MTBkMzBjMWNmN2ZlYmYyYWI0N2MyOWY5MzU3MjI4ZWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3OTg4YmRiMDg1OTc2MDA1ZWEyYjgxNzNjYmYxMjFjOTkwZjIzYWQzOWU0YzkxYmI1NDAyMDU3MWE2MTdiYWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmZjgyYTA0NWVkNzg5MzI5YzJmZDc1N2ExNTA0OTdkZTJhYzI3ZmZhZWQzNmE0MWI4NGRmZjIzZmI0ZTRjMmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkYzRmZjQwN2IwN2IxNDA1OTllZjEyNTQ3MzcxNTBjZjc2Mzk4ZTNkMDg0MTkwMWViZTE2ZDJiM2VhZGY1MTgifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L89-L232", + "version": "repo-lines-v1:sha256:06417f664159d51d61d0c4b32815f91c17e12c0127151e5dc657a463e34aa7cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2ODc2Yjk2OGUwYmE1M2U1NTE2ZDdkMjM4ODIwMjU2NTYzZDk1NTM0NzhlZmMxMDZhMWFhOTQ5ZTFjNGE5OTJmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmYmE3MWNhMmVmMjRjOTljYWUwYzAyNjNiMzdlZTBkNzlhNjI0MWQyY2FhYzU2ODRhOWYyMDlmNzYyN2ZmMDZkIn0" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L69-L83", - "version": "repo-lines-v1:sha256:0d5f7cf554fd4cb9799565481fd35f87cf6deabc07509813f9f40d913426acea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3MzJiN2I3NTFlYmQ1MmM4OWQxOGYwNzU4M2EwYjQ1MDY4OWNlMjkyYTIyNzJmNDVlNGZkNTY5ZGZhYjg1YmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImExMjMyNjI1MGIyOGY3Yzg0NmRmODVkODZhMWM1MmM1MjBhNjM1Y2Y5ZGIzMTY3MjFhNTE1MDdjY2E2NjE4NGYifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L697-L820", + "version": "repo-lines-v1:sha256:573ad87c0b11a4de84485b6efb502dec46851957f042f8e9ec82bd6e97473bd1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5NjAyZDY4M2JmOTNkNjM2MTgwMmU2Nzg5Y2UzODc1Yjc4MDJhYzk3MWU2YmRjNzhkNGJjNmJiZDRiN2FmN2ViIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NzVkZDcxOTdjZGMzM2M2MDAwMDg2NzJjNjYxMmRhZjVlMjJjMzMwMjQ3YTNhZjBiZjgyZTQ4MDJmMzZlZTIzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2YjBkM2YxNGUzNjMyYmE5ZjBlM2I5YTJhYTM4M2UwNjVhYTBhY2UxOWM1MTU4NTA2YTliMWJjNzQ1MjExYWU4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0YzZjN2NkYzhjNGVjMGJjOWRlOGU2OTE4ZmFlMGFhOTg0NDEyYjQyY2U1YzM2OWVhMWUzMzI4NDdmOGYyMjliIn0" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2002-L2044", - "version": "repo-lines-v1:sha256:5f7c207116c45771d4001646f206431e25efa58c2af14a14e1a3dd246214827a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJhNmU1M2Y5MmUyYzc2YmFhNWU3ZTY0NWE2YzczODVmZTU1NTYxMTkxOTczMWY3ZTczMTIwZTMwYjQxY2IxMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1YTY2MjU5MDc5M2ZlMzYwNDc3ZTJkNzYzMWEyNTdjYzk2YmY3OWI5OWE5MzliZDJhNjZkOTBmNDM0MzY1ZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiMWY2NWVjZTVlNWRlN2I1MGM5YmUwZWI4Yzg5NjZlMWRlYTliNzI4ZGQ3YTI5ZDIzZjNmN2U1NjM2MWM2ZjUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2021-L2060", + "version": "repo-lines-v1:sha256:8d4c3fb9b24dfa0ab3d8fb7cb45f87ed9b26dfd5b8968fe78a901620259fc572:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzMzczZWM0MDZhNWVmOTI3NjRiZTgyZjMwMGUzNzYxOTE3Nzk0ODc5MDA0ZTkyMmM2NWI5NzllMjNmYTFlMDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQzYTY3OWY3NjY0ZmU2OGU3MDViYmQ2NDU1NjUwNmE3YmU3MmI4N2E5OGUwYWFkMTIzZjViNGRkYjIxZDc0ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM1M2MwMzAxNDBiMDg4NjZhMWFlZjNiZGNjOWNkYjdjM2Y1ZDUwNDU2MTc1ZWYwMjI1Y2IyYjc2ZWNkM2QxOWEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2227-L2242", - "version": "repo-lines-v1:sha256:0877731fa1e8352dfc7c8b1bdfee9b3fc14252c3499600a5e0969727f7c89879:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmN2VjZjg2MjQ3ZmRjYTUwNThhY2VmOGMzNGE3ZTNjYzA2ZmU3ZDA1MGU1YWE0OGNjNDNkNmE0ZWRjZmViYTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NGMwNjM0YWI1Nzk0YTg2OTJlN2U2ZDI0M2FhYzEyYmEyOTM2ZDc2ODY5NTdkZDcxYWI0NGUzNDYyNzc1OGUifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L895-L900", - "version": "repo-lines-v1:sha256:9fcf92bd03be2ed38be9f053105c95d1fc4299bd68565fde983ed6b044b787c2:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGJjZDc5Njg3ODBjY2VlZDAwMzExZWE1MmE3MDdiZTk2NWYxODZlYTJhNGM4NGEwZWUxMGQ2Y2M1YWM2NTdlOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzZiMTI5ODBjNTljNDNiNTBkZDIxYmJlZDczMzZiMzZjNjQ0OTdhYzg5ZDlhMWU0ZWQxNDIyOGFjZjIxMzFjZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjg0YTExYWVkOTE4MTlhZGYxNjA4MjNjNzZjMjhkNzRjMjc1OGE1ZWIxOWQxNmY5ZTI5OWRhYTg0YTBhYmJmZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDgxOTI4NGM5ZmE5NWJhNzRkYmZiNjBhNTA0ZjI5Njc4OTU1NTQ4NzJmMjYxYjZjODA1YmNhMzMwNGM0Y2Q1OSJ9" - }, - { - "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L153", - "version": "repo-lines-v1:sha256:a447b5e91a0ff06e0586ba98bce9ea20c40d2acd56bae2b931f831e2e81f8b7c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzNjFmYmU2YTJkNzg3MDAzYjc5NDliMDY4NTFjOGJlNDczNTI3MDNiZjAwYmZkYjM5YTVlZmEwZDAxYTdkNmQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L880-L894", + "version": "repo-lines-v1:sha256:effe69b144bd9d7c0a32076fdae06243bc39251f850db112c0d6ff2c1b563740:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhYzI2YWFiZWQxNTc5NjAxMzg4OWViMTk2ZGIzYzM1YzBlMjljMjU3MjkwZTI3Y2M0ZjdjNWZmZmMwMTc5ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzY2ZiNzUzNjUxMjEyMTZkNjk0ZjlhMjQ3NWU2Y2IwNDFmMTdhMmVlZGYxNzdkNDk0YzdkMjgzZGRjYzA3YmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwNTI0ZGU2ZDc2YzhhYTJkMzlmOGIzOGZlZTIwZjc1NWZkNDFlMzE3NzI1YzBhNTJjMGZkOTMzNjRjZGU3MzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc2NjcwZjc1NjIzOThkYzdmZjNlZjVhMzVkOWRkMDY0NGY2NTU3ODY4ODA1ZmI4M2QyYzFjN2U1NzUzM2I2OTIifQ" } ] }, { "id": "claim_21c0e6a726c9423098e4ca8f42b2651b", - "statement": "Archive/delete cancel and join pending preparation before their activity guards; manual compaction refuses preparation, navigation leaves it active, and automatic compaction uses captured preparation settings while preserving persistence-before-live-apply ordering.", + "statement": "Archive/delete request cancellation and join pending preparation or captured failure persistence before their activity guards; manual compaction refuses preparation, navigation leaves it active, and automatic compaction uses captured preparation settings with persistence-before-live-apply ordering.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", - "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L186-L232", + "version": "repo-lines-v1:sha256:097594636389e9cd19fc96d08c329f1f1bf55ec16f7f52bf5a8dcc5845eea1ef:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA3NmZlNzYwMGU0N2E1Y2E4NzcyYThjNmU0MThkMDEwNzFiM2RhNDAxODQ2NjFjN2Q2MzAzN2RjMzIzMWNkZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiYTcxY2EyZWYyNGM5OWNhZTBjMDI2M2IzN2VlMGQ3OWE2MjQxZDJjYWFjNTY4NGE5ZjIwOWY3NjI3ZmYwNmQifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L377-L399", + "version": "repo-lines-v1:sha256:57fc8a5056ef2daacc45c49b9c7531b6e09dc48efa4c91167e87e481dc29f343:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0NDQ1ZGQ3NzZkZGY3MmE2OGI5ODIzMjUxMjgyYzFlNTI3NmIwNTQwYWVlMzQ3YmViZjEwMWQyODlkZGNmZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJhNTdlYjBlMmFlMGQwZTFjNGJkOGY0MTFjZTc4NjU4ZjIzZjJmZWFmZTU3YTg5ZTUzN2E5YmNlNjljM2JhN2QiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L28", @@ -545,16 +541,12 @@ "version": "repo-lines-v1:sha256:da559c6fb05b1afa694d3397263759479e8be0ff76854ea7fa06fd269153b9e5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlZGZjY2UzODdkY2Y5MTEzNWQ4YjcyMzViYjIzZGU4ZDM4OGVkOWY0YjU5MzhkODdlMTM1ZjA3OGU5MzBlNjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViZDk1YzE1MTE4MTI1NDYxYzI5ZGMzMGJjNGJlOTFkZGUyOGI1NDFlYjJjM2NiZTkwNzc4NDA4NTFiZDMyZDcifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L632-L660", - "version": "repo-lines-v1:sha256:b7ea68c5df571de68ab0ae45d9c94ed876783cac80350d6c5d0b17aaf3a8683e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjMGM5OTdhNmNkYjEzZjBmMGY3NDJjNjE2YWRkMmMzYjRlOGMwZDY2OTQ2ZTlkNzQ5Nzc1YjY3YTBkNGY5Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZmNTNlNmY5NWE4YTI0ODczYjhiMTUyMjJmODMwOWNmNTE2OTY3N2NmYjgwN2JhN2YwYWQwMWQxYTAxMWJhMzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg1ZThlZjg0MGM3NWUwYTg0ZDlhYTdjN2NkMWY4MmRhMWU2ZmNlM2VjOTY4Yjk5YjIwYzYyZmU1MWYwZDg2OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L648-L677", + "version": "repo-lines-v1:sha256:20e8d55102d4e34a447997f0633dac52e0ef24bf81e6a7d5626490f158b51c34:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjMGM5OTdhNmNkYjEzZjBmMGY3NDJjNjE2YWRkMmMzYjRlOGMwZDY2OTQ2ZTlkNzQ5Nzc1YjY3YTBkNGY5Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg1ZThlZjg0MGM3NWUwYTg0ZDlhYTdjN2NkMWY4MmRhMWU2ZmNlM2VjOTY4Yjk5YjIwYzYyZmU1MWYwZDg2OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQzNTAzNmFmMzFkMDY5MDkxNTZmZmIwOTU2MmE2YWIwYzYwNzI2OWEyNTg4OTdmMmM3MDQ1NTJiMTI4MTZlMDIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L830-L839", - "version": "repo-lines-v1:sha256:ce3f227083ebc0587a705272d2c18cfa110d702714310ad736937b34384653a6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZDUzYmFhYzE4Nzg1MmI0MzFjMjMyZTA1MTYwNjFkNDk2ZjdhMjE1NmVlNGJmN2RlYTY0N2ZjNjY0OTg2NmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmNzA5MTI3NmEyNGM5MTU5YzRjZGUzZWZiY2RlYWM3YmQ4ZWM3ZjQxYzVkYjNhNmIyOWM3MTE1OGZkZDg5Y2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlOGQzZmM2YzYwOTVmMmM4OTlhMmM0MTRlMTg4Zjg4MjY3MTA1ZjQ0ZTQ3ZTZhODQ4NjE3MTM1NGE1ZmYzYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY5MTNmNzA3ZWY5NzdkM2FlMDUxOTgzMGM2YTc2NmYxYmI1OTdjZjJlMDgxOTQ3MDM0MzU0ZWU0MTQ1ZjA0NmUifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L881-L892", - "version": "repo-lines-v1:sha256:511d122c9d8b8981de6d7c39b078ca3b5f6af4930853207aa98b7d3365557bf8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZkNWViODBiM2MyZWVhZTUwMjUzNWRmMTM0YzJjMGY2NzljZGU3OTU4NDQzNjdmYzNhZjVjNDAzZWE4YzMyNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlMGJhYWZiMDE3NmFmY2M1Yzg3NzI0MTkzYmY4ZmM2NWU4MzgwNGUyYWUwYzk5YjZkODViZjViMmY2ZWFmODEifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L854-L862", + "version": "repo-lines-v1:sha256:504cda2464237420ec63c343898675c595d777aaaa1d5a37b00a8723ac6f734d:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzI5ZjgxOTIzYTdiY2NkMzMzYWQwNDZiMTgzYmIyZGQzODllZDViYzJhMDZkNjk4NTliZWQ2YWU1YmI3NjgzNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiM2Y3Yjk4MGEyMGJhMTc5NGU2YjM1NmU2YTZlOTAyNjZmYTU4MmNkYWRkN2I5NWM5ZWM5MjdmNWY5ZjAzMzYzYiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDVlYjdlNGI2YTk2YmJlNWNiYTMxZDY4ZDdhYWRiNzAxNWJhMGIzYWM1YzU5ZTNmY2I4M2QwMWE4OTdlOGVlMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODFlYWE3ZWIzN2Q3NzY1N2QyZjU3NjE4NzZhZmE5MDU2MGY3NjcyYTQ3NzEwZTQ1MWRiZTU1ZWRkZDQzZjYyZiJ9" } ] }, @@ -615,16 +607,16 @@ "statement": "A missing model or credential at capture stays a failed submission even if configuration is supplied during preparation.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L85", - "version": "repo-lines-v1:sha256:80d25cc0451a928bc50f91e07a28b411b83c0d7299889938c179b8d5e96fb33b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L86", + "version": "repo-lines-v1:sha256:80d25cc0451a928bc50f91e07a28b411b83c0d7299889938c179b8d5e96fb33b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk3ZGY0M2ExZTA3MDM2YzJmMjk2OGUwYzdmMjZhMzQ2ZDJjOTliNWU4MmUyYWU2ODFkZWVhODQ5MWFlMmE2ZWUifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L348-L379", - "version": "repo-lines-v1:sha256:6eaf43bb757978c5badc8b8bc08b8d551439733c979c24ef1a948607211463ce:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNmE0YjUxN2VkYTk2ZmVhODI1MDU2NzdjMGJlNmY0NTYzM2E1NTVjNzg3MTM4OTkxOTcxN2M4YWY2ZTY3MzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MDMyZTk2NGQwOTMyMzUxZTVkMTg1MjY5MjgxYjE3MWEzYTA5YjA4YjI4NmU2NjFlYTQ3MDMyM2M4MmViNGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3MWE2YmMwYjNlZDNlZmEzMmI4NjkzYmQxYTljOTg4NjZhYjU5YjM1NjlmMTMxYmNjOWU1YjBkNDE1MGJkYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRiYTg0YTA3Nzk4ZjI3OTg2OWI0MzVlN2JkZTYyZmRjODZkNGFmOTM3MmRhNzIyZjk1YzYyNjlhMmFiOTAzZTcifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L351-L386", + "version": "repo-lines-v1:sha256:b745e63d660e2fa89e550983ab77ec29af93c029251043b7869e16a2bd346733:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwYzRmYjc2MWFjNmVmZDQ0YzY2Zjc1ZjMyZmJlMmI2MDBlZWMyYzdmMGQwYjBmMGZhM2JjYmFlMzY5MWQzZjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwYWFhOGQ2YTcxZmY4OTIyNzczMzM4NDIxMWM5MTJjZmVlYTY1MGJmYTExODgzNGUzNjAxZDgxM2M4YmQxOGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJiZjg5OWQzZTUyNzU4YjE5ODcxMjc5NzVlMzcyYTU1OTE4MTUzNjFhZGQ4ZmNlZWFiYjNlMmUwYzBiZjM3YTIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L523-L525", - "version": "repo-lines-v1:sha256:3cad3b5cb26d6fd2806c7989d5cd8b766f0080b717490e9a6998983f8650f32b:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDcwYjdhMjk5OTAzODQzNTQ2ZDM5YmFkZDg5NzQ4YmNjMTUzZTQ0NmZkZWY5OGQwZWQ1ZGZmZWI3MWZlMjYwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjFiNmJkZjk5Njg4ZWIwNmJkNWI1YjVhOGI4ZDZhODg2NzExYjVkMzY2YTY2Mjg1YWQ2YjAwOTU1YjRlZmQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2RkZmFhMDUwMzQ3NzIxMThiMjE4ZWU1MWZjZjMyN2U5ZGMwYjUwNTRjYjE5OGFmOTcwMGI4MzM5YTJhZTkzNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTEzM2Q3NzZkZWFkNDA1ZTEyOTNiZWRiMzdkZGRmMmFjNDA0OTJhNGE1NDRjYjhjZTQxZDQ4Y2JhMmQ3ZTBlMyJ9" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L523-L533", + "version": "repo-lines-v1:sha256:b3860a4f7d61bdc1c99d7174ee8c662324e9f8fd786246d547aaaccf84ce6a0c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3ZTZjOTQxMjBkZTM2NTQ4YzFmNzAxOTNjMzFjODQ1NDU4MjQ4ZDQzYTg3ZTZlNTQyNjkxMGZjZWRiYmU4YmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg5MDUzNmU5MDk0YTUwNGNkMWZkODY2ZjUxZDliMWI3NWU2NmU3MzZiZGYwNmU1NmY1ZjU1MDM4YTUxNWE1ODYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0OGI1M2NhMjYxZmM5ZWYxNWMzODlhZWRhMDRmODYxMWM3YjQ3ZTE4ZTYyMDk1ZDExZGY4OTBhY2MxZDhmNGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdhZmUxNTg2Yzk2OGI2MDgzMTE1MzhlZWFhMTE0ZDEyOThkM2RjNjk4ZWU4YTUwY2U1MDEzOTIwMjJlNjBhMmQifQ" } ] }, @@ -744,31 +736,23 @@ }, { "id": "claim_eb12b2d099c145dba48004e4797b19a7", - "statement": "Live run-started, captured-failure, preparation-interrupted and queued-runtime-cancelled paths adopt the staged replacement's actual run provenance and chronology. Preparation cancellation awaits save and rechecks identity before adoption, whereas queued-runtime cancellation and missing-model/credential failure project live before asynchronous save; only the queued cancellation's stopAndWait joins its save attempt.", + "statement": "Live run-started, captured-failure, preparation-interrupted and queued-runtime-cancelled paths adopt the staged replacement's actual run provenance and chronology. Preparation interruption and captured configuration failure await successful save and recheck identity before adoption; rejected failure persistence leaves staging intact. Queued-runtime cancellation projects before save. stopAndWait joins the relevant save attempt, which does not guarantee storage success.", "evidence": [ { - "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L114-L195", - "version": "repo-lines-v1:sha256:e04b4abd57b7b220f9a0e87a9be347cc76190405efb19f7ffeda06c847620bdc:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRhMTMwMDNhMmEyMDc0NmI2ZGY4NjAxNjVlZDkwNjg4OTZlNjUyODA4Y2Q3YTM2MDdlNjhjMjRhYTlkMmFmOGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L114-L269", + "version": "repo-lines-v1:sha256:97fd1f52a5ac06c31001c84eab8a533c78eaccbff1efd2975553de5780ee2b9e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTU2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YTEzMDAzYTJhMjA3NDZiNmRmODYwMTY1ZWQ5MDY4ODk2ZTY1MjgwOGNkN2EzNjA3ZTY4YzI0YWE5ZDJhZjhmIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3OTI0MTdiZjM5ZTUzNDc3NzRjZDk0NTZmOTYxMTc0MzAwNTVlY2E1NDAxZDhiNzljZjdkOWMyYmMzN2FkOTM2In0" }, { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L476-L534", - "version": "repo-lines-v1:sha256:435b7b43d6dfe28c7d197656e31af82f72b308168427674bccfeb12777d04735:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYwZWU0MDJjOGFkMzY5M2YwZjFjNzY1MzZlZTk5OTVmZWMxYjkzNTBkNzU5YTVkOGUyMDY0YmRhYjRlN2Y1MzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L689-L717", + "version": "repo-lines-v1:sha256:bbb94ddd10755f8cf1b1844ea133c2896ffb57c69057eddb56ec03c7c3ea745f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2MDA0NjNkOTgwYTgxZjIxMzI3OTc0MTJmY2M1M2RlODJkNTg0YWNjNGJhMTFjODdkN2NmNzNmZTg0MmRkY2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiM2M1MzE3OGIwNjU0NDdkYTk1NjJlYjVhMTRjNDMzNDVlYmQyZWRkYjlkMmQ5OTFkZGIxNTQzNjkyNWYyMGQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L797", - "version": "repo-lines-v1:sha256:264e0504f5139486947350a5ec872d5440593d50d499f73e6156a8fc3b7a6ffc:eyJzZWxlY3RlZExpbmVDb3VudCI6NzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyNjU3ODFmNGU1ZDY5ZTk4OWU5YjFkOWQ5YzRlMTgyMDZmMTI1ZGUwNmU0OTVjOWUyOGE1OGMzYTEwOWY3ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMzZDYyMjM4YjljY2EzZTg2ZTlmMDM1ZWM0YzFhMmJkNDI0ZWIxYmEwMzUzYzU4ZjhkMGVjMjMzYWE0MDMyOGQifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L738-L820", + "version": "repo-lines-v1:sha256:6d146eff97275cd62487bfad2c03014fc984feffae2753012b1c6107e541c5f4:eyJzZWxlY3RlZExpbmVDb3VudCI6ODMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2131-L2166", - "version": "repo-lines-v1:sha256:20935b8e4d562e7318f72909f63c38e93c8c3eea78fc9e624b0dd6507ccb26fc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYxZjE5MzcwNTE4YzZmN2QwYTk3YmU0ZTFkMThiMTliMzk1MzMwM2E2ZjA1YWY1NTVhZjYzYTg5NTZhYzQ0NjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmMGQ3NTk1MGRmMGZmOWEzZWU5NjYzOWRjYzhmZjVlYWVkYjQwZDY4MmJlYTkyNjIyYjUyMGRkODg3ZDkzZTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOWM2YjNmMTRkNjAwZjcxY2Y5YzBmZDdkOThiMTljZjQ1MzMxOWYzYWQyMzFhMmIxNDA4YjRjZmQ2NTNjNDkifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2171-L2224", - "version": "repo-lines-v1:sha256:02cd0d582029e215a49f5473ec19e433dda398fb1bf3526919d5b4d87cad0e2b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyMDJkMWVjMzI2MWJhMDI3M2Y4M2Y4NzVjZDYyMDE0NGVlYjdmNmE4OGQ3NjRjZjUwMDFkNGY3MzI2ZGE4ZjAifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2790-L2820", - "version": "repo-lines-v1:sha256:9f1fd5a5eabda463aa83e327bcdf01a23021582c819550c870538be441fa237d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkOWI4N2E4OTdlYjVhOTk0MmY4ZTliNjFhMTk0ZThlZDQzODg3MzE3YTlkMmZmZWUxYzI2NzgyYWRjYmUwOWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkMGJhMmVmZGVlZTg3MTExODdlMWRlMDc5ZTZkNmYzMTVkYzUyMTY1MzdmMWMzZjc4NzFlMjE4ODljYjY0YzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4ZDUxOWQ4ZGMzYjIyZmUxOGIzMDY2MjU0OTU1NGI1Zjc2NDMwM2MyOWI0NTZjNWNhYzdiZjUzMDM5NTdlNDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNWNmZjQ1MWYyZTI3MmQwOTc3OWQwYmExODM3NzU2NzcxYzIzMzA5YjQzMmQ4OWMzNTIyYmI3YWM0ZmJlMWUifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2147-L2255", + "version": "repo-lines-v1:sha256:f7c4fb5c08a0616ca44af1d854b265e1fdf05d6e8d98c0ba3659712fb92a714e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA5LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwZjAxMWIxODViNWVjNTNlMDBmNGJiM2RjMDVhZDkzZDBlZDlkN2E1ODQwOWNhZGE3NDkwZmFlNjRmODNmYjljIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhZDllZDdlZjJmZTQ4ODA2MjdhYTIxNDdkYzRlN2IxNDgwYzJmMGY0NTQxNGFhYTJmZGNmNGNjNmEyNjUyMTJjIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YWE1NWE3YTE2MTk2YjkxMzQ1YTQyNzUzMmVmMjQ3YTNjZDMyMzI2OWFlZDhiZmQyNDczMGU0MTM3ZTk5MzAzIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIxOTNjNGJiNDJjZGQxYjg4NzQwMmM2ZDUyMGE2NGY5NmJlNmRhMmRjYmFhN2IwZjA4NTQyMjE5MmM0NDk3YmZkIn0" } ] }, @@ -877,10 +861,58 @@ "version": "repo-lines-v1:sha256:b7653b07c27d97951ef1ff2e860483c8774a6281bedddb2049bb368cfcb7a2f5:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4NzY2NjhiZmJlNjM1NGJjNGVmNjQ4NjBlMjVmOTkzY2ZjMWJmZWM4NGVmMjFhMmE1ZWY4OTM5YzBlZjdiMWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3NmM1MmY0NWEyMTRkNmRiYmJmMTIwMDRlYmMwNTE1MjkyNzVhZmE2OTlmZGEwNjcxYmFhNmFjYzZlNzVkNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2NmU4ZjFjYzllNzRkNTllYzgxYjhiZjBmNmY5ZWQ4YjRjNjI2YjRlNjg4MmZjMDBhZGU3MTgyMzk3Y2Q4YmUifQ" } ] + }, + { + "id": "claim_4a9771452ead45e28f2078d9182a28b6", + "statement": "Captured configuration failure persistence retains the controller, conversation and repository until the save task returns, replaces the enclosing preparation task without self-await, and refuses edit supersession plus direct edit/fork operations until retirement; ordinary prepared edits remain supersedable.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L134-L157", + "version": "repo-lines-v1:sha256:b98f89f38a02619fe24af1c5df67726ef33c1f3737119fc09f407ec57de37c4d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNmYjUwYTQxZDQ1MjY0ZjQyZDRiZGJkYzA1ZmQxODQ5NDdlODQ1MGRkNmJlNjFjOWRiN2ZiYThmYjA5ZDRiMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRmY2MxYmZiNTgzZDUyMDA0NjUwMTdlZTE3YzBlNzE5MWQwOTZjYWQ1MDVlYzcxOGY3NTQ3ODEzMTFlZjcyODQifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L652-L686", + "version": "repo-lines-v1:sha256:204b187bc56e58ba5718dae42d8c67838ce91fe119dddd523ff2b5b479bf3157:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhYTU4ZmU0MmEyZWE4ODVmNDg3OWQ3MzVlOGJkMzU2ZTczYmEwZDFkNjkxYzc5MWVmZmU5YmE5YzJjNDg5ZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg3ZGNlYTZjODRkNTBjODkyMjYzNTcxMDQwMmQ5NTdhZGViMzI0ZDg2YmY5NzBiOTQ4NWRjNmUzZGY0NGFjOGEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L580-L645", + "version": "repo-lines-v1:sha256:78614a89ac0b4b379fa6a786e94ecde151212950698960df912627c62ae98356:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU0ZDAxODA3ZGNmMmZiYmQ5OTlhOTJjMWJhMGY2YmRkNWY4MGViYmRkOThjZTQ2MWYwMGRlZjBiNTQ3OTgwZDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEzOTk1MmMwODMwOWU3Zjk4MzNiMDc1MmI5NjNlNDIyZWZlYWUyZDI2NmQxY2YzYTc2NWEzZDZhN2M3YTU3OTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI5ZDI3MjNlYzczZWMzZTlhODdmM2QzNmI0ZTU2Y2NkZGYxZGI0MDM4OTk2YjQ4NTczODEwM2UxOWFkMjdhODEifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L784-L820", + "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1088-L1127", + "version": "repo-lines-v1:sha256:b17c24ee237eab5393c8275af0750d4c90435d607878c72ba65fb3664e1f1664:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1MmFjMDJlY2QwNWE5M2M1NGI3NDdjNDIxM2IxZDdmMjY5OGM4ZTRlMjBiYjNhMGI2ZGZiYjZjMTc4MDE0OGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBmMDMzM2U2ZTFlZjc5ZTY1MDliN2ZkNTA5MmZiMDFmMzY5NDQxODk0YzNmZGVjMTliOTU3NTY4NmJjNzc5ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1YjAwYjk5MjY2NDI4ZmU1NTI1NDYwZDE0YzNhMjZlNDI5Yzc3Yjk1ODUyZmEzNzEyYzEzYmNiMTMwM2I3MTAifQ" + } + ] + }, + { + "id": "claim_72ba80e4518341798ba555bef3508fca", + "statement": "Controller regression fixtures use blocked save attempts and synchronous MainActor join probes to check ownership, Stop/controller joins, save-before-archive/delete ordering, edit/fork refusal, lifetime after caller release and injected save rejection without staging adoption. The controller failed-edit restore fixture checks payload, context, permission and chronology through a fresh ModelContext in an in-memory SwiftData container, not a disk failure or process restart.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L196-L269", + "version": "repo-lines-v1:sha256:7697da7b2aab9dddc1b7d45cb0d33eb8cd52a84562a516b2156d742ce8a0d558:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYjg4ZDJmYmY0Njc5YmVjNzJmYjM3MWQ4ZjVmNWMwZGU3YzM2YWEyNDFjN2ZjYzU3Y2NhZGM2NmQ2MTQ3NzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L652-L717", + "version": "repo-lines-v1:sha256:6b8eb70bb6c318b4e14ac50fde58fbd362830d046c50e1727140d6980e727740:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhYTU4ZmU0MmEyZWE4ODVmNDg3OWQ3MzVlOGJkMzU2ZTczYmEwZDFkNjkxYzc5MWVmZmU5YmE5YzJjNDg5ZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiM2M1MzE3OGIwNjU0NDdkYTk1NjJlYjVhMTRjNDMzNDVlYmQyZWRkYjlkMmQ5OTFkZGIxNTQzNjkyNWYyMGQifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L797-L814", + "version": "repo-lines-v1:sha256:af356e00debb9d78ef75b58cde8ef7cbc573512c578ef87eb17ed868648285b6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ0NzUxNDBlMjZlOWM1YTE3MTFkNjQ0NWExOGI3ZjRhZTIwMjJmOTkzZWY2Mjg0MDc1NTQzMDFiOTZlMGRjMjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBmN2Q5MjQ1MDBjZTE0ODEwOTUzODdhNWU5ZWIxYmMzYjFlM2VhYWY1Y2ViMzBkMTQ2Y2MwNTU0YWU1MWRlNmIifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L89-L232", + "version": "repo-lines-v1:sha256:06417f664159d51d61d0c4b32815f91c17e12c0127151e5dc657a463e34aa7cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2ODc2Yjk2OGUwYmE1M2U1NTE2ZDdkMjM4ODIwMjU2NTYzZDk1NTM0NzhlZmMxMDZhMWFhOTQ5ZTFjNGE5OTJmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmYmE3MWNhMmVmMjRjOTljYWUwYzAyNjNiMzdlZTBkNzlhNjI0MWQyY2FhYzU2ODRhOWYyMDlmNzYyN2ZmMDZkIn0" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:11:02.802Z" + "at": "2026-09-23T06:54:45.592Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index abfa490f..2c3475dd 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6f9cbf0a3aa5fea9637709e225bee6cf28af7cdf172609b3c903f75bade148b7", + "pageVersion": "sha256:f69ed12d7d30fae123a7513fe0417b3822282a643f6ae6127089ba9bc407b7c8", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", @@ -412,25 +412,51 @@ }, { "id": "claim_b0b5b4708fda4de6bcc5775728746d34", - "statement": "Application history mutation publishes only after truncation commits, but captured missing-model/credential failure still projects live before a separate asynchronous save; live failed/interrupted adoption therefore does not turn preparation completion into a universal persistence barrier.", + "statement": "Application history edits publish after truncation commits. Captured configuration-failure persistence remains preparation-owned until return, and success plus identity recheck precede failed projection and staging adoption; rejection reports a persistence error and retires with staging intact. Synchronous Stop preserves the save task without cancelling it or adding interruption; stopAndWait or awaiting the controller-cancellation task joins it. Archive/delete await it and edits/forks cannot overtake it. Queued-runtime cancellation still projects before saving, and a join guarantees attempt completion rather than storage success.", "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L12-L81", + "version": "repo-lines-v1:sha256:5094626afffa14ec455d6b6233a6753c625cc0d6c8892b5acd4403a8649d1e3d:eyJzZWxlY3RlZExpbmVDb3VudCI6NzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkNWFkOTQ4ZjVhN2JlOWJlMTVkNTRhY2EwNzlmZWI4MDYwMjNlMjVjNjIzNmVmMWI0NTM0YzQ0NDA1ZTk1NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NDU0MDY3MDk1MTM2ZmQyMDViNmNhM2JkMGFjYjk5N2M0MDg5M2JjNGI4OWFjMzMxN2YxZmNhYzFiMzc5ZmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViZDk1YzE1MTE4MTI1NDYxYzI5ZGMzMGJjNGJlOTFkZGUyOGI1NDFlYjJjM2NiZTkwNzc4NDA4NTFiZDMyZDcifQ" + }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift#L129-L147", "version": "repo-lines-v1:sha256:45383d212fb6991f01b24f3ea84538b0f619b50ed950673b4c79037e4de1652d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3ODhmN2QzZWVhNGY0NTY0NzdmM2QwMjliNTAzZDI5YmU4YjExOTNiNjJjZTZlOWMzZTk5ZDQ1NDczMjViNDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzBkMzQxYjc5NmIyZjQyNTk4YTk3M2UwMTJkOGIyZTZjNTE2MDkwYzliMDQ0YWMwZDhkYTIyZjU2MzI4MzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L719-L793", - "version": "repo-lines-v1:sha256:81156ef97d808d87d63a184f6020940cf4a5bbbdecd8dc55a2d3a3c7e7b0b60c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjhlMjRkZDNlZmY5YzY3YTZiNzg3ZWMwMjA3ZmMxZTI0ZGMxZTcwZmRlZWViNmFjOTljOGFkNjA1Yzk3YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNzdhMDY4Y2ZiMmUwMWRiMDJlMTAyZDRiODhhOWMzYWRlOTU3YTNhMWMzYjczOGE3ZTBjMWNmMTc5MTUwMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L697-L820", + "version": "repo-lines-v1:sha256:573ad87c0b11a4de84485b6efb502dec46851957f042f8e9ec82bd6e97473bd1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5NjAyZDY4M2JmOTNkNjM2MTgwMmU2Nzg5Y2UzODc1Yjc4MDJhYzk3MWU2YmRjNzhkNGJjNmJiZDRiN2FmN2ViIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NzVkZDcxOTdjZGMzM2M2MDAwMDg2NzJjNjYxMmRhZjVlMjJjMzMwMjQ3YTNhZjBiZjgyZTQ4MDJmMzZlZTIzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2YjBkM2YxNGUzNjMyYmE5ZjBlM2I5YTJhYTM4M2UwNjVhYTBhY2UxOWM1MTU4NTA2YTliMWJjNzQ1MjExYWU4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0YzZjN2NkYzhjNGVjMGJjOWRlOGU2OTE4ZmFlMGFhOTg0NDEyYjQyY2U1YzM2OWVhMWUzMzI4NDdmOGYyMjliIn0" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2040-L2092", - "version": "repo-lines-v1:sha256:d1a56a12b39372c0277e40115884e11b8c29d16d3df1e847c7407b89efbd1881:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkOGQ3MGNkOTIxY2E1NTJmMGEwZWIxMGJhNzc3OWQ2OTk0M2RlZjUwY2NhNjQ5YjIyZTg2MzU3OTgyM2ExM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhZDUyYWNhNzhkODcxNDMzZjJhNzMzZGJmMDQ3MGNmYTZhNGEwZGQzM2IzY2IwYzgyMTJlODNkYTIxY2ExZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiNTdmZTYwMTUxZDJlN2IwYWNlZTEzOTJhYzI5OGUxYzBjOGE2OWRiMzlkNWU4OTllYzNkMzdkMTM4Njc3ZjMifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L78-L96", + "version": "repo-lines-v1:sha256:a441f3d481d80a2027d336b7fb106ab185a3e064fd227d09b20118538b189c60:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkZmM5ODBiNzAzODQyOTJlMmNkMTMyMzZiYTg3YTM3NmFkOGYyYjVmZGUxZmRmNzlhNTYxODk1NzI1NzA1NjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZhMzFiOTVhMTAzODBmZTgyY2ZlNjM1ODRkZDgyZDc0ODFmNjZjMDM0YmJlNjQ5ZDA0NmI5M2QzZjE0M2M2MDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmNWY2ZGIwODM0YjM4NzEyNGRlZWVmZGZhMjFhOGIwN2UwN2FiYTM0OWFiMWRkZTNlMDg3Yzg0ZGE5NmEyYTIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1088-L1127", + "version": "repo-lines-v1:sha256:b17c24ee237eab5393c8275af0750d4c90435d607878c72ba65fb3664e1f1664:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1MmFjMDJlY2QwNWE5M2M1NGI3NDdjNDIxM2IxZDdmMjY5OGM4ZTRlMjBiYjNhMGI2ZGZiYjZjMTc4MDE0OGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBmMDMzM2U2ZTFlZjc5ZTY1MDliN2ZkNTA5MmZiMDFmMzY5NDQxODk0YzNmZGVjMTliOTU3NTY4NmJjNzc5ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1YjAwYjk5MjY2NDI4ZmU1NTI1NDYwZDE0YzNhMjZlNDI5Yzc3Yjk1ODUyZmEzNzEyYzEzYmNiMTMwM2I3MTAifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2147-L2261", + "version": "repo-lines-v1:sha256:0507eb851091785bbec9ddf2adbdc3047ef267037f79729cb97dbdf4633d0da4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwZjAxMWIxODViNWVjNTNlMDBmNGJiM2RjMDVhZDkzZDBlZDlkN2E1ODQwOWNhZGE3NDkwZmFlNjRmODNmYjljIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI2MWU3ZGUzMTg3MTE0YzQyZDkxNGMzMDQzOTgxNGRlMzg3ZmMxMTAyOTRkNDcyZWY0NDBmM2E0ZDMwMjAxNGIxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI0YWE1NWE3YTE2MTk2YjkxMzQ1YTQyNzUzMmVmMjQ3YTNjZDMyMzI2OWFlZDhiZmQyNDczMGU0MTM3ZTk5MzAzIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwNmVkYWIyM2FkYTZkZjU4NjgzZTE0MDliODIzMmVlNDk2MjgxNGFjMTNjOGE2M2QxOWJlYWQ2ZDA0YzFlNDYzIn0" + } + ] + }, + { + "id": "claim_58181193299f42449d8640e3a109a2cc", + "statement": "The controller-owned failed-edit restore regression uses a fresh ModelContext in an in-memory SwiftData container to verify one failed adopted run, captured payload/context/permission and staging chronology; a separate controller fixture injects repository save rejection without adoption rather than simulating a disk transaction failure or process restart.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L196-L269", + "version": "repo-lines-v1:sha256:7697da7b2aab9dddc1b7d45cb0d33eb8cd52a84562a516b2156d742ce8a0d558:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYjg4ZDJmYmY0Njc5YmVjNzJmYjM3MWQ4ZjVmNWMwZGU3YzM2YWEyNDFjN2ZjYzU3Y2NhZGM2NmQ2MTQ3NzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L689-L717", + "version": "repo-lines-v1:sha256:bbb94ddd10755f8cf1b1844ea133c2896ffb57c69057eddb56ec03c7c3ea745f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2MDA0NjNkOTgwYTgxZjIxMzI3OTc0MTJmY2M1M2RlODJkNTg0YWNjNGJhMTFjODdkN2NmNzNmZTg0MmRkY2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiM2M1MzE3OGIwNjU0NDdkYTk1NjJlYjVhMTRjNDMzNDVlYmQyZWRkYjlkMmQ5OTFkZGIxNTQzNjkyNWYyMGQifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T03:40:50.823Z" + "at": "2026-09-23T06:59:53.554Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 342ccfe7..f1c1f8ae 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:5533c3350dcd3e0218aa0071621fea4658eb209ed33958d8f719a4f6752f4cc6", + "pageVersion": "sha256:dfca6fed3c974476cc25cd9f575515138b3897864e77e682e4f791d86f56756e", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -112,7 +112,7 @@ }, { "resource": "repo://AGENTS.md#L40-L48", - "version": "repo-lines-v1:sha256:e0e8c9958812182ec2d7001a28a31979a8ef4bba9255fe9f04867d56046faca6:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTdmYzk4NmVmMDFlZTVmNThjNmYxZmEyNjViOGE2NDEzMTE2OGY4YWE1OTkzZDc4ODZkOTZjNmUxODk4OTQzMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODQ1MThlZDQyNWZhZWNhNTgxNGExYzEwMTU0ZjEzMjgxOGViM2VjMGI2ZTgzOThkZjQ5NGI4YWRmN2MzYTI0NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDFkYTg3Nzk4Y2M5NTc1N2I3NzQ0NThlYzQ5Yjg0MWRjYWMzODA1MWQ5MjljZGI5ODE5MGJiMTdiMjdkZmQ4OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGVkYzQzMzI5OGFkNTgxMjllZTBhODg1MmMxM2Y1MjkyMGRhYWY5NGE4YzY0YTQ4NzFlZWVmNDZlMTMxNWVkMiJ9" + "version": "repo-lines-v1:sha256:b27ae6846aa3bf61e897870b4f13db11ff5622aa74b65e7c5b153d79066742e2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3ZmM5ODZlZjAxZWU1ZjU4YzZmMWZhMjY1YjhhNjQxMzExNjhmOGFhNTk5M2Q3ODg2ZDk2YzZlMTg5ODk0MzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0NTE4ZWQ0MjVmYWVjYTU4MTRhMWMxMDE1NGYxMzI4MThlYjNlYzBiNmU4Mzk4ZGY0OTRiOGFkZjdjM2EyNDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxZGE4Nzc5OGNjOTU3NTdiNzc0NDU4ZWM0OWI4NDFkY2FjMzgwNTFkOTI5Y2RiOTgxOTBiYjE3YjI3ZGZkODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZGM0MzMyOThhZDU4MTI5ZWUwYTg4NTJjMTNmNTI5MjBkYWFmOTRhOGM2NGE0ODcxZWVlZjQ2ZTEzMTVlZDIifQ" } ] }, @@ -125,10 +125,24 @@ "version": "repo-lines-v1:sha256:a556c3aea2c14cdaa85d89a15243c4606e48562f600fe675a3e4f75594c7a07e:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2VkMDNiODMzODVkNTYyYTZmN2VhN2FjNzljMDRjOTAyYjliNDc1YmU2ZGRmMGJmZWU4NmJmNGY1MWYxMTAyOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODI1YTI1OWIwZmM4NzNiZDgwYjhhOTVlYzE0NTVlYjI4YWNmMDBjZDE1YzAzZGQxN2Q5NGU4YmVlNDI4NTEzYyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTAyZjA5NDM5NjA4ZjcyZGY0MzQxYzNiZWJlODVjNDFiMGI1YWQyNmFmZjY1ZTYwZDhjZGI2MzAzNjAyNjAxMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiJ9" } ] + }, + { + "id": "claim_f46780cd3b9b4898b21d6d93e20fafda", + "statement": "StoreKitTrialTests requires manual authorization, is skipped by default and runs only with TERMIND_STOREKIT_TESTS=1; repository guidance recommends an explicit narrow suite selection.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L38-L44", + "version": "repo-lines-v1:sha256:b4bdfc1dd31e3110c8db7dc96616e1c6f44099a823533aba94f92226bd89d638:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTdmYzk4NmVmMDFlZTVmNThjNmYxZmEyNjViOGE2NDEzMTE2OGY4YWE1OTkzZDc4ODZkOTZjNmUxODk4OTQzMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGUwNDgwMzIwNDhlODdiMGE2MWE3ZjMwMTU4YTE3YTZlODA1MWI0YzQ4ZDUwZTE0YjJmYjY3NmI4MDBlYTJjYyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDFkYTg3Nzk4Y2M5NTc1N2I3NzQ0NThlYzQ5Yjg0MWRjYWMzODA1MWQ5MjljZGI5ODE5MGJiMTdiMjdkZmQ4OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L12-L22", + "version": "repo-lines-v1:sha256:c502b82a80a53329d256f638301cdaa11432ebba24ee9005eec7d39ed1f5dd65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTU3NzFmOGY3ZmZmZjlhMjNkOWZiZTMyYTlmYzAwYzc0ZjRjYTU2NWY2NjFhNmQ2YmYxNGRiNmY2ZTlkNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNjVlZGIzMWM5NDAzM2M2ZWVmOTIzOTQ3OWMzODczYzY0NzhhYmMyMWZhZDZlODJkYjJhNWU5ZDMyYzRhNmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwNjZjMGUyZDJhNzZhZGViYTcyYzdmMjY3OWJlMTNkM2UzZWM5MzgzNzFhODliNTUzM2I0OTk0OTQwZTBhOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzY2EyMjE1MzJlMmQwOGM1YTgwYjA2MDJmNTBkMzIyMjBmMjZhMTBlNGFmYmYyMzA1NWU2ZGU2MmY3Yjk2ZDkifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T06:54:45.592Z" } } diff --git a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json index 564f5210..d7fcc69e 100644 --- a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json +++ b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:6bee77cc55ac7f4209d5ea3e889e5ef21b88a7e3f39118c9fa0dffe665855886", + "pageVersion": "sha256:3c5890c5831313021a01c7840ee3ab3fa0b82708cc9e2b60a336255809efbf3f", "claims": [ { "id": "claim_b7fbd64158554b8b8ca80a0a613b3bc3", @@ -275,44 +275,104 @@ "statement": "Application edit tests reject invalid replacements before repository calls and preserve live history after injected repository rejection; controller cancellation after committed editing checks interrupted staging adoption. These fixtures replace the persistence port and do not inject real SwiftData storage failure.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L228-L266", - "version": "repo-lines-v1:sha256:d504dc650e02ce7236152c633382cef19bd6762a026047c05f7c1ad0785f7a7c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyNWIwMmI1NzE0ZDA2NmZkMzIxNmQyMjMxYmRkZGFjYjM2ZDcwODc3OTE4NmRhYzg5MjQyYWViZDA2ZjQ1M2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiYjYzYjhkYTE1ZTdkOGZhNDBlNjhmYjhkNjY0YzhjZDQ4NGFlMzQ4NGEyMjEzNWU0NDAwYjA4NjI3Yjg2ZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNWU4YzMwNjkwMDIyNmYwNDA2OWM5YTZmMTZjNmY0ODE2NzkzYzM5NTNjZDM3YWFlNzQ5OWY2OGQ1YjAyNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" + "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L473-L514", + "version": "repo-lines-v1:sha256:70c017e4fd4742ac1a8eff8c685ae4daf7c00755d6c14a68ae2e962fa7c18613:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyNWIwMmI1NzE0ZDA2NmZkMzIxNmQyMjMxYmRkZGFjYjM2ZDcwODc3OTE4NmRhYzg5MjQyYWViZDA2ZjQ1M2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNWU4YzMwNjkwMDIyNmYwNDA2OWM5YTZmMTZjNmY0ODE2NzkzYzM5NTNjZDM3YWFlNzQ5OWY2OGQ1YjAyNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmNzlkNTgyZDc4ODhjODJjODQ4Y2M2MGUxMmNhNGYxMzU3MjhjNWIwMmEyYjM1NGMzZDE3YTBmOGI0YmMyNDIifQ" }, { - "resource": "repo://FrameworkTests/AssistantConversationOperationsTests.swift#L290-L309", - "version": "repo-lines-v1:sha256:1aadebda28ab65aaf172c7524dd75bfbc882c73879618892eee83488349e51aa:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMGI5NDg0YjQ4YTE3M2QzMTM2ZjhkYWRiNzdiNTUwYWQ5ZmRiMzU4YjMyMjllNTRjOGQyNjg1NGNkMWVlNGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc1MjhmNGExMDgwN2M3ZDcxOTRjZTUxNTlmMzNmMjFiZjM1YzYxMzA5ZGNhNDkzNjgyOTViZjBkYzE3Y2QyYTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkMWY5Njk2ZTM2ODQ2MzcxNzIxODJiOGMzZmNlMWFlMzQ4Yzc4MzJkOGU4OTE0ZDVjZjJlZjU1NDIxNDIwYWUifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L596-L619", + "version": "repo-lines-v1:sha256:9013a9b0c8dda08deb9c74c8b93dd354b89a5d981c5c43457c083341a7639434:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjMWE5ZjIzZTE1YzkwM2U4NGYxMDdkNGI4YmQ2NDZhYjcwNmRkNmEyNjczZWI5N2VhMTZkODc0ZTRhYzMxZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwNTYyNGUyNDJmZjU3YTY0YjU1M2YxMTQzNzBlMmQxZmViYmU4ODIyZjI5NzA2ZmZlYmI4YTgwZjVhM2FiMzcifQ" }, { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L450-L534", - "version": "repo-lines-v1:sha256:fd0683e00df11cd8ca35568565d1b1d43a68ad37ab7bf54e5f68803954f14349:eyJzZWxlY3RlZExpbmVDb3VudCI6ODUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkYTc3OGQ5NDJjNzRkNGFjOGYzMmM1MTgyMDg2MGFlOGE2MDQ5YWE1NDUwYTE5NjMzMTM4MmNmNWVmNWU1MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L719-L747", + "version": "repo-lines-v1:sha256:cafc285231544b00272d58ff3d5e4205703f2bd167937c426e285a131b1184ec:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE4ZGIwNzEzYzU1NTE0MjI3ZmQ3NGRmMTNlODYzZjQyMTU0MGFiMDk1YzVhNDU2Mjg5OTJiYzE3YjdlZDE1M2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk1NTUyNmNmOWEwOGQ4Yzc1ZTA5ZThhMWZhMTIwODkxZmQ3MWEwMDNhMjkwY2U5ZTEzY2YwOWQ5NjRmYjkxYWUifQ" } ] }, { "id": "claim_9b91e98a29a34c4caa268050b13a5f3e", - "statement": "Missing-credential and selection-during-preparation tests wait for the recording repository's failed snapshot as well as checking live failure because production captured failures publish before asynchronous save; actor waiters handle both already-recorded and future snapshots.", + "statement": "Captured configuration-failure tests distinguish attempted snapshots from completed saves: preparation remains owned until return, successful save and identity recheck precede failed projection, and rejection retires without adoption. The recording repository uses actor waiters for existing or future events; synchronous MainActor join probes check Stop/controller joins remain pending while the single terminal save is blocked.", "evidence": [ { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L231-L255", - "version": "repo-lines-v1:sha256:63509c11ab1c2718bead3930614de6427e1e6df47c0e787aeed401498661891c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY0YzVmZGJhOWRmZTUwNjMwNjlkYTdlN2RiZWVjM2QzZWRiNjE3YmUzMTQxYjBmNGEyYzY0OTEzNWI0N2M2ZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyMzkzYWYzYmRiOTExYzA2ZWFmMjNkYjMxZTkyMmNkNGY3NDc2MmYzNGFmMTE1NTJkNDczYzc1ODhhZmFhZTkifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L797-L814", + "version": "repo-lines-v1:sha256:af356e00debb9d78ef75b58cde8ef7cbc573512c578ef87eb17ed868648285b6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ0NzUxNDBlMjZlOWM1YTE3MTFkNjQ0NWExOGI3ZjRhZTIwMjJmOTkzZWY2Mjg0MDc1NTQzMDFiOTZlMGRjMjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBmN2Q5MjQ1MDBjZTE0ODEwOTUzODdhNWU5ZWIxYmMzYjFlM2VhYWY1Y2ViMzBkMTQ2Y2MwNTU0YWU1MWRlNmIifQ" }, { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L66-L86", - "version": "repo-lines-v1:sha256:80d25cc0451a928bc50f91e07a28b411b83c0d7299889938c179b8d5e96fb33b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1OGZlNzcyYzJiNDExMGEyYzJhOWQxYTk5MjI3OGY4ZTNmZDg5YzMzNzJlZjZmYjE0Y2NjYmU1NDU3MmQyMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkMzMwZjllZDlkNjUwYTVhMDRlNTk4Y2YxNmE2NmZkOTM3NWZjYWIxMzhkMmJiNmE0MWYxNTJjYzRjMjExNDQifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L876-L963", + "version": "repo-lines-v1:sha256:3375908ebaf9b2d4c4f15c8b1a3cf12eb10655fea5770f3f4c8166873cd09c59:eyJzZWxlY3RlZExpbmVDb3VudCI6ODgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkYTE1MzZiMTI0MWUxNzZmNjhjNjhmYTdlZWM3NTkwYmI4M2JmMmExNTVmNmEwNGE2N2FkMDJjODJlZTYzYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAwMDQyZDA4N2MwMjJlZDJjZDM1YjkzODg0YTkyZTU3NDAzN2UyNjJhMjg0Mjc3MzI3MTRkNjM2ZjkxMWVlOWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdjMzk2ZGVjMGY5OGEyZjE4ZGUxMTk1NGMxOGJlOGQxYjdiY2VhOTc2YTQ5ZjQ3ZjQzNTgzZGE5ZGY5ZTY5ODMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUwNjNhNTViZWUzYmQxYjU3Nzc4NDgzYWRhOWRkOWEzZDE4OTM1OTcyOGI3NGU4YzE3ZTBlNmU0ZmVhZWVmODgifQ" }, { - "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L661-L689", - "version": "repo-lines-v1:sha256:74df831f049f3d94b2df3e3684005e41efcfdbae4210f2a76d72f07088772ff4:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3M2UyMmU5ZjA4MTYzZWY3NDM5ODE2MTc3YzcyODZiZjg0MGRhODFhYTAxODc0ZTUxOTExNDM3NTNhNTIzNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4YTQ5OThkMTFmOGNhNTNkOWY1ZWE2OWQ3ZmJmYmE1MGViMTFhMzBhMTdkNDZiNzQzZDZiN2EyNzA5MDhlYzciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVjYWZkOTVhYTdjMWM0MzA0YWEyNTI0OTg4YWNlMTM1NjQwMjU1MGZjYTg1ZmY5ODlhODEzYjNmNTEwNmJmZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRlMzE4ODBjNjRmODIxZTk5ZmMxNGNjNWE0MDY5OTE0MzQzZTIwNGUwYmQ3N2U4NjU5ODhjMzQ1Y2RkZTdiMDgifQ" + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L89-L131", + "version": "repo-lines-v1:sha256:0aabaf1fd1e04c8fba54bf8ea2f77d091b5d3ea63e944027c2702ee290dfe3e4:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY4NzZiOTY4ZTBiYTUzZTU1MTZkN2QyMzg4MjAyNTY1NjNkOTU1MzQ3OGVmYzEwNmExYWE5NDllMWM0YTk5MmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWNkNjA4NmI0MGM1OWViOGRiZGFmOThjNzMzYzMyMWM5MDgxMzFjNWU2ZmQ4NjBhNWY5NjQ4ZDZiOGM3YmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L765-L793", - "version": "repo-lines-v1:sha256:e87d08aec434dbaca6f03d91f4f5c0cab6e7e04e6040b3e13dc277d1eff4ed2f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4MzY5NDdmMWVlZjZmNjIwZWJlZmQzMGE0N2EwNWI4ZDhiZWI2ZTNkYTFlZjkzOTM3MTg5OTZiZTI1NDkxYWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ODhmNzU3ODA1NDczNjk2N2U4NzY5MmJiYTg2YjI4MDhmMzI0MTk3NTQ0MDVlYmZkNmM0NWI3MTk4OWQ0NzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L697-L820", + "version": "repo-lines-v1:sha256:573ad87c0b11a4de84485b6efb502dec46851957f042f8e9ec82bd6e97473bd1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5NjAyZDY4M2JmOTNkNjM2MTgwMmU2Nzg5Y2UzODc1Yjc4MDJhYzk3MWU2YmRjNzhkNGJjNmJiZDRiN2FmN2ViIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4NzVkZDcxOTdjZGMzM2M2MDAwMDg2NzJjNjYxMmRhZjVlMjJjMzMwMjQ3YTNhZjBiZjgyZTQ4MDJmMzZlZTIzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2YjBkM2YxNGUzNjMyYmE5ZjBlM2I5YTJhYTM4M2UwNjVhYTBhY2UxOWM1MTU4NTA2YTliMWJjNzQ1MjExYWU4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0YzZjN2NkYzhjNGVjMGJjOWRlOGU2OTE4ZmFlMGFhOTg0NDEyYjQyY2U1YzM2OWVhMWUzMzI4NDdmOGYyMjliIn0" + } + ] + }, + { + "id": "claim_7dd64768ed39484797fa78c85266ebd4", + "statement": "Captured failure regressions verify save-before-archive/delete event order, asynchronous memory-to-failure transition, controller/conversation retention after caller release, superseding/direct edit and fork refusal with a retained assistant prefix, and injected save rejection preserving staging while reporting the error.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L134-L232", + "version": "repo-lines-v1:sha256:add34c3dc6b409429595dda053aa9f9a9f4e6a3da7dbf513bd2d2bfde8f03436:eyJzZWxlY3RlZExpbmVDb3VudCI6OTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNmYjUwYTQxZDQ1MjY0ZjQyZDRiZGJkYzA1ZmQxODQ5NDdlODQ1MGRkNmJlNjFjOWRiN2ZiYThmYjA5ZDRiMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiYTcxY2EyZWYyNGM5OWNhZTBjMDI2M2IzN2VlMGQ3OWE2MjQxZDJjYWFjNTY4NGE5ZjIwOWY3NjI3ZmYwNmQifQ" + }, + { + "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L652-L717", + "version": "repo-lines-v1:sha256:6b8eb70bb6c318b4e14ac50fde58fbd362830d046c50e1727140d6980e727740:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhYTU4ZmU0MmEyZWE4ODVmNDg3OWQ3MzVlOGJkMzU2ZTczYmEwZDFkNjkxYzc5MWVmZmU5YmE5YzJjNDg5ZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiM2M1MzE3OGIwNjU0NDdkYTk1NjJlYjVhMTRjNDMzNDVlYmQyZWRkYjlkMmQ5OTFkZGIxNTQzNjkyNWYyMGQifQ" + } + ] + }, + { + "id": "claim_93d64172184241cf8f5472dfee5a85ae", + "statement": "The controller-owned failed-edit SwiftData regression restores through a fresh ModelContext over an in-memory container and checks one failed adopted run, text/images/attachments/context/permission, removed staging and monotonic chronology; it is not a disk-failure or process-restart test.", + "evidence": [ + { + "resource": "repo://FrameworkTests/AgentReplayPersistenceTests.swift#L196-L269", + "version": "repo-lines-v1:sha256:7697da7b2aab9dddc1b7d45cb0d33eb8cd52a84562a516b2156d742ce8a0d558:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYjg4ZDJmYmY0Njc5YmVjNzJmYjM3MWQ4ZjVmNWMwZGU3YzM2YWEyNDFjN2ZjYzU3Y2NhZGM2NmQ2MTQ3NzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc5MjQxN2JmMzllNTM0Nzc3NGNkOTQ1NmY5NjExNzQzMDA1NWVjYTU0MDFkOGI3OWNmN2Q5YzJiYzM3YWQ5MzYifQ" + } + ] + }, + { + "id": "claim_f5aac7bd4e8b450088cc9bf29e8beeaf", + "statement": "WorkspaceNavigatorRenameTests is serialized and requires the visible-window opt-in; it exercises context-menu and double-click rename and verifies the editor starts with the complete title, which production seeds before publishing the workspace rename identity.", + "evidence": [ + { + "resource": "repo://TFM/Features/Workspaces/WorkspaceNavigatorView.swift#L324-L328", + "version": "repo-lines-v1:sha256:f8e4a5c0e7a2300738f7cce45a7c486f0bacfc92825f6d761fac53bc50ef1695:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWNiNGMyNDM1MzhjMjRmY2M0YmUyYTY0ZTdkMTBiNzkwNmI4OGUyNTliNmNmYWE5ZDJhNTZkMWE3Zjk4YWY3ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTUzMjgyNGM2OTFiNzdhNDUwN2EyNDhkNmVmMjNjMjRkNzRkZDIwNDQ0YTA2Nzc3ODI3YTcxNTljNzZhZjVkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGYyN2ViYzQwNDY2MWU1NjQzZTZhYmE1MTQwNzNiN2RiOGJmNDlmMjAzMmYxMTRjYmI4OGI4YWU2ODVlN2UyNyJ9" + }, + { + "resource": "repo://TFMTests/VisibleWindowTestTrait.swift#L4-L12", + "version": "repo-lines-v1:sha256:cfd19f71f216d3c053e0e94522292d5084c78db67cbe7e2d740a830c1c08390d:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDZkYzRhNmI2NzJiZTRkMGI5ZjA5ODI5ZGNkMzgyYzEwMWJhMWMwMGQxZjYzNTY3MDI4OWFiNWE1NzBiNTYzZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzNkOTM2OTExYzY5MzE5OTAxOGFmMTI5ZTA3Njg2YjY5NjgyMDZmMjk0MmY2ZGViNzdkNWJjMmQ0MGI1ZTJhMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://TFMTests/WorkspaceNavigatorRenameTests.swift#L1-L32", + "version": "repo-lines-v1:sha256:2c721458e519a812013020dc767cdd9f740f66ad200910e07c7aeff4a9e79b1b:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjMzYyZGQ0NjUwMDgyZDM0N2RhMTFjMmRlYzc0MjBjNDJjNmMwZDEzMzYwNTAzYjg4OGZjOTgxZmU3Njk3ZDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZjNGY2MWQ0YWEzNzkxMTQ4YTUyNGZmNzA2YWE5YTg2MjQwMDZkNGNhMTIzZWM3NWU4YjQzOTRlYmZkYjdhOWUifQ" + }, + { + "resource": "repo://TFMTests/WorkspaceNavigatorRenameTests.swift#L94-L104", + "version": "repo-lines-v1:sha256:7f43f16b1edffd49fc0d67c0ad2f6b0b02929bb07166fa9c92645704ac36aaa2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzYTVmMTM3YmZhZTRiNWFmN2ZjZGFkYzg3NjA4Njc5YjkyYmMwMTdmNTQ2OTIzZDg1NmU3OWM0YjNjZDYyYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkNWFhMDZhNzQ1NzBjMWVmMjU3OWM2ZmYyNGEyODM2YzI4YjdjMTBjMjMwN2MwNmZkMzYyYzUzOTBlYTJiMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI0NzM4ODM3Zjk3OWNlYmIzODdjOGU1OTc2ZTQ5YjVhOWNkNGMxYzcyNmI4OWVmZTMyNGIxMmQ0ZDNhYzU3NDQifQ" + } + ] + }, + { + "id": "claim_7c9b6608858b4bcc8a896f22150b1e9f", + "statement": "StoreKitTrialTests is serialized and skipped unless TERMIND_STOREKIT_TESTS=1; repository guidance requires manual authorization and recommends selecting that suite explicitly, so default-suite success does not show it ran.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L38-L44", + "version": "repo-lines-v1:sha256:b4bdfc1dd31e3110c8db7dc96616e1c6f44099a823533aba94f92226bd89d638:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTdmYzk4NmVmMDFlZTVmNThjNmYxZmEyNjViOGE2NDEzMTE2OGY4YWE1OTkzZDc4ODZkOTZjNmUxODk4OTQzMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOGUwNDgwMzIwNDhlODdiMGE2MWE3ZjMwMTU4YTE3YTZlODA1MWI0YzQ4ZDUwZTE0YjJmYjY3NmI4MDBlYTJjYyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDFkYTg3Nzk4Y2M5NTc1N2I3NzQ0NThlYzQ5Yjg0MWRjYWMzODA1MWQ5MjljZGI5ODE5MGJiMTdiMjdkZmQ4OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L12-L22", + "version": "repo-lines-v1:sha256:c502b82a80a53329d256f638301cdaa11432ebba24ee9005eec7d39ed1f5dd65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTU3NzFmOGY3ZmZmZjlhMjNkOWZiZTMyYTlmYzAwYzc0ZjRjYTU2NWY2NjFhNmQ2YmYxNGRiNmY2ZTlkNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNjVlZGIzMWM5NDAzM2M2ZWVmOTIzOTQ3OWMzODczYzY0NzhhYmMyMWZhZDZlODJkYjJhNWU5ZDMyYzRhNmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwNjZjMGUyZDJhNzZhZGViYTcyYzdmMjY3OWJlMTNkM2UzZWM5MzgzNzFhODliNTUzM2I0OTk0OTQwZTBhOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzY2EyMjE1MzJlMmQwOGM1YTgwYjA2MDJmNTBkMzIyMjBmMjZhMTBlNGFmYmYyMzA1NWU2ZGU2MmY3Yjk2ZDkifQ" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T06:54:45.592Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 15520fa4..52c497a7 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T06:15:26.424Z", + "updatedAt": "2026-09-23T07:00:31.695Z", "command": "update", - "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index 04cb2f23..efaef30e 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -9,11 +9,11 @@ "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/engineering-invariants.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:f62db2005beea84e609b860cb6d2e52e7a742faf98e3d9f088179b031465fbbe", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", + "pageVersion": "sha256:c8ad1f11ac788af965dd38b668950c45dda95ab336ac725cd1d9ab3e8f7ce8a8", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" }, "/openwiki/architecture/mobile-ui.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -51,25 +51,25 @@ "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/supporting-system-directories.md": { + "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", + "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d", "pageVersion": "sha256:bc62050f5799c6e7764ff9d08a0e721dda371eb74b7b8342346c59f747abb3b5", "completedBy": "codex", - "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64", - "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", - "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d" + "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64" }, "/openwiki/architecture/system-boundaries.md": { - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", - "pageVersion": "sha256:3cbf1be9eb94f0eb87d83be0a1277c9801cb4df9a3da898ef95b63b20b72a785", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", + "pageVersion": "sha256:47037593b68f4328e80cb60aadad6bb3290eb90e38ef9005c31e46440925be24", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" + "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "pageVersion": "sha256:d3bcf7c3b342b7c7e0a24bff200d9de3fd902e66a05ac6d1f8ff1046b23948d5", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", + "pageVersion": "sha256:7b1c2e0d961fa1fba7e88eb2273136e7a6950c3a06f80900d23aab9694031e69", "completedBy": "codex", - "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64", - "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", - "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d" + "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,11 +79,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { - "gitHead": "73b40a1b937fc7fa4e24208563cce22e50674495", - "sourceFingerprint": "sha256:606589a25f14109f9d01d3e3e814a6bcbf889bb3e8c86ffa63bd3f27ca488827", - "pageVersion": "sha256:6f9cbf0a3aa5fea9637709e225bee6cf28af7cdf172609b3c903f75bade148b7", + "pageVersion": "sha256:f69ed12d7d30fae123a7513fe0417b3822282a643f6ae6127089ba9bc407b7c8", "completedBy": "codex", - "completedRunId": "5cf94351-ac48-4e6e-b587-76affa9694d9" + "completedRunId": "8631be05-cca1-48cf-9f9c-5426ae3f8660", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -156,18 +156,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:5533c3350dcd3e0218aa0071621fea4658eb209ed33958d8f719a4f6752f4cc6", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", + "pageVersion": "sha256:dfca6fed3c974476cc25cd9f575515138b3897864e77e682e4f791d86f56756e", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:6bee77cc55ac7f4209d5ea3e889e5ef21b88a7e3f39118c9fa0dffe665855886", + "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", + "pageVersion": "sha256:3c5890c5831313021a01c7840ee3ab3fa0b82708cc9e2b60a336255809efbf3f", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index e4105cf3..ca104f1a 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -114,10 +114,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } +generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T06:54:45.592Z --- # Cross-Cutting Engineering Invariants @@ -506,8 +506,11 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo search, skills/registry, Guardian and preparation settings synchronously before memory or automatic compaction awaits. First-send promotion follows accepted capture; edit UI captures before creating its persistence task. Plan implementation captures acting mode and consumes attachments/armed skills once. Historical context replay does not reconstruct old model settings or skill-package bytes. -- Preparation is distinct from streaming. Stop/background expiration invalidates preparation identity synchronously and joins pending - history/preparation work before its interrupted save; stale captures cannot edit or start. Archive/delete cancel pending preparation, +- Preparation is distinct from streaming. While preparing, Stop/background expiration invalidates its identity synchronously and joins pending + history/preparation work before its interrupted save; stale captures cannot edit or start. Once a captured configuration failure enters + `savingFailure`, cancellation joins that one terminal save attempt without cancelling it or adding an interrupted snapshot. Archive/delete + await the same boundary, and edits/forks cannot overtake it. The save task retains its controller and conversation until it returns. + Archive/delete cancel ordinary pending preparation, manual compaction refuses it, and navigation alone leaves it running. See [Assistant Runtime](/openwiki/concepts/agent-runtime-and-assistant.md) for the preparation boundary and existing runtime/recovery limitations. - History edits validate before persistence and publish their live replacement only after truncation commits. The boundary is the selected @@ -517,7 +520,9 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo - An edited replacement initially owns a staging run whose ID equals its message ID. The first real snapshot transaction adopts only that staged row and deletes the staging run; historical replay rows keep their owners. Live running, failed and interrupted projections use the same provenance and preserve the staging chronology lower bound. Retired run IDs reject late saves/events in the repository instance. - This is not universal persist-before-publish: missing-model/credential failures still project live before their asynchronous save. + Missing-model/credential failures publish failed state and adopt staging only after their save succeeds and identity is rechecked. + Save rejection reports the persistence error and retires preparation without adopting staging; joining the attempt does not guarantee storage + success. Queued-runtime cancellation retains its separate live-before-save projection contract. - Project tool output once before `toolFinished`: bounded preview and scratch reference, retaining crash/replay metadata and paging offsets. Later completion never shortens earlier results; `sftp_read`/`scratch_read` report complete lines/truthful offsets. - Terminal suggestions use latest delivered user snapshot, not focus. Surface availability is partial and does not restrict independent SSH/ diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index ed6e9a8a..55372f0d 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T06:54:45.592Z --- # System Boundaries and Dependency Ownership diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index eef1b671..b10fb783 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -80,10 +80,10 @@ sources: resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T06:11:02.802Z" } +generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:11:02.802Z + at: 2026-09-23T06:54:45.592Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -176,9 +176,13 @@ This sequence shows an accepted draft submission and the persistence points afte `ConversationModel.isPreparingAgentRun` is distinct from `isStreaming`; `isAgentRunActive` combines them for activity guards. Preparation has no runtime that can accept steering, so it must not masquerade as streaming. When neither compaction nor memory loading is needed, preparation can hand off synchronously without an asynchronous preparation task. -Stop, controller-wide cancellation and mobile background expiration invalidate preparation identity synchronously. Cancellation cancels and joins tracked edit-history/preparation work before finishing its interrupted save; completion rechecks identity after the save so an older cancellation cannot retire a replacement capture. The concrete SwiftData truncation method checks cancellation before entering its synchronous transaction. These boundaries prevent a stopped or superseded preparation from later editing history or starting a runtime; they do not roll back a history transaction that already committed. +While a capture is still preparing, Stop, controller-wide cancellation and mobile background expiration invalidate its identity synchronously. Cancellation cancels and joins tracked edit-history/preparation work before finishing its interrupted save; completion rechecks identity after the save so an older cancellation cannot retire a replacement capture. The concrete SwiftData truncation method checks cancellation before entering its synchronous transaction. These boundaries prevent a stopped or superseded preparation from later editing history or starting a runtime; they do not roll back a history transaction that already committed. -Archive/delete operations cancel and await pending preparation before their existing activity guards and persistence work. Manual compaction refuses preparation, while selecting another conversation leaves its preparation running. Automatic compaction uses the captured model/settings and the valid transcript available during preparation, with persistence preceding the live projection change. +A captured missing model or credential moves preparation into `savingFailure`. Its one terminal snapshot save replaces the tracked preparation task, and the enclosing preparation task returns without awaiting itself. The failure task retains the controller, conversation and repository until the attempt returns. Preparation remains active throughout the save; only success followed by the current-identity check publishes failed state and adopts an edited staging run. A rejected save appends the persistence error and retires preparation without publishing a failed run or adopting staging. + +Cancellation during `savingFailure` returns that existing task without cancelling it, dropping it, or adding an interrupted snapshot. Thus Stop followed by `stopAndWait`, controller cancellation and archive/delete all join the same save attempt. This join includes a rejected attempt and does not guarantee storage success. While it is pending, edit supersession and direct edit/fork operations are refused, preventing a new history mutation from overtaking the failure snapshot. Ordinary prepared edits remain supersedable. + +Archive/delete operations request cancellation and await pending preparation or failure persistence before their existing activity guards and persistence work. Manual compaction refuses preparation, while selecting another conversation leaves its preparation running. Automatic compaction uses the captured model/settings and the valid transcript available during preparation, with persistence preceding the live projection change. ### Streaming, steering, and cancellation @@ -250,7 +254,7 @@ Tool cleanup follows ownership before time. Known source-message and run-local c The replacement is durable before a runtime exists: its message ID also identifies an independent, completed, failure-free staging run containing that one user row and no tool graph. Its start time is placed after the latest retained run if clocks tie or move backward, including when the earlier retained run has no messages. On the first real snapshot containing that staged final user turn, `save` transactionally moves only the staged message to the real run, assigns its snapshot sequence, retains its attachments/replay backing, deletes the staging run and retires its ID. Historical messages replayed into the snapshot keep their existing owners and are not duplicated. Adoption preserves the staging start-time lower bound; subsequent snapshots cannot move it backward, and terminal time is clamped to that start when necessary. -`ConversationModel` mirrors adoption on `runStarted`, captured failure, preparation interruption, and queued-runtime cancellation; `runFinished` retains the adjusted chronology. Failed, interrupted, and cancelled replacements therefore use their actual run provenance rather than an abandoned staging summary. Publication order still depends on the path: preparation cancellation awaits the interrupted save and rechecks identity before adopting, while queued-runtime cancellation and `recordFailedRun` for a missing model/credential project live before their asynchronous save. Neither the end of preparation nor visible terminal state alone proves that persistence finished. For queued cancellation, `stopAndWait()` joins that save attempt. The persist-before-live contract above is specifically the history mutation boundary. +`ConversationModel` mirrors adoption on `runStarted`, captured failure, preparation interruption, and queued-runtime cancellation; `runFinished` retains the adjusted chronology. Preparation interruption and captured configuration failure await a successful save and recheck identity before adoption. A failed save leaves the edited staging provenance intact. Queued-runtime cancellation retains its separate immediate live projection before asynchronous save. `stopAndWait()` joins the relevant attempt in either cancellation path; a visible runtime terminal event alone remains insufficient evidence of save completion or success. The repository instance retires the edited target run, removed later runs and adopted staging run so late snapshots or durable tool events cannot resurrect the discarded suffix. Truncation and snapshot save roll back SwiftData changes and restore the previous in-memory retired-ID set on error. These rules use the existing schema and Termind's Application/Persistence adapters, without changing AgentKit or its package pin. See [Persistent Data](persistence-and-secrets.md) for the storage contract. @@ -268,7 +272,7 @@ stateDiagram-v2 Ready --> Preparing: accepted capture Preparing --> Running: current capture handed to runtime Preparing --> Interrupted: cancellation saved - Preparing --> Failed: captured configuration unavailable + Preparing --> Failed: captured failure saved Running --> WaitingApproval: Ask action pending WaitingApproval --> Running: allow WaitingApproval --> Running: denial result recorded @@ -308,15 +312,15 @@ The focused tests establish behavior that is important when changing this area: - `TFMTests/AgentRuntimeTests.swift` covers schema-before-approval/execution, approval mode routing, Guardian timeout/failure fail-closed behavior, single-use secret handles, steering delivery, tool cancellation, registry shadowing, and durable-event filtering. - `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, graph deletion, same-run edit prefix/tool preservation, canonical equal-time ordering, empty retained runs, staging adoption, late-write rejection, fork persistence and compaction restoration. `ChatComposerStateTests.swift` checks the compacted live prefix, tool cards, title, plans and adopted run summaries. -- `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption and queued cancellation of an edited replacement. The cancellation fixture reloads through a fresh `ModelContext` and repository, checking one adopted run, captured text/images/attachments/context, permission mode, and staging/terminal chronology. -- `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, failed/interrupted replacement provenance, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. Repository snapshot waiters distinguish async save completion from a live failed state. +- `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption, queued cancellation, and controller-owned configuration failure of an edited replacement. The cancellation and captured-failure fixtures reload through a fresh `ModelContext` and repository, checking one adopted run, captured text/images/attachments/context, permission mode, and staging/terminal chronology. +- `FrameworkTests/AssistantExecutionControllerTests.swift` covers capture across a memory barrier, missing credentials, first-send guards and payload folding, plan implementation, edit persistence/cancellation barriers, failed/interrupted replacement provenance, superseded captures, navigation, compaction refusal, memory revocation, and neutral legacy context. Failure-save barriers verify preparation stays owned, Stop/controller joins, exact save-before-archive/delete ordering, edit/fork refusal, retention after caller release, and rejection without staging adoption. Synchronous MainActor join probes distinguish entered joins from completed joins while the save is blocked. - `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first history mutations and activity guards, plus queued Stop/Stop-and-Wait with zero provider/tool calls, captured cancelled snapshots, Stop followed by a blocked-save join, persistence after presentation release, direct queued-owner displacement, pending approval cancellation, and an old persistence drain finishing while a replacement remains running. Injected review events verify that the stopped run's review state and card do not contaminate that replacement. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. - `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. - `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. Mobile's SwiftData restore/edit fixture also verifies same-run prefix preservation and replacement provenance. - `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. -The queued-cancellation SwiftData regression uses an in-memory container with a fresh read context, not a process restart or an on-disk crash test. Barrier fixtures establish save-attempt ordering; they do not exercise storage failure. The review-state fixture injects presentation events rather than contacting a Guardian provider. Preserve these distinctions when choosing additional validation. +The queued-cancellation and captured-failure SwiftData regressions use an in-memory container with a fresh read context, not a process restart or an on-disk crash test. Controller barriers establish save-attempt ordering and inject a repository save rejection; they do not simulate a failing SwiftData disk transaction. The queued-runtime barrier and review-state fixtures remain ordering/presentation tests, with injected events rather than a live Guardian provider. Preserve these distinctions when choosing additional validation. ## Change guidance diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index 2a5adf79..3c368757 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -62,6 +62,8 @@ sources: resource: repo://Frameworks/TermindPersistence/TermindSchemaV33IdentityModels.swift - id: openwiki-source-017f5e2abe9b13df755a5322 resource: repo://FrameworkTests/AgentReplayPersistenceTests.swift + - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 + resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift - id: openwiki-source-bf7a64e7c819eb9279e2f9e4 resource: repo://FrameworkTests/CloudSyncLifecycleTests.swift - id: openwiki-source-40cfadc2320da9a292c64d26 @@ -82,10 +84,10 @@ sources: resource: repo://TFMTests/AgentPersistenceTests.swift - id: openwiki-source-1d1efb20e239b1f2142f6cb7 resource: repo://TFMTests/SettingsSyncTests.swift -generated: { by: "codex", at: "2026-09-23T03:40:50.823Z" } +generated: { by: "codex", at: "2026-09-23T06:59:53.554Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T03:40:50.823Z + at: 2026-09-23T06:59:53.554Z --- # Persistent Data, Migrations, and Secret Boundaries @@ -136,7 +138,9 @@ The first real snapshot whose final ordinary user turn is this staged replacemen The repository's in-memory retired-ID set includes the edited target run even when its prefix survives, all removed later runs, and adopted staging runs. `save` and durable-event `record` ignore retired IDs, preventing late work from rebuilding the discarded suffix. This is a repository-instance guard, not a new persisted tombstone or schema migration. Both truncation and snapshot save restore the previous retired-ID set and roll back their `ModelContext` changes if the transaction fails. -`AssistantConversationOperations` applies the live history edit only after the repository commits; invalid replacements and rejected writes leave history unchanged. Live running, failed and interrupted projections adopt staging provenance too, but publication ordering is not universal: a captured missing-model/credential failure still updates live state before its separate asynchronous save. See [Assistant Runtime](agent-runtime-and-assistant.md) for that boundary and the distinct immediate-stop runtime-handoff limitation. Shared replay tests restore failed/interrupted adoption through a fresh context; desktop and mobile fixtures compare same-run prefix history and provenance after edits. These fixtures do not inject a real disk-full or read-only SwiftData failure. +`AssistantConversationOperations` applies the live history edit only after the repository commits; invalid replacements and rejected writes leave history unchanged. A captured missing-model/credential failure remains owned by preparation until its terminal save returns. Success and a current-identity check precede failed projection and staging adoption; rejection reports the persistence error and retires preparation with staging intact. Synchronous Stop preserves this one save task without cancelling it or adding interruption; `stopAndWait()` or awaiting the task returned by controller cancellation joins the attempt. Archive/delete await it, while edit supersession and direct edit/fork operations are refused until it finishes. A completed join therefore proves the attempt returned, not that storage succeeded. + +Queued-runtime cancellation retains its separate immediate live projection before saving; see [Assistant Runtime](agent-runtime-and-assistant.md) for its save-attempt join and runtime-handoff contract. Shared replay tests restore failed/interrupted adoption and a controller-owned failed edit through fresh contexts; desktop and mobile fixtures compare same-run prefix history and provenance after edits. The controller rejection fixture uses an injected repository error, and fresh-context restore uses in-memory SwiftData. Neither is a real disk-full/read-only SwiftData failure or process-restart test. ## Schema evolution: preserve the past exactly diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index d7031fa4..8ea107de 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -22,10 +22,12 @@ sources: resource: repo://TFM/Features/Workspaces/WindowModel.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift -generated: { by: "omp", at: "2026-09-22T03:23:14.035Z" } + - id: openwiki-source-0dd012e3740bcc348fc36ccb + resource: repo://TFMTests/StoreKitTrialTests.swift +generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T06:54:45.592Z --- # Termind Engineering Quickstart @@ -99,6 +101,8 @@ Scripts/check-zh-localization.sh Use an installed iPhone or iPad simulator destination for `TFI` tests. Keep visible-window macOS tests opt-in with `TEST_RUNNER_TERMIND_WINDOW_TESTS=1` and a narrow `-only-testing` selection; routine checks should stay headless. UI string changes require completed `zh-Hans` and `zh-Hant` translations in `Frameworks/Resources/Localizable.xcstrings`; landing-page work is separately validated with `cd LandingPage && npm ci && npm run build`. +StoreKit tests also remain skipped by default because they require manual authorization. When explicitly running them, set `TERMIND_STOREKIT_TESTS=1` and prefer `-only-testing:TFMTests/StoreKitTrialTests`. Default-suite success does not imply that opt-in suite ran. + ## Change checklist - Identify the domain and owner in the routing map; read the detailed page before implementing. diff --git a/openwiki/testing/behavioral-boundaries-and-fixtures.md b/openwiki/testing/behavioral-boundaries-and-fixtures.md index ad6049ba..a558389b 100644 --- a/openwiki/testing/behavioral-boundaries-and-fixtures.md +++ b/openwiki/testing/behavioral-boundaries-and-fixtures.md @@ -40,6 +40,8 @@ sources: resource: repo://TFITests/MobileConnectionPickerTests.swift - id: openwiki-source-9b78a7e39bfa08bea5192722 resource: repo://TFITests/MobileWorkspaceStoreTests.swift + - id: openwiki-source-7e364c6182a8339831ecab24 + resource: repo://TFM/Features/Workspaces/WorkspaceNavigatorView.swift - id: openwiki-source-cfbc34def649c94f760c5695 resource: repo://TFMTests/AgentHostAuthenticationTests.swift - id: openwiki-source-715de8d16be5a3d06ae915ad @@ -52,14 +54,18 @@ sources: resource: repo://TFMTests/ConnectionHistoryTests.swift - id: openwiki-source-53e2b2fd2e661123f63e69e1 resource: repo://TFMTests/SFTPTransferContextTests.swift + - id: openwiki-source-0dd012e3740bcc348fc36ccb + resource: repo://TFMTests/StoreKitTrialTests.swift - id: openwiki-source-960109cd5bc0de7a879c1e16 resource: repo://TFMTests/VaultSyncTests.swift - id: openwiki-source-a08489338be00125d646c77e resource: repo://TFMTests/VisibleWindowTestTrait.swift -generated: { by: "codex", at: "2026-09-23T03:34:06.838Z" } + - id: openwiki-source-05f0aeba12df8388b97b3c76 + resource: repo://TFMTests/WorkspaceNavigatorRenameTests.swift +generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T06:54:45.592Z --- # Testing Strategy and Behavioral Boundaries @@ -105,7 +111,11 @@ A message edit inside one run needs a fixture such as `opening → assistant/too Application error and timing fixtures complement those real SwiftData checks. `AssistantConversationOperationsTests` proves an invalid replacement never reaches persistence and a rejecting truncation leaves both visible and model history unchanged. Controller tests hold preparation at a memory barrier, cancel after a successful edit, and assert interrupted real-run provenance replaces staging. These seams do not inject disk-full or read-only failures into the real SwiftData transaction; its rollback path must not be described as fault-injection tested. -Use the boundary that the assertion needs as its wait condition. Captured missing-model/credential failure projects `.failed` before launching a separate asynchronous repository save, so `!isPreparingAgentRun` is not a save barrier. The controller fixture's `waitUntilSnapshot(state:)` checks existing snapshots or suspends an actor-owned continuation; `save` records the snapshot before resuming matching waiters. The missing-credential and navigation tests retain both live-state and repository-state assertions and wait for the latter explicitly. This does not change production failure ordering or prove durability across the separate queued-runtime immediate-stop handoff. +Use the boundary that the assertion needs as its wait condition. Captured missing-model/credential failure stays in preparation until its save attempt returns; `.failed` and staging adoption publish only after success and an identity check. A retired preparation can also mean a rejected save, so retirement alone does not prove durability. The recording repository distinguishes attempted snapshots from completed saves; actor waiters handle both already-observed and future events. + +`AssistantExecutionControllerTests` blocks the first failure save and verifies preparation remains active without failed projection. Stop followed by `stopAndWait` and controller cancellation join the single failed snapshot, with no interrupted write. Synchronous MainActor start/completion probes establish that those joins are pending before releasing the save. Archive/delete tests assert the exact save-attempt → save-completed → metadata/delete sequence. Further fixtures cover the memory-preparation transition, controller/conversation lifetime after callers release them, rejection of superseding and direct edits/forks over a retained assistant prefix, and injected save rejection leaving staging intact while reporting the error. + +`AgentReplayPersistenceTests.capturedConfigurationFailureAdoptsEditedStagingOnlyAfterDurableSave` exercises the production controller, operations and SwiftData repository, then restores through a fresh context. It checks one failed run with the edited message's text, images, attachments, context and permission, no remaining staging run, and monotonic staging/terminal chronology. This in-memory fixture and the injected rejection do not test a disk failure or process restart. Queued-runtime cancellation retains its separate live-before-save behavior and save-attempt join described in [Assistant Runtime](../concepts/agent-runtime-and-assistant.md). ### CloudKit compatibility: test prohibited uploads, not just decoding @@ -170,6 +180,10 @@ TEST_RUNNER_TERMIND_WINDOW_TESTS=1 xcodebuild -project Termind.xcodeproj -scheme In Xcode, set `TERMIND_WINDOW_TESTS=1` in the Test action environment. Existing screenshot suites can retain their dedicated gates. A visual change still needs behavior-appropriate coverage: an offscreen test for state/policy, a visible-window test for focus/presentation when applicable, and screenshot/marketing capture only where that workflow owns the deliverable. +`WorkspaceNavigatorRenameTests` is a serialized visible-window suite. It exercises both the context-menu and double-click rename paths and checks that the editor starts with the complete workspace title. The production view seeds the title before publishing the renamed workspace ID, so mounting the editor sees its initial value. + +StoreKit tests use a separate opt-in because they require manual authorization. `StoreKitTrialTests` is serialized and skipped unless `TERMIND_STOREKIT_TESTS=1`; when explicitly authorized, prefer the narrow `-only-testing:TFMTests/StoreKitTrialTests` selection. Ordinary default-suite success therefore does not imply this StoreKit suite ran. + ## Change-to-regression matrix | Change type | Primary regression seam | Required behavior to protect | @@ -177,6 +191,7 @@ In Xcode, set `TERMIND_WINDOW_TESTS=1` in the Test action environment. Existing | Framework dependency or data-owner change | `ArchitectureBoundaryTests` plus affected framework suite | No reverse import, concrete implementation leak, duplicate AgentKit type, or SwiftData model outside Persistence. | | SwiftData schema, migration, or durable agent record | Persistence compatibility/replay fixtures | Old stores open, records retain intended values, interrupted timing remains recoverable, and deletes clean backing rows. | | Assistant message-boundary edits or replacement provenance | Agent persistence/replay, conversation operations/controller, composer and mobile Assistant fixtures | Same-run prefix and graph survive, suffix backing is removed, staging adopts once, chronology remains stable, live/restore agree, and late retired writes cannot resurrect history. | +| Captured Assistant configuration-failure persistence | Controller save barriers and fresh-context replay restore | Failure remains owned until save return; joins add no interrupted write; success precedes adoption, rejected saves retain staging, and edits/forks cannot overtake the attempt. | | CloudKit format, upload scheduling, conflict or recovery behavior | Shared lifecycle, coordinator-safety and on-disk safety fixtures | Unsupported IDs never upload, compatible peers progress, restrictions survive retry/reopen, and failed reconciliation cannot unblock data. | | SSH credentials, prompts, host trust, or agent authentication | Authentication and interaction-policy fixtures | Ephemeral secrets stay ephemeral, prompt scope/order/coalescing is correct, forbidden work stays noninteractive, and secrets are invalidated after use. | | SSH/SFTP transfer scheduling or pane/session teardown | `SFTPTransferContextTests` with a controlled channel harness | Concurrency bounds hold, background transfer ownership survives UI teardown, cancellation cleans up, and fallback does not invite interaction. | From 76949f8c816682ab9d229675155b3c8ccb20067a Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 15:35:50 +0800 Subject: [PATCH 20/30] Fix mobile background expiration ownership --- TFI/App/MobileAppModel.swift | 46 +++--- .../Agent/MobileAssistantModel.swift | 2 + TFI/Platform/MobileBackgroundController.swift | 83 +++++++--- TFITests/MobileAppTests.swift | 46 ++++++ TFITests/MobileAssistantTests.swift | 153 +++++++++++++++++- .../MobileBackgroundControllerTests.swift | 95 +++++++++++ 6 files changed, 384 insertions(+), 41 deletions(-) create mode 100644 TFITests/MobileBackgroundControllerTests.swift diff --git a/TFI/App/MobileAppModel.swift b/TFI/App/MobileAppModel.swift index d854462e..896d8ad6 100644 --- a/TFI/App/MobileAppModel.swift +++ b/TFI/App/MobileAppModel.swift @@ -558,26 +558,11 @@ final class MobileAppModel { } func sceneChanged(_ id: UUID, phase: ScenePhase) { - let wasBackground = !scenePhases.isEmpty && scenePhases.values.allSatisfy { $0 == .background } + let wasBackground = allScenesAreBackgrounded scenePhases[id] = phase - let isBackground = scenePhases.values.allSatisfy { $0 == .background } + let isBackground = allScenesAreBackgrounded if isBackground && !wasBackground { - sftpEdits.setMonitoring(false) - memory.suspend() - vaultSync?.pause() - metricsSync?.pause() - settingsSync?.pause() - metrics.pause() - outbox.stop() - assistant.suspendRecording() - background.begin { [weak self] in - guard let self else { return } - for workspace in workspaces { workspace.suspendForBackgroundExpiration() } - forwardings.stopAll() - transfers.pauseAll() - assistant.expireBackground() - } - liveActivities.update(workspaceActivitySessions) + enterBackground() } else if phase == .active { devices.updateCurrentDeviceName(Device.current.safeDescription) sftpEdits.setMonitoring(true) @@ -648,10 +633,35 @@ final class MobileAppModel { } func removeScene(_ id: UUID) { + let wasBackground = allScenesAreBackgrounded scenePhases[id] = nil + if allScenesAreBackgrounded && !wasBackground { enterBackground() } reconcileSync() } + private var allScenesAreBackgrounded: Bool { + !scenePhases.isEmpty && scenePhases.values.allSatisfy { $0 == .background } + } + + private func enterBackground() { + sftpEdits.setMonitoring(false) + memory.suspend() + vaultSync?.pause() + metricsSync?.pause() + settingsSync?.pause() + metrics.pause() + outbox.stop() + assistant.suspendRecording() + background.begin { [weak self] in + guard let self else { return } + for workspace in workspaces { workspace.suspendForBackgroundExpiration() } + forwardings.stopAll() + transfers.pauseAll() + assistant.expireBackground() + } + liveActivities.update(workspaceActivitySessions) + } + func focusWorkspaceActivity(_ id: UUID) { liveActivities.focus(id, in: workspaceActivitySessions) } diff --git a/TFI/Application/Agent/MobileAssistantModel.swift b/TFI/Application/Agent/MobileAssistantModel.swift index d44764d4..e4255e04 100644 --- a/TFI/Application/Agent/MobileAssistantModel.swift +++ b/TFI/Application/Agent/MobileAssistantModel.swift @@ -144,6 +144,8 @@ final class MobileAssistantModel { execution.cancelPendingRuns() speech.cancelAll() let conversations = store.conversations + // Request every cancellation before awaiting a drain that may be held by persistence. + for conversation in conversations { conversation.stop() } suspensionTask = Task { for conversation in conversations { await conversation.stopAndWait() } await background.closeAll() diff --git a/TFI/Platform/MobileBackgroundController.swift b/TFI/Platform/MobileBackgroundController.swift index 5049685b..669bd6f2 100644 --- a/TFI/Platform/MobileBackgroundController.swift +++ b/TFI/Platform/MobileBackgroundController.swift @@ -6,37 +6,80 @@ protocol MobileBackgroundTaskManaging: AnyObject { func finish() } +@MainActor +protocol MobileBackgroundTaskPlatform: AnyObject { + func begin(expiration: @escaping @MainActor () -> Void) -> UIBackgroundTaskIdentifier + func end(_ task: UIBackgroundTaskIdentifier) +} + +@MainActor +private final class UIApplicationBackgroundTaskPlatform: MobileBackgroundTaskPlatform { + func begin(expiration: @escaping @MainActor () -> Void) -> UIBackgroundTaskIdentifier { + UIApplication.shared.beginBackgroundTask( + withName: "Finish remote session work", expirationHandler: expiration) + } + + func end(_ task: UIBackgroundTaskIdentifier) { + UIApplication.shared.endBackgroundTask(task) + } +} + @MainActor final class MobileBackgroundController: MobileBackgroundTaskManaging { - private var task: UIBackgroundTaskIdentifier = .invalid - private var expirationID: UUID? + private final class Lease { + var task: UIBackgroundTaskIdentifier = .invalid + var ended = false + } + + private let platform: any MobileBackgroundTaskPlatform + private var lease: Lease? + + init() { + platform = UIApplicationBackgroundTaskPlatform() + } + + init(platform: any MobileBackgroundTaskPlatform) { + self.platform = platform + } func begin(expiration: @escaping @MainActor () -> Void) { finish() - let expirationID = UUID() - self.expirationID = expirationID - task = UIApplication.shared.beginBackgroundTask(withName: "Finish remote session work") { [weak self] in - Task { @MainActor in - guard self?.expirationID == expirationID else { return } - self?.expirationID = nil - expiration() - self?.endTask() - } + let lease = Lease() + self.lease = lease + // UIKit invokes expiration synchronously on the MainActor. Request cancellation and retire the lease + // before returning; the async drains started by `expiration` remain best effort. + lease.task = platform.begin { [weak self, weak lease] in + guard let self, let lease else { return } + expire(lease, expiration: expiration) } - if task == .invalid, self.expirationID == expirationID { - self.expirationID = nil - expiration() + + guard self.lease === lease else { + retire(lease) + return + } + if lease.task == .invalid { + expire(lease, expiration: expiration) } } func finish() { - expirationID = nil - endTask() + guard let lease else { return } + self.lease = nil + retire(lease) } - private func endTask() { - guard task != .invalid else { return } - UIApplication.shared.endBackgroundTask(task) - task = .invalid + private func expire(_ lease: Lease, expiration: @MainActor () -> Void) { + guard self.lease === lease else { return } + self.lease = nil + expiration() + retire(lease) + } + + private func retire(_ lease: Lease) { + guard !lease.ended else { return } + // Expiration may run during `begin` before UIKit returns the identifier. An invalid task is still pending. + guard lease.task != .invalid else { return } + lease.ended = true + platform.end(lease.task) } } diff --git a/TFITests/MobileAppTests.swift b/TFITests/MobileAppTests.swift index 29d9a4e1..bfdba839 100644 --- a/TFITests/MobileAppTests.swift +++ b/TFITests/MobileAppTests.swift @@ -411,6 +411,50 @@ struct MobileWorkspaceConnectionRecoveryTests { #expect(connections.connectCount == 1) } + @Test func removingTheLastActiveSceneStartsOneBackgroundLeaseForRemainingScenes() async throws { + let background = MobileTestBackgroundTaskManager() + let connections = MobileSFTPConnectionBox(clients: [MobileTestSFTPClient()]) + let app = try MobileAppModel( + inMemory: true, background: background, workspaceConnectionServices: connections.services) + let host = try configureConnectionFixture(in: app) + _ = try #require(app.open(hostID: host.id, kind: .sftp)) + defer { app.closeAll() } + #expect(await eventually { app.workspaces.first?.connected == true }) + let backgroundScene = UUID() + let activeScene = UUID() + app.sceneChanged(backgroundScene, phase: .active) + app.sceneChanged(activeScene, phase: .active) + app.sceneChanged(backgroundScene, phase: .background) + + app.removeScene(activeScene) + + #expect(background.beginCount == 1) + #expect(app.workspaces.first?.connected == true) + app.removeScene(activeScene) + #expect(background.beginCount == 1) + background.expire() + #expect(app.workspaces.first?.connected == false) + + app.removeScene(backgroundScene) + #expect(background.beginCount == 1) + } + + @Test func removingABackgroundSceneWhileAnotherSceneIsVisibleDoesNotStartALease() throws { + let background = MobileTestBackgroundTaskManager() + let app = try MobileAppModel(inMemory: true, background: background) + let backgroundScene = UUID() + let activeScene = UUID() + app.sceneChanged(backgroundScene, phase: .active) + app.sceneChanged(activeScene, phase: .active) + app.sceneChanged(backgroundScene, phase: .background) + + app.removeScene(backgroundScene) + + #expect(background.beginCount == 0) + app.removeScene(activeScene) + #expect(background.beginCount == 0) + } + @Test func failedForegroundResumeKeepsReconnectionJourneyVisible() async throws { let (host, library) = try connectionFixture() let connections = MobileSFTPConnectionBox(clients: [MobileTestSFTPClient()]) @@ -1266,8 +1310,10 @@ private final class MobileSFTPConnectionBox { @MainActor private final class MobileTestBackgroundTaskManager: MobileBackgroundTaskManaging { private var expiration: (@MainActor () -> Void)? + private(set) var beginCount = 0 func begin(expiration: @escaping @MainActor () -> Void) { + beginCount += 1 self.expiration = expiration } diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 0b5d0ab7..066bf9d5 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -225,7 +225,7 @@ struct MobileAssistantTests { #expect(assistant.conversation.composer.draft.isEmpty) } - @Test func firstNewConversationSendPromotesDraftAndStartsRun() throws { + @Test func firstNewConversationSendPromotesDraftAndStartsRun() async throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() let draft = assistant.conversation @@ -240,10 +240,11 @@ struct MobileAssistantTests { #expect(draft.messages.map(\.role) == [.user]) #expect(draft.messages.first?.text == "Why is the service unavailable?") #expect(draft.transcript.map(\.role) == [.user]) + #expect(await eventually { draft.runState == .failed }) try expectMissingModelFailure(in: draft) } - @Test func attachmentOnlyFirstSendPromotesTheConversation() throws { + @Test func attachmentOnlyFirstSendPromotesTheConversation() async throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() let draft = assistant.conversation @@ -257,10 +258,11 @@ struct MobileAssistantTests { #expect(draft.messages.first?.text.isEmpty == true) #expect(draft.messages.first?.contextChips.map(\.label) == ["service.log"]) #expect(draft.transcript.first?.text.contains("connection refused") == true) + #expect(await eventually { draft.runState == .failed }) try expectMissingModelFailure(in: draft) } - @Test func planImplementationStartsAnActingTurnWithPendingComposerInputs() throws { + @Test func planImplementationStartsAnActingTurnWithPendingComposerInputs() async throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() let conversation = ConversationModel() @@ -284,6 +286,7 @@ struct MobileAssistantTests { #expect(turn.text.contains("")) #expect(conversation.composer.attachments.isEmpty) #expect(conversation.composer.armedSkillName == nil) + #expect(await eventually { conversation.runState == .failed }) try expectMissingModelFailure(in: conversation) } @@ -487,6 +490,67 @@ struct MobileAssistantTests { #expect(!app.assistant.isRunning) } + @Test func backgroundExpirationStopsEveryConversationBeforeJoiningTheFirstDrain() async throws { + let app = try MobileAppModel(inMemory: true) + let assistant = app.assistant.makeSceneModel() + let first = ConversationModel() + let second = ConversationModel() + assistant.library.insertAndSelect(second) + assistant.library.insertAndSelect(first) + assistant.select(second) + #expect(app.assistant.store.conversations.map(\.id) == [first.id, second.id]) + let secondCalls = MobileAssistantModelCalls() + let firstRepository = MobileTerminalSaveGateRepository() + let secondRepository = InMemoryAgentRunRepository() + first.start( + runtime: AgentRuntime( + model: MobileImmediateAssistantModel(calls: MobileAssistantModelCalls()), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: firstRepository + ), + request: AgentRunRequest( + conversationID: first.id, prompt: "first", permissionMode: .askForApproval), + repository: firstRepository + ) + await firstRepository.waitForTerminalSave() + second.start( + runtime: AgentRuntime( + model: MobileImmediateAssistantModel(calls: secondCalls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: secondRepository + ), + request: AgentRunRequest( + conversationID: second.id, prompt: "second", permissionMode: .askForApproval), + repository: secondRepository + ) + + app.assistant.expireBackground() + app.assistant.expireBackground() + + #expect(app.assistant.isSuspending) + #expect(!first.isStreaming) + #expect(first.runState.isTerminal) + #expect(second.runState == .cancelled) + second.composer.draft = "preserve" + assistant.send() + #expect(second.composer.draft == "preserve") + #expect(secondCalls.count == 0) + let join = MobileAssistantJoinProbe() + let waiter = Task { @MainActor in + join.markStarted() + await app.assistant.waitForSuspension() + join.markFinished() + } + await join.waitUntilStarted() + #expect(!join.finished) + #expect(app.assistant.isSuspending) + + await firstRepository.releaseTerminalSaves() + await waiter.value + #expect(!app.assistant.isSuspending) + #expect(secondCalls.count == 0) + } + private func measuredInteractionHeight( conversation: ConversationModel, hosts: HostLibraryModel, @@ -521,6 +585,89 @@ private struct MobileDelayedAgentModel: AgentModelStreaming { } } +nonisolated private final class MobileAssistantModelCalls: @unchecked Sendable { + private let lock = NSLock() + private var callCount = 0 + + func called() { + lock.withLock { callCount += 1 } + } + + var count: Int { lock.withLock { callCount } } +} + +@MainActor +private final class MobileAssistantJoinProbe { + private(set) var finished = false + private var started = false + private var startWaiters: [CheckedContinuation] = [] + + func markStarted() { + started = true + let waiters = startWaiters + startWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func waitUntilStarted() async { + guard !started else { return } + await withCheckedContinuation { startWaiters.append($0) } + } + + func markFinished() { finished = true } +} + +private struct MobileImmediateAssistantModel: AgentModelStreaming { + let calls: MobileAssistantModelCalls + + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + calls.called() + continuation.yield(.finished(.completed)) + continuation.finish() + } + } +} + +private actor MobileTerminalSaveGateRepository: AgentRunPersisting { + private var terminalSaveEntered = false + private var terminalSaveWaiters: [CheckedContinuation] = [] + private var terminalReleaseWaiters: [CheckedContinuation] = [] + + func save(_ snapshot: AgentRunSnapshot) async { + guard snapshot.state.isTerminal else { return } + terminalSaveEntered = true + let waiters = terminalSaveWaiters + terminalSaveWaiters.removeAll() + for waiter in waiters { waiter.resume() } + await withCheckedContinuation { terminalReleaseWaiters.append($0) } + } + + func waitForTerminalSave() async { + guard !terminalSaveEntered else { return } + await withCheckedContinuation { terminalSaveWaiters.append($0) } + } + + func releaseTerminalSaves() { + let waiters = terminalReleaseWaiters + terminalReleaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func record(_ event: AgentEvent, runID: UUID) {} + func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} + func loadConversations() -> [StoredAgentConversation] { [] } + func deleteConversation(_ id: UUID) {} + func setTitle(_ title: String, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} + func truncateConversation( + _ id: UUID, fromUserMessageID messageID: UUID, + replacingWith message: AgentTranscriptMessage + ) {} + func createConversation(_ conversation: StoredAgentConversation) {} +} + @MainActor private func expectMissingModelFailure(in conversation: ConversationModel) throws { // In-memory apps have no Chat model. That must produce a failure row, not an authored answer. diff --git a/TFITests/MobileBackgroundControllerTests.swift b/TFITests/MobileBackgroundControllerTests.swift new file mode 100644 index 00000000..fe4a8de7 --- /dev/null +++ b/TFITests/MobileBackgroundControllerTests.swift @@ -0,0 +1,95 @@ +import Testing +import UIKit + +@testable import TermindIOS + +@Suite @MainActor +struct MobileBackgroundControllerTests { + @Test func expirationRunsSynchronouslyAndEndsEachLeaseExactlyOnce() throws { + let first = UIBackgroundTaskIdentifier(rawValue: 1) + let second = UIBackgroundTaskIdentifier(rawValue: 2) + let platform = MobileBackgroundTaskPlatformSpy(tasks: [first, second]) + var events: [String] = [] + platform.didEnd = { _ in events.append("end") } + let controller = MobileBackgroundController(platform: platform) + + controller.begin { events.append("expire-first") } + let stale = try #require(platform.expirations.first) + controller.finish() + stale() + stale() + + controller.begin { events.append("expire-second") } + let current = try #require(platform.expirations.last) + current() + current() + controller.finish() + + #expect(events == ["end", "expire-second", "end"]) + #expect(platform.ended == [first, second]) + } + + @Test func synchronousExpirationDuringBeginRetiresReturnedTaskWithoutOverwritingReplacement() throws { + let expired = UIBackgroundTaskIdentifier(rawValue: 1) + let replacement = UIBackgroundTaskIdentifier(rawValue: 2) + let platform = MobileBackgroundTaskPlatformSpy(tasks: [expired, replacement]) + var expirationCount = 0 + var replacementExpirationCount = 0 + var controller: MobileBackgroundController! + platform.expireSynchronouslyOnNextBegin = true + controller = MobileBackgroundController(platform: platform) + + controller.begin { + expirationCount += 1 + controller.begin { replacementExpirationCount += 1 } + } + + #expect(expirationCount == 1) + #expect(replacementExpirationCount == 0) + #expect(platform.ended == [expired]) + let replacementExpiration = try #require(platform.expirations.last) + replacementExpiration() + #expect(replacementExpirationCount == 1) + #expect(platform.ended == [expired, replacement]) + } + + @Test func invalidBeginExpiresOnceWithoutEndingAnInvalidTask() { + var expirationCount = 0 + let platform = MobileBackgroundTaskPlatformSpy(tasks: [.invalid]) + let controller = MobileBackgroundController(platform: platform) + + controller.begin { expirationCount += 1 } + controller.finish() + + #expect(expirationCount == 1) + #expect(platform.ended.isEmpty) + } +} + +@MainActor +private final class MobileBackgroundTaskPlatformSpy: MobileBackgroundTaskPlatform { + var expirations: [@MainActor () -> Void] = [] + var ended: [UIBackgroundTaskIdentifier] = [] + var expireSynchronouslyOnNextBegin = false + var didEnd: ((UIBackgroundTaskIdentifier) -> Void)? + private var tasks: [UIBackgroundTaskIdentifier] + + init(tasks: [UIBackgroundTaskIdentifier]) { + self.tasks = tasks + } + + func begin(expiration: @escaping @MainActor () -> Void) -> UIBackgroundTaskIdentifier { + let task = tasks.removeFirst() + expirations.append(expiration) + if expireSynchronouslyOnNextBegin { + expireSynchronouslyOnNextBegin = false + expiration() + } + return task + } + + func end(_ task: UIBackgroundTaskIdentifier) { + ended.append(task) + didEnd?(task) + } +} From 250e1ae194f606d6690cfd2db5a743f0cd218959 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 15:43:28 +0800 Subject: [PATCH 21/30] Document mobile background expiration boundaries --- .../architecture/engineering-invariants.json | 72 ++++++++++++----- openwiki/.claims/architecture/mobile-ui.json | 20 +++-- openwiki/.claims/architecture/module-map.json | 8 +- .../architecture/native-app-directories.json | 10 +-- .../architecture/platform-runtime.json | 70 ++++++++++++---- .../architecture/system-boundaries.json | 8 +- .../concepts/agent-runtime-and-assistant.json | 48 +++++++++-- .../concepts/persistence-and-secrets.json | 8 +- .../concepts/ui-design-principles.json | 12 ++- .../integrations/mcp-server-and-bridge.json | 6 +- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 80 +++++++++---------- .../architecture/engineering-invariants.md | 30 ++++--- openwiki/architecture/mobile-ui.md | 2 +- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 24 +++++- openwiki/architecture/system-boundaries.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 14 +++- openwiki/concepts/persistence-and-secrets.md | 2 +- openwiki/concepts/ui-design-principles.md | 2 +- .../integrations/mcp-server-and-bridge.md | 2 +- 22 files changed, 288 insertions(+), 140 deletions(-) diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index 9a57af47..a3360c44 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:c8ad1f11ac788af965dd38b668950c45dda95ab336ac725cd1d9ab3e8f7ce8a8", + "pageVersion": "sha256:24879797730fe459acba1497911aff236d56a55e1ff25ed0b17487461df1506a", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -44,7 +44,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -150,27 +150,19 @@ }, { "id": "claim_396197f6506e448888fc408eefe3eb04", - "statement": "Mobile workspace restoration persists connection-free descriptors rather than live sessions, reuses one live workspace across scenes, and closes runtime transports on background expiration without making foreground restoration an implicit approval or replay boundary.", + "statement": "Mobile workspace restoration persists connection-free descriptors rather than live sessions, reuses one live workspace across scenes, and detaches transports with asynchronous closure on background expiration while retaining foreground resume intent without implicit approval or execution replay.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "resource": "repo://TFI/App/MobileAppModel.swift#L669-L696", + "version": "repo-lines-v1:sha256:367a617cc39e7415e2493f6a035e70947f5ad0ee2100caedf0db931be1e0f115:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4YjM0MjE2MjYzZjM3NTkzNDUyZGMyNDNlYTQyNTQ3MDJkZTEzYjI0OTliYTQ4ODYxODk2NTI4NGNmNDQzNmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhkZjI2Yjc1ODA3NjRhMzA3MzcwNDAxZTZiZmI4NWVmMDBlMDFiOWY5MDA0M2RjNWIxY2I2ZDhkZGU1Y2Y2NzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1NWE2YmQ5MDhjMTBjOGM0MzYyZDBkNDAyZDI0Y2JkMDY2MDgyYjNjMjQ0N2YwZTA0MjZhYjEzY2M4NDc2M2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQwMTYwNjlmZDYzNTRiM2JjOWZiMDlmNTY3MzEzMDA5YmRmN2IxYmExNGZkNWFmMzBjYzU5NGYyN2M5YjJiNDUifQ" }, { - "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift", - "version": "repo-file-v1:sha256:3aed203d6e01bca706733b899b9636aa4808717dea3a8f2d044fd01db08a33d5" + "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift#L1056-L1101", + "version": "repo-lines-v1:sha256:0a7ae642dd083c84f164b264fee0281ba51d48288dcddb66d8c2e9912a631140:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljN2Y4NWI1NDQ5YmI0MTM0YjhmMTAzN2M1ZDkwNjU4ZDNiMjU4YWIxMDZlNWZmNjEzNTBiZjg2MzlmYzY4ODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGMzODIwMjNmZDQ2NDhkYjQzZmZhNjFjNWJmYjQ3ZjliOTZjZTIyZjFmNGI2OGYxZTczZGY3ODkxN2VlMDkifQ" }, { - "resource": "repo://TFI/Infrastructure/MobileWorkspaceStore.swift", - "version": "repo-file-v1:sha256:ea46bc5d91b8f0cbbdf562a56e1383dc1d7732ba249b109dd676a54369acf9c6" - }, - { - "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" - }, - { - "resource": "repo://TFITests/MobileWorkspaceStoreTests.swift", - "version": "repo-file-v1:sha256:9953e49389c0c2b390fe6bd51e467de56c31ec1556a13633ae239ac842c9241d" + "resource": "repo://TFITests/MobileAppTests.swift#L364-L454", + "version": "repo-lines-v1:sha256:2b5ee79378c0c8cfc0f21fa30e0a0e50fc55623a81494d2017a5704cb5444c30:eyJzZWxlY3RlZExpbmVDb3VudCI6OTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiZDFmNThmMjIzZDQ1NjI2YzYyY2U4NDc3Nzg4MjI0OWNkMDdjMzM4MzQyYTE0MWRhYWRhMzkzMjUwNGY5NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMTU4OTBkZTAwODI3MDQ1MWMxMTA0MmE4ZTE1MWNiMmRhZTg3NTRlNjkxOWQ3ZTQzNzZkM2FmOGRiMGI1ODciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzY2QxZDE2MWMyZWJmNTNmZjg1NjNmM2Q5MDdiMmM5MDJiYzVmZWE1MThmMDcyY2UwNmU2MjI2NTQwMGIyMmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1NTM3NjE5OGEzMTdiYjQxMmRiMzk1OGI4MGQxY2RkMDQ1ZjA3NzA0NzllZWI4MjMwNzA0NWZiYzk4OWI3MDEifQ" } ] }, @@ -419,10 +411,54 @@ "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" } ] + }, + { + "id": "claim_fe5acbb88c2345a4b3d951759f2e36f7", + "statement": "Mobile global background begins only on a transition to a nonempty all-background scene set, including after scene removal; each platform lease is identity-scoped, invalid acquisition expires immediately, stale callbacks cannot affect replacements, and valid identifiers are ended exactly once even if expiration reenters acquisition.", + "evidence": [ + { + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L568", + "version": "repo-lines-v1:sha256:7becf695f5482b64587fb4e18ae54ebf2a59c6f3780a5061e49760375b2fd4fe:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTFiNjA5Yzc4MmIyYzY0NWEzYzZkMWY5YzIwMDdjN2MwZWUxYjkxMjViMmM4YjE5NTRhMWUxNWNjZTEzZWRiMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMmNhZjUwNmM2OTYxMDZlMDQwZGRiMGZiMzcyYWFhMzdhNDdkNzlhMDA5NDIxZDgwODY5MDY3YjZkNmQyYTYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTEwZWFjNTJhYTY2MzQwNzM2NDQyNTUxODZhNzk0YjEyN2ZjNDkxMDY1NGZhM2EyMGI5ODMyMzE5MjFlZTZlYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjYzZGU0NmI2NzgwOTE4OGI2ZDU3YjhmNTUwNGMxYWQ2YTEwOTljMWY2OGFmMGM3MGVkNzY4NmY4MzczNmVkZiJ9" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", + "version": "repo-lines-v1:sha256:87cbd4a7d016416f80a016da585683c18e185beb78163ce57d73511d38bd8d59:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjQ1ZWEzZDVjMjljYjRiOTAxNThmOTJlOGNiYzk4OGFjZjMyNGYzYzlhMTQ0NDExNmE3NjkyYmZjYmM2MDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlMGMwYzg3ZjJiZjhhNjVmNDIwYmY3ZjkzMWVhMmZmOWZiZmRjNmI4ZTlmNDg0YTdmNTgzNjhkMmRmYjE0NjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0Yjc0YmRmNzgxODA1OTJlM2YyOTM1YzcxMzhlZmU1MDA0NzZhNjFjOTllMGZiZTcxNDVjYmRlMDBkYTU5MmEifQ" + }, + { + "resource": "repo://TFI/Platform/MobileBackgroundController.swift#L45-L84", + "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://TFITests/MobileBackgroundControllerTests.swift#L6-L67", + "version": "repo-lines-v1:sha256:1e309292d25fb55839d18d0e9b27e986381b3ffa8eeb651fb1abe688b3e3e9a8:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjODI4MTNjZjZlYzQ4NTFmYzUxZTEzMGMzM2Y3ZWY4MGI3MzhhMDlmYWFiY2VkZjE1NDNhN2M4NWQ5ZTMwNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJjYmU2ZmRmMWQ0MTI5NWY2NTAyYmE4MWM1ZDJiN2ZlN2Q5ZmEwMWZlNTQ1ZDdlNmEyYWJjYzliZTMyNGI4YjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY5N2Y4NDFiZDJjNTk2YjJiMmYxMDU5MmNiYWE5Yzg5NWM3ZDkwMWYzOGJmZTY2N2M3MDMyZTAzOWM1YWY4MjIifQ" + } + ] + }, + { + "id": "claim_e7c82a1a4db64870b6b4ae40612980a3", + "statement": "Mobile Assistant expiration synchronously requests cancellation of all conversations before asynchronous retained-owner joins and command/connection cleanup; the composer-send guard lasts until cleanup returns, and the platform expiration handler does not wait for or guarantee completion of those drains under iOS suspension.", + "evidence": [ + { + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L577", + "version": "repo-lines-v1:sha256:a270cc13b36412458b74942420cae0e8abaa6638637ae686fba94b336f5c53ed:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L158", + "version": "repo-lines-v1:sha256:0873ac799d8046793f591b26c6ec43d87615e49a3cf7b52e5fe0d10760b719e8:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + }, + { + "resource": "repo://TFI/Platform/MobileBackgroundController.swift#L45-L84", + "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:9c7f365e523b2d28fcfa76ef2f04bc330d1eaad9369561d1ac24393cc8a1a9e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NjIxN2JhNDkzNWU3Y2YwNDllYWNmYjNiNmQ5YWE3M2ExYzkzNjFlNDdmNGJlMDljODVlZTJhNTQ0Zjk2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWUyNTFjZGNkY2ZlNTFlNGY1M2E5ZGZhMjQxMjM4ZGNmZGNiNmNiYTdhNDEwZDdmOWJkMjY3NTg0ZGFlZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmNGJhMmVjOTU2MDNlZThiOWFlZmZiYmQ3MzY1NzEyOGNlYmJmNWNmYmFlZDczZmY5ZmEzZTI0ZTI0MzhjMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZWE3MTMwOWQ1YjQzYjZiZTIyZTQyZmIyOWFiNjAwYTIxZDY0ODcyOWVjMTIzYjk0MTVjOTA2MzJlMDQ3M2YifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:54:45.592Z" + "at": "2026-09-23T07:42:01.096Z" } } diff --git a/openwiki/.claims/architecture/mobile-ui.json b/openwiki/.claims/architecture/mobile-ui.json index 4af9086a..944c8fad 100644 --- a/openwiki/.claims/architecture/mobile-ui.json +++ b/openwiki/.claims/architecture/mobile-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:5a57c9f6cd1214629ea2a75c3ba60386a70d5c2170cdb552f4dea473f0dde193", + "pageVersion": "sha256:3e4fffdec19e331d34ec12f5b4ead52c83e7d4d17e8f0937e8ba884b0d26ed34", "claims": [ { "id": "claim_fbb919f62a2d4ab88aafcc4c1186731b", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -159,12 +159,16 @@ "statement": "TFI aggregates scene phases before entering background handling; background expiry suspends workspaces, stops forwarding, pauses transfers and expires Assistant work, while an active scene refreshes the current device name, resumes retained workspaces and restarts appropriate services.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift#L512-L544", - "version": "repo-lines-v1:sha256:b41d3de43fde9c50d0b478946d6263804791efe4519784713c872a1679a205dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", + "version": "repo-lines-v1:sha256:87cbd4a7d016416f80a016da585683c18e185beb78163ce57d73511d38bd8d59:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjQ1ZWEzZDVjMjljYjRiOTAxNThmOTJlOGNiYzk4OGFjZjMyNGYzYzlhMTQ0NDExNmE3NjkyYmZjYmM2MDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlMGMwYzg3ZjJiZjhhNjVmNDIwYmY3ZjkzMWVhMmZmOWZiZmRjNmI4ZTlmNDg0YTdmNTgzNjhkMmRmYjE0NjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0Yjc0YmRmNzgxODA1OTJlM2YyOTM1YzcxMzhlZmU1MDA0NzZhNjFjOTllMGZiZTcxNDVjYmRlMDBkYTU5MmEifQ" }, { "resource": "repo://TFI/App/MobileRootView.swift#L49-L74", - "version": "repo-lines-v1:sha256:427a1b65cef9a08c166a810c9819f16094bdadda3efd6d064bf1010c9467c8c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwNmIyOTNhY2FiZjI2YWJjZmZmNDhiYzNlZmVkZTIzZmEyMDFiYjA3MDMzMDMxYzcwOWVkZDc2NmQ1YjQ2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkNGM4YzM4M2UxNWUxNjA3Njg3NDNkZjUwZTY1MWE0N2YzOWY5YTcxZWJjYzMwOGMwMTFlOWQyZTRiMjE4Y2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM2MTlhZGM1NjcxMTNjZmI2YWE4YTM2ZmI4N2U5ZWI3N2FhZGFlNjU4MDc3ZDRlODQ0NGNkNGM0NWM1YTFiNWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ1OGE3YWU2OTU4YjYxMGZmMmRhM2Q1ZDM0MjdjMWYyZDZlMDQ2NzRmYzdkZjEyNzgwYThhMWU0NzFkODVhNjAifQ" + "version": "repo-lines-v1:sha256:427a1b65cef9a08c166a810c9819f16094bdadda3efd6d064bf1010c9467c8c8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwNmIyOTNhY2FiZjI2YWJjZmZmNDhiYzNlZmVkZTIzZmEyMDFiYjA3MDMzMDMxYzcwOWVkZDc2NmQ1YjQ2NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkNGM4YzM4M2UxNWUxNjA3Njg3NDNkZjUwZTY1MWE0N2YzOWY5YTcxZWJjYzMwOGMwMTFlOWQyZTRiMjE4Y2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5MzEyMzk0OTNhNjBlYTFkOTllZWQyZjE3MTljZjEyNjZmMThlNTE5ZDQxZTZmODNiMjYwNjg3ZjUxZTA3OWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ1OGE3YWU2OTU4YjYxMGZmMmRhM2Q1ZDM0MjdjMWYyZDZlMDQ2NzRmYzdkZjEyNzgwYThhMWU0NzFkODVhNjAifQ" } ] }, @@ -248,7 +252,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFITests/MobileAdaptiveLayoutTests.swift", @@ -256,13 +260,13 @@ }, { "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" + "version": "repo-file-v1:sha256:b2c528520ad5272155355404f91af52e490c6f17780a845d9e8a596c523484ef" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T02:40:53.789Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 3bf4d0a8..69b44247 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:cf89f7807de84434ff5a04aeed85734ce7e7f87b5fbc652e7210a60122fdd1d5", + "pageVersion": "sha256:bca342add2ba08c7d23d8e2249dbd1fc90d80da4cd53969a03f04507a81c9e8e", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -32,7 +32,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -140,7 +140,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -151,6 +151,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index 6135a5a9..a111028b 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1011bb9602def6c6ca1bb7bf0cde718b8479b58ac8af9526e85bae3771596073", + "pageVersion": "sha256:46bd31b52ab67eba8321c1885f18fb3436a08178dbccd92f5b6405559b9c3212", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -182,7 +182,7 @@ "evidence": [ { "resource": "repo://TFI/Platform/MobileBackgroundController.swift", - "version": "repo-file-v1:sha256:0fbc54522867c50f4c6ddff1294d936dd65a21acf6dbdec239bb78866166e525" + "version": "repo-file-v1:sha256:75c3d7bda5bf9ce83518666d5801cf3e01f9bbc82ed5acdc557bf17c2c6d4efc" }, { "resource": "repo://TFI/Platform/MobileLiveActivityController.swift", @@ -215,8 +215,8 @@ "statement": "Native app composition adapts platform device naming into the shared device directory: TFM uses the trimmed kern.hostname with DeviceKit fallback and refreshes on application activation; TFI uses DeviceKit's Device.current.safeDescription and refreshes on active scene transitions.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift#L580-L589", - "version": "repo-lines-v1:sha256:71bcd9d234f4fe43fb8074f23095acbfa238266403359ce6f85621ad7d170c6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1Nzg1MjI2MjQyOTAxY2NkOTlmMTgzNjZjYTdhOGNhOGEwNTc1YTA0OGNkOGU3NTZlZmI2ZmRmMjc5MTFiNTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQzZGFiZTIzNDEzOTNjYzU1NWUzZjI0MzRlOTFlZTZjYjVjZTNhYTgwNWEwOWVhNjA2YTY1MDI3YzNlZWUzY2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzNDVkYzM2OGMxODNiMzEwOTZlMDYwYWFiODA1NzE2MTY3YTYyNDQ5MTgzMTM1NjJlMTgzNmQ1NDk0ZjIwMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4ZWY4Mjc0MmI5NzUwODBiMTdhNDZjZjVjMDYzOGM0MmY1ZGYxNjE3NTBjM2U0OTY1MGRhYTc1OTQzNjJmMWIifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFM/App/TermindApp.swift#L285-L294", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index c0045758..6eaf31d8 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:4dbecf9ffc4a898da594e820778cd3e693619b49c45ffc6745b7684ef00c4f46", + "pageVersion": "sha256:11fb1a6bc89cdf3a069ac0842a06a6b2f060dff04e708d76584daa857818c42c", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -116,7 +116,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" } ] }, @@ -144,7 +144,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -152,7 +152,7 @@ }, { "resource": "repo://TFITests/MobileAppTests.swift", - "version": "repo-file-v1:sha256:d2466771edf0d6d85ccbd8d5c6a8a7cd05521af153016478430a466085380ba9" + "version": "repo-file-v1:sha256:b2c528520ad5272155355404f91af52e490c6f17780a845d9e8a596c523484ef" } ] }, @@ -176,19 +176,19 @@ }, { "id": "claim_9b627f261e1648d8a6a30ed16515bf1f", - "statement": "Mobile background behavior is aggregated across scenes: only when all tracked scenes are backgrounded does the app suspend sync/monitoring and begin a finite background task; expiry suspends workspace, forwarding, transfer, and assistant runtime work, while an active scene refreshes the current device record and resumes existing workspaces and foreground services.", + "statement": "Mobile background behavior starts only on transition to a nonempty all-background scene set, checked on both phase changes and scene removal; expiry suspends workspace, forwarding, transfer and Assistant work, while an active scene refreshes the device record and resumes retained workspaces and foreground services.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift#L512-L544", - "version": "repo-lines-v1:sha256:b41d3de43fde9c50d0b478946d6263804791efe4519784713c872a1679a205dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { - "resource": "repo://TFI/App/MobileRootView.swift#L49-L51", - "version": "repo-lines-v1:sha256:6facd243b08481babb75f6282b56d53f1e0165402144532ee098000deb645e4c:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzA2YjI5M2FjYWJmMjZhYmNmZmY0OGJjM2VmZWRlMjNmYTIwMWJiMDcwMzMwMzFjNzA5ZWRkNzY2ZDViNDY1YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMmI5N2I4NWVjMDNiMGRhMGM3YWYyNzU2OTkzYTE5OTY2MDg4NGI3MTgyMWEyZDRkZDg3ZjgyMzRlODU1MWZiYSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzYxOWFkYzU2NzExM2NmYjZhYThhMzZmYjg3ZTllYjc3YWFkYWU2NTgwNzdkNGU4NDQ0Y2Q0YzQ1YzVhMWI1ZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDVlMmZmMDFmMDRiNTlmYjYzMjQwYWU1ZTQ2OWVhYzMxN2ZkZDhjNTljNmIxMTA0MjBiODQ3YmNlZjJlYjVlYyJ9" + "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", + "version": "repo-lines-v1:sha256:87cbd4a7d016416f80a016da585683c18e185beb78163ce57d73511d38bd8d59:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjQ1ZWEzZDVjMjljYjRiOTAxNThmOTJlOGNiYzk4OGFjZjMyNGYzYzlhMTQ0NDExNmE3NjkyYmZjYmM2MDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlMGMwYzg3ZjJiZjhhNjVmNDIwYmY3ZjkzMWVhMmZmOWZiZmRjNmI4ZTlmNDg0YTdmNTgzNjhkMmRmYjE0NjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0Yjc0YmRmNzgxODA1OTJlM2YyOTM1YzcxMzhlZmU1MDA0NzZhNjFjOTllMGZiZTcxNDVjYmRlMDBkYTU5MmEifQ" }, { - "resource": "repo://TFITests/MobileAppTests.swift#L318-L365", - "version": "repo-lines-v1:sha256:a931ada6538eda81068f2cb53aa5b6af5e5cffe259a41873160448d469d86c7a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiZDFmNThmMjIzZDQ1NjI2YzYyY2U4NDc3Nzg4MjI0OWNkMDdjMzM4MzQyYTE0MWRhYWRhMzkzMjUwNGY5NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5MzgwYmQ2NzA4ZjU3ZTE2Mjk5NmM5OWQ4M2VkZTA1Njg5OTQ2YjRlOTZhYTgxYTAxZmU3YTU2MWI0OTM5MWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzY2QxZDE2MWMyZWJmNTNmZjg1NjNmM2Q5MDdiMmM5MDJiYzVmZWE1MThmMDcyY2UwNmU2MjI2NTQwMGIyMmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI4MjZjODhhNmZiNTA3OGEyZjViYTNmZDdjZDM4ZTZkOWQ4ODQ0ZTM2NTliMDBlNWFhODRiNTc5ZTliMWRhNTcifQ" + "resource": "repo://TFITests/MobileAppTests.swift#L364-L454", + "version": "repo-lines-v1:sha256:2b5ee79378c0c8cfc0f21fa30e0a0e50fc55623a81494d2017a5704cb5444c30:eyJzZWxlY3RlZExpbmVDb3VudCI6OTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiZDFmNThmMjIzZDQ1NjI2YzYyY2U4NDc3Nzg4MjI0OWNkMDdjMzM4MzQyYTE0MWRhYWRhMzkzMjUwNGY5NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxMTU4OTBkZTAwODI3MDQ1MWMxMTA0MmE4ZTE1MWNiMmRhZTg3NTRlNjkxOWQ3ZTQzNzZkM2FmOGRiMGI1ODciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgzY2QxZDE2MWMyZWJmNTNmZjg1NjNmM2Q5MDdiMmM5MDJiYzVmZWE1MThmMDcyY2UwNmU2MjI2NTQwMGIyMmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1NTM3NjE5OGEzMTdiYjQxMmRiMzk1OGI4MGQxY2RkMDQ1ZjA3NzA0NzllZWI4MjMwNzA0NWZiYzk4OWI3MDEifQ" }, { "resource": "repo://TFITests/MobileForwardingLifecycleTests.swift#L62-L91", @@ -216,7 +216,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/Features/Workspaces/Pane.swift", @@ -237,8 +237,8 @@ "version": "repo-lines-v1:sha256:e4c05ddc982049c744719ce0e23ec5e9583f75ad8cdf645293c1d455c1430051:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2M3ZjhiYzVmMjYwMjJmY2QyMGMzM2QzN2EyMTA3MTc5ZWNiYTMzNTAzMzgyNzAxZDg3YTM0MmVjMDQzZjNjNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYmU1NDFmMThiZTNlOTczZTBjY2Y3YzQ1ZjljOWRhMmE3ODU2ODYxMDk2NDA3M2EyNGFhOTg0Zjk2MDVmYjJiZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNmE5OTJjNGI5YTNmYzZiMzgwNDU3ZDAyZGU5N2I3ZjJjYTg2NmE4MmEwZjdlNzg2OTc5NzhmZmRkOTExOTgxMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTYyMmI4OWNhYjU0MGMxM2UwNjFiNTdjNjQ5ZDY3ZWMyMTZhNjY0YmJmZGZjNDg3YTAyM2JkZGIxMTYxMGU0MSJ9" }, { - "resource": "repo://TFI/App/MobileAppModel.swift#L580-L589", - "version": "repo-lines-v1:sha256:71bcd9d234f4fe43fb8074f23095acbfa238266403359ce6f85621ad7d170c6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc1Nzg1MjI2MjQyOTAxY2NkOTlmMTgzNjZjYTdhOGNhOGEwNTc1YTA0OGNkOGU3NTZlZmI2ZmRmMjc5MTFiNTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQzZGFiZTIzNDEzOTNjYzU1NWUzZjI0MzRlOTFlZTZjYjVjZTNhYTgwNWEwOWVhNjA2YTY1MDI3YzNlZWUzY2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzNDVkYzM2OGMxODNiMzEwOTZlMDYwYWFiODA1NzE2MTY3YTYyNDQ5MTgzMTM1NjJlMTgzNmQ1NDk0ZjIwMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4ZWY4Mjc0MmI5NzUwODBiMTdhNDZjZjVjMDYzOGM0MmY1ZGYxNjE3NTBjM2U0OTY1MGRhYTc1OTQzNjJmMWIifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFM/App/TermindApp.swift#L285-L294", @@ -253,10 +253,50 @@ "version": "repo-lines-v1:sha256:2e3a802d84fd2b017f0074f76c3ef896d3127453e0142f3a054521e823ba686f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIzZWIyNmFmMDY4MGI1ZTY3MGVmYjZkZGRhNjRmNGEyMzVmNDhmNTE2N2NlMWY4OTYwMDQ0MDU0MGE1OWRkMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlNGQzYWNkOTllYjdjOGNhNzFkNzZjODJjMTIxOTcxNzU5YjBlOTFmODVlNzhhNWQ1NDJlOWUzNWQ4ODFhODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] + }, + { + "id": "claim_02a529b91be24776a67c0d9335cd4710", + "statement": "MobileBackgroundController synchronously detaches and expires the matching lease, ends each acquired valid identifier at most once, rejects stale callbacks, immediately expires denied acquisition, and retires an identifier returned after reentrant expiration without overwriting or ending a replacement lease.", + "evidence": [ + { + "resource": "repo://TFI/Platform/MobileBackgroundController.swift#L45-L84", + "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://TFITests/MobileBackgroundControllerTests.swift#L6-L67", + "version": "repo-lines-v1:sha256:1e309292d25fb55839d18d0e9b27e986381b3ffa8eeb651fb1abe688b3e3e9a8:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjODI4MTNjZjZlYzQ4NTFmYzUxZTEzMGMzM2Y3ZWY4MGI3MzhhMDlmYWFiY2VkZjE1NDNhN2M4NWQ5ZTMwNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJjYmU2ZmRmMWQ0MTI5NWY2NTAyYmE4MWM1ZDJiN2ZlN2Q5ZmEwMWZlNTQ1ZDdlNmEyYWJjYzliZTMyNGI4YjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY5N2Y4NDFiZDJjNTk2YjJiMmYxMDU5MmNiYWE5Yzg5NWM3ZDkwMWYzOGJmZTY2N2M3MDMyZTAzOWM1YWY4MjIifQ" + } + ] + }, + { + "id": "claim_acd00840e6eb4c23b803ef72e666f4d6", + "statement": "Platform expiration requests cancellation without awaiting async teardown; mobile Assistant stops every conversation before retained consumer joins and command/connection closure, keeps its composer-send guard until cleanup returns across foreground activation, and cannot guarantee persistence or network cleanup completion after iOS suspension.", + "evidence": [ + { + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L158", + "version": "repo-lines-v1:sha256:0873ac799d8046793f591b26c6ec43d87615e49a3cf7b52e5fe0d10760b719e8:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + }, + { + "resource": "repo://TFI/Features/Workspaces/MobileWorkspace.swift#L1056-L1101", + "version": "repo-lines-v1:sha256:0a7ae642dd083c84f164b264fee0281ba51d48288dcddb66d8c2e9912a631140:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljN2Y4NWI1NDQ5YmI0MTM0YjhmMTAzN2M1ZDkwNjU4ZDNiMjU4YWIxMDZlNWZmNjEzNTBiZjg2MzlmYzY4ODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZGMzODIwMjNmZDQ2NDhkYjQzZmZhNjFjNWJmYjQ3ZjliOTZjZTIyZjFmNGI2OGYxZTczZGY3ODkxN2VlMDkifQ" + }, + { + "resource": "repo://TFI/Platform/MobileBackgroundController.swift#L45-L84", + "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:9c7f365e523b2d28fcfa76ef2f04bc330d1eaad9369561d1ac24393cc8a1a9e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NjIxN2JhNDkzNWU3Y2YwNDllYWNmYjNiNmQ5YWE3M2ExYzkzNjFlNDdmNGJlMDljODVlZTJhNTQ0Zjk2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWUyNTFjZGNkY2ZlNTFlNGY1M2E5ZGZhMjQxMjM4ZGNmZGNiNmNiYTdhNDEwZDdmOWJkMjY3NTg0ZGFlZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmNGJhMmVjOTU2MDNlZThiOWFlZmZiYmQ3MzY1NzEyOGNlYmJmNWNmYmFlZDczZmY5ZmEzZTI0ZTI0MzhjMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZWE3MTMwOWQ1YjQzYjZiZTIyZTQyZmIyOWFiNjAwYTIxZDY0ODcyOWVjMTIzYjk0MTVjOTA2MzJlMDQ3M2YifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T07:42:01.096Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index e551363e..ca80b087 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:47037593b68f4328e80cb60aadad6bb3290eb90e38ef9005c31e46440925be24", + "pageVersion": "sha256:b01cc417a1a0c5a773b8d9629cd44db7d365627da3cd7e699150cd184d1cb82f", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -66,7 +66,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFI/App/TermindIOSApp.swift", @@ -176,7 +176,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -191,6 +191,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:54:45.592Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index 81bf642a..e655a3af 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:7b1c2e0d961fa1fba7e88eb2273136e7a6950c3a06f80900d23aab9694031e69", + "pageVersion": "sha256:3401ab14c4112a9f5b713fed880da0ba98d8262566bc33d5e1f8288d540d7835", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -556,19 +556,19 @@ "evidence": [ { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L324-L370", - "version": "repo-lines-v1:sha256:ad9eaf7b1dc87c1e840daa96a98450465bda4c91dc8e5fb16f1c3d665084f121:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzYzdiMTYwOTA4ZTYxYWIxZThjNTM0ZTY4ZjU2Mjk0OTc0ZDNiYzI5M2EyZGFlNmU2OGY2NjJiMmU2ZWMyNjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlmOGIzMTVhNmQ3Y2JlZGQzOGVlZmUzMjBiZmRhZjdjMDI2ZGQ4YWRlMGNlNGE1NWY0OTExMjZiODA5YmJiNDYifQ" + "version": "repo-lines-v1:sha256:68b7a6565f302105947b7abf970400113b4d78049e9f6ea5285ea0dd92ba7dd9:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY3OGU1ZmRjMmU5N2UzOGUzMWNmMGQ4Nzk2ZGFjZmFmOGM3MGRjMzQ0M2Q4Yzk5M2ZhMDQyMjViZTY3YmVlNGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzNDZiOTg4YTdmZTk4YzVhYzg4ZGU5ZTQ5MzE0ZDlhMTYxMzFhYzFjNmFhNzlhZDI5MzVjNzNjNTRiZjllM2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkYjgwNDI2NmY5MzY2MTQyMzM0MTU3YWI3OTAyNWYxMWVkN2UzZTlmNTM1NzExOWViNGUwYjFhMzI1MzcwZDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmOGQ4YzU4NDUzYzE5YjU4YTg4NzY0NTIzNDcxNTExZTNiYzI1NzdjZmU4YmQ5MGRmOWE1NjZkNTQxYjA4NmIifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L185-L213", - "version": "repo-lines-v1:sha256:a30ec2eddcb339c146e32da1428a8f2381c02b6515aadcf05f27b41d4b6410bf:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU0NWZhOWExYTg0YWM0MzAwODViZTIxMDNkZmRlMTA5OTQ5ZTQ4OGY0NjA3YzRmYjM1ZWQ4Yjc2MjUyOTAwYzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YzRmZjVmY2EyMGUzODk0Zjc3ZmY2NDk0Y2FmNmVkZDU2NzFiZjE3N2YyMmVmMDE3NDBlNzNhMTYwMTc3OWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkM2NkNTgwOGZiNWY1MTI3NWEyY2IzNTQ3ZDcxYWJhZjJmYmYyMjlkYzFkNTkxOGU5MjNlYzNkN2FhNTRhY2QifQ" + "version": "repo-lines-v1:sha256:0ab82ec327ea7b5dccff1908e25447f652f40538356011b4340265fe298ee0cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwMDIyN2Q3NDQ0MzlkNGM2ZjIxNDM4NDAxOGE4YTYyNzE4NDk3ODliYjU4ZjFhZTY5MmM4ODQ2OWI5MmM5Y2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNGQxYWYwMGU2YjdhOWIyYWIyZjJlYWQ3N2ZiNzRiNWE0NGZkZTRlMTY0MDg0MGZiODMwYzg3NGVjZmM2ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViMzJkMTRjMmUxMDM3MWJkMmQ0NWJlYWRhMDdlZGM4NDlmNzlhMGE0M2EyNzg4ODJlNzFhYmM2YzBmYTkxYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNDE3ZTIwNzgwMTg5NTg4NGJkMWQ1OGRlM2FlOWNiMTNhODdiYWZjMzhmZDI0NzM4MjEwMTljNWRhMDkxMjAifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L1613-L1632", - "version": "repo-lines-v1:sha256:1bb7fc88e5f9e73eeaea78d36f1655d94dba673f8fe9bf6b08652e7e18fd45a5:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUyNjc3YmY3OTBlMWIzMDZhMTY1MWNlOGJjMTliM2JjYmU5NGQ3NDk4MjlhYThiNDcwNGZjYjRjYjkyYTc4MTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlkNmE1NzQxNTViMDMyNWQ5ZTA3YzA5MmNhMWJhNDAwMDBhYWNmN2FlYzMzZmNkOTMwZTA2NDg2YjVjYjY5YTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5NzkxODQ1NWVkODQyNThjMThkMDBmYmM2OTkwNGYyZWNkNWJjMmFlODVlMTY4MjlmYzY4YWEzNDhmZTI0YzMifQ" + "version": "repo-lines-v1:sha256:b2ad00511b042421f05266cc5113a422916a0f2495dc4806f0b30682b2edb40d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZlZTllNGFiODAxNWRmZDQ0ZmU0NzA3MTc4MjY5MmRlMTBlMzZjOGM4NTI1MGVkNTZiYTc1YTgzZDYxNzAzYTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyZmVlZmNmZWRkZDJmYmUxNGE2NGMyMjM1ODJmNzIxNTZkOWM0ODQ4NTk1Yzc4NzYxYjk2MmJkNTgyYmQwNjUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxMTQ1OGRlYzRmZTFiMDZhOWFlNDg3NTBhOGQyMWU5OTAyMDczMmRmNTE5ODUxZWJhZGNkOGE0MmU1MTExOWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1YmE2NDAxMDRkMTMzZmE0YmZiMjBlZDFhM2EzODdmMjhjOTViYjg4OGYxNzcwN2ZlZjk2OWEzODQwNTA3ODIifQ" }, { - "resource": "repo://TFITests/MobileAssistantTests.swift#L215-L240", - "version": "repo-lines-v1:sha256:198760cf2a134c8b7734e21e1666526e81539cda162f86a0c718c9af531b0643:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjMjY5OTU5MDM0ZmNmMTk1NzhiYTA5MTVkMTUyNTUzNzQ0Y2FjMTQ4NTIyM2M2YzI3MDY1ZTc4NzRhMzYwZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViMmY3NTYyYjc4YzhiNGIxMTdjZDY1ZDFlZTg4NmQzN2E4NGQyYTA1Yzg1YzY0MzA4Yzk4MmFiYzU4M2JlMDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmZWEwZWQ4NzRhMWQyZmU0MmUxZmUwOTBkNGYwOTFjOTdlMGYwYzRjZjJmZDdhNDFkNTg5Y2ZlZWVjMzVhMWUifQ" + "resource": "repo://TFITests/MobileAssistantTests.swift#L265-L291", + "version": "repo-lines-v1:sha256:c76b693bfa38fb88724ca5f747835c9ecd1da2a82e14a3aaf20c27a6b1f4eea6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxMjcxMWVhNWNjY2M3NjU4OGYwYzc4MzhhNDgyNjk1NzQ1ZDJjYjgwNTEwOTk4OGNlYjdiNDcxYjBlYjk2ODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViMmY3NTYyYjc4YzhiNGIxMTdjZDY1ZDFlZTg4NmQzN2E4NGQyYTA1Yzg1YzY0MzA4Yzk4MmFiYzU4M2JlMDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmZWEwZWQ4NzRhMWQyZmU0MmUxZmUwOTBkNGYwOTFjOTdlMGYwYzRjZjJmZDdhNDFkNTg5Y2ZlZWVjMzVhMWUifQ" } ] }, @@ -818,7 +818,7 @@ }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L156", - "version": "repo-lines-v1:sha256:948a190f2472d04ec334b75189ed92f9e5ee43e56f9d414625e299e5e9b5915b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + "version": "repo-lines-v1:sha256:5d7b7b28f33ec3976e03d613ce0916ef4e6630ba495c329ffb3513f1cc077a17:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" }, { "resource": "repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved#L1-L12", @@ -909,10 +909,42 @@ "version": "repo-lines-v1:sha256:06417f664159d51d61d0c4b32815f91c17e12c0127151e5dc657a463e34aa7cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2ODc2Yjk2OGUwYmE1M2U1NTE2ZDdkMjM4ODIwMjU2NTYzZDk1NTM0NzhlZmMxMDZhMWFhOTQ5ZTFjNGE5OTJmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlOGVhZjNlMWRiMjg0ZjZhNmZkNGUwNjZhZTJmYjY1NGFlNjhmZGZjZjNlN2U0ZGNhNGMyZGQyMTRhNDliOWQwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmYmE3MWNhMmVmMjRjOTljYWUwYzAyNjNiMzdlZTBkNzlhNjI0MWQyY2FhYzU2ODRhOWYyMDlmNzYyN2ZmMDZkIn0" } ] + }, + { + "id": "claim_41432081f4b343f7aaa2d05420f61d51", + "statement": "Mobile Assistant expiration sets its composer-send guard and synchronously requests cancellation of every stored conversation before awaiting any retained consumer; it then closes background commands and connections, ignores duplicate expiration while suspending, and clears the guard only after cleanup returns.", + "evidence": [ + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L158", + "version": "repo-lines-v1:sha256:0873ac799d8046793f591b26c6ec43d87615e49a3cf7b52e5fe0d10760b719e8:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:9c7f365e523b2d28fcfa76ef2f04bc330d1eaad9369561d1ac24393cc8a1a9e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NjIxN2JhNDkzNWU3Y2YwNDllYWNmYjNiNmQ5YWE3M2ExYzkzNjFlNDdmNGJlMDljODVlZTJhNTQ0Zjk2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWUyNTFjZGNkY2ZlNTFlNGY1M2E5ZGZhMjQxMjM4ZGNmZGNiNmNiYTdhNDEwZDdmOWJkMjY3NTg0ZGFlZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmNGJhMmVjOTU2MDNlZThiOWFlZmZiYmQ3MzY1NzEyOGNlYmJmNWNmYmFlZDczZmY5ZmEzZTI0ZTI0MzhjMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZWE3MTMwOWQ1YjQzYjZiZTIyZTQyZmIyOWFiNjAwYTIxZDY0ODcyOWVjMTIzYjk0MTVjOTA2MzJlMDQ3M2YifQ" + } + ] + }, + { + "id": "claim_e3490c4152854f0ebaa94bfcdd710f33", + "statement": "Mobile platform expiration invokes cancellation and ends its matching valid UIKit lease synchronously without joining Assistant persistence or transport cleanup, so iOS suspension or termination may prevent asynchronous cleanup completion even after the lease has ended.", + "evidence": [ + { + "resource": "repo://TFI/App/MobileAppModel.swift#L646-L663", + "version": "repo-lines-v1:sha256:8613dda7483542917c347fbb037141f5d2ec700d16c61b5ae233ade853ad9dc3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZDY0ZTIzNWJjNzZlYTM1MDI3NmE4MGZlMGM1NjM0ZTYwZDFkZmUwN2FjYWQ4NTJlMTFhMWQ2MjkzM2ViMTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyMWFjNTE4NDc5MWIzNWNkZTM4MDkzNDU1MTVjMGFjMDEwZjA3MGVhZTAyMTRjZGY0MzIwZDE4NzI5N2M3MDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0Yjc0YmRmNzgxODA1OTJlM2YyOTM1YzcxMzhlZmU1MDA0NzZhNjFjOTllMGZiZTcxNDVjYmRlMDBkYTU5MmEifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L141-L158", + "version": "repo-lines-v1:sha256:5d7b7b28f33ec3976e03d613ce0916ef4e6630ba495c329ffb3513f1cc077a17:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjMxZTAyNWIyZDQ2MGNjNGNjZGY5NTdkNjkzMGM4MTRiM2JlZGRlZGQ5MDVjMjNkOTg1MGIxMTNmNTFiMDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkNGJhYjUyYTNjOGQ3ODAzMDA0ODIwNzhlNGZkYzZhNzE5OTczNDg0OGI3ZmQwYmUyM2NkMzc5ZjJmNWIwNDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NTdhYTY2MTFhOTk2ZTA3NjllNjNiN2VhYjJmMzIzYTEwZTA3YTAxMmViYjk1MTBjYTdhMTE4M2U1MzQ4ZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhYjY4NmNlMzk2MmRjNDVmMTEyMDEzZDgyNzY4YTZmYzY0YWNjZTFmZjY4N2NhOWI3MzI5YzhlMGU5YTQ2MWUifQ" + }, + { + "resource": "repo://TFI/Platform/MobileBackgroundController.swift#L45-L84", + "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:54:45.592Z" + "at": "2026-09-23T07:42:01.096Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index 2c3475dd..922b7d09 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:f69ed12d7d30fae123a7513fe0417b3822282a643f6ae6127089ba9bc407b7c8", + "pageVersion": "sha256:9f1996861cee13d1379365982ac5f68c358ecf77f48759dfd86c90246c8c1b1a", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", @@ -12,7 +12,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -26,7 +26,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -457,6 +457,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:59:53.554Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/concepts/ui-design-principles.json b/openwiki/.claims/concepts/ui-design-principles.json index dbaa7b60..047b53f2 100644 --- a/openwiki/.claims/concepts/ui-design-principles.json +++ b/openwiki/.claims/concepts/ui-design-principles.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:92110e4a68f9373b2a5339feea69ea1a11bb1fe73727dd0e4709661aa7cb595a", + "pageVersion": "sha256:3204534fc02c6315b0c2fe23d6cb486a18f8b86be9b8a42dd31523c3bbfd560c", "claims": [ { "id": "claim_3a403468456840acbfe0a524b90d5717", @@ -181,8 +181,12 @@ "statement": "Continuity uses different retention budgets: desktop parked workspace geometry, an unmounted-owner transcript LRU, mobile app-owned workspaces with background-expiration policy, and stable children in adaptive mobile region layouts.", "evidence": [ { - "resource": "repo://TFI/App/MobileAppModel.swift#L512-L544", - "version": "repo-lines-v1:sha256:b41d3de43fde9c50d0b478946d6263804791efe4519784713c872a1679a205dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", + "version": "repo-lines-v1:sha256:87cbd4a7d016416f80a016da585683c18e185beb78163ce57d73511d38bd8d59:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjQ1ZWEzZDVjMjljYjRiOTAxNThmOTJlOGNiYzk4OGFjZjMyNGYzYzlhMTQ0NDExNmE3NjkyYmZjYmM2MDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlMGMwYzg3ZjJiZjhhNjVmNDIwYmY3ZjkzMWVhMmZmOWZiZmRjNmI4ZTlmNDg0YTdmNTgzNjhkMmRmYjE0NjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0Yjc0YmRmNzgxODA1OTJlM2YyOTM1YzcxMzhlZmU1MDA0NzZhNjFjOTllMGZiZTcxNDVjYmRlMDBkYTU5MmEifQ" }, { "resource": "repo://TFI/DesignSystem/MobileContentRegions.swift#L138-L163", @@ -347,6 +351,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.claims/integrations/mcp-server-and-bridge.json b/openwiki/.claims/integrations/mcp-server-and-bridge.json index 0e048e1f..3e083082 100644 --- a/openwiki/.claims/integrations/mcp-server-and-bridge.json +++ b/openwiki/.claims/integrations/mcp-server-and-bridge.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:25994ac645085f10b88bf658588df753039ac6bd5c79e6cdde785a0323fb495e", + "pageVersion": "sha256:e8702d030f8cc69c70ce0b630321aa0bf139b70e4dfb9e29b469cb5cfd77a53c", "claims": [ { "id": "claim_b47979774aa94e7dae517a6894c508d4", @@ -8,7 +8,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:72bc88a9f06e31aca2ec86d3b13ba58773798d2a53742e63a0287edf971c7f25" + "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" }, { "resource": "repo://TFM/App/MCPServerController.swift", @@ -319,6 +319,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T01:34:03.251Z" + "at": "2026-09-23T07:36:39.730Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 52c497a7..31077290 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T07:00:31.695Z", + "updatedAt": "2026-09-23T07:43:18.006Z", "command": "update", - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index efaef30e..fed5d5fa 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -9,39 +9,39 @@ "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" }, "/openwiki/architecture/engineering-invariants.md": { - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", - "pageVersion": "sha256:c8ad1f11ac788af965dd38b668950c45dda95ab336ac725cd1d9ab3e8f7ce8a8", + "pageVersion": "sha256:24879797730fe459acba1497911aff236d56a55e1ff25ed0b17487461df1506a", "completedBy": "codex", - "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" + "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" }, "/openwiki/architecture/mobile-ui.md": { - "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", - "sourceFingerprint": "sha256:437b5dbe7eee74926b4f7906dc9421b029371a6e9727a372f3728b7e67628691", - "pageVersion": "sha256:5a57c9f6cd1214629ea2a75c3ba60386a70d5c2170cdb552f4dea473f0dde193", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:3e4fffdec19e331d34ec12f5b4ead52c83e7d4d17e8f0937e8ba884b0d26ed34", "completedBy": "codex", - "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/architecture/module-map.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:cf89f7807de84434ff5a04aeed85734ce7e7f87b5fbc652e7210a60122fdd1d5", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:bca342add2ba08c7d23d8e2249dbd1fc90d80da4cd53969a03f04507a81c9e8e", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/architecture/native-app-directories.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:1011bb9602def6c6ca1bb7bf0cde718b8479b58ac8af9526e85bae3771596073", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:46bd31b52ab67eba8321c1885f18fb3436a08178dbccd92f5b6405559b9c3212", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/architecture/platform-runtime.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:4dbecf9ffc4a898da594e820778cd3e693619b49c45ffc6745b7684ef00c4f46", + "pageVersion": "sha256:11fb1a6bc89cdf3a069ac0842a06a6b2f060dff04e708d76584daa857818c42c", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" }, "/openwiki/architecture/shared-module-directories.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -58,18 +58,18 @@ "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64" }, "/openwiki/architecture/system-boundaries.md": { - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", - "pageVersion": "sha256:47037593b68f4328e80cb60aadad6bb3290eb90e38ef9005c31e46440925be24", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:b01cc417a1a0c5a773b8d9629cd44db7d365627da3cd7e699150cd184d1cb82f", "completedBy": "codex", - "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", - "pageVersion": "sha256:7b1c2e0d961fa1fba7e88eb2273136e7a6950c3a06f80900d23aab9694031e69", + "pageVersion": "sha256:3401ab14c4112a9f5b713fed880da0ba98d8262566bc33d5e1f8288d540d7835", "completedBy": "codex", - "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" + "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,11 +79,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { - "pageVersion": "sha256:f69ed12d7d30fae123a7513fe0417b3822282a643f6ae6127089ba9bc407b7c8", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:9f1996861cee13d1379365982ac5f68c358ecf77f48759dfd86c90246c8c1b1a", "completedBy": "codex", - "completedRunId": "8631be05-cca1-48cf-9f9c-5426ae3f8660", - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -107,18 +107,18 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/ui-design-principles.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:92110e4a68f9373b2a5339feea69ea1a11bb1fe73727dd0e4709661aa7cb595a", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:3204534fc02c6315b0c2fe23d6cb486a18f8b86be9b8a42dd31523c3bbfd560c", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/integrations/mcp-server-and-bridge.md": { - "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", - "sourceFingerprint": "sha256:f9936767fcb96648d4077d9b0eda87d3b9e9f36d42c78ac381d7c200080bcbf8", - "pageVersion": "sha256:25994ac645085f10b88bf658588df753039ac6bd5c79e6cdde785a0323fb495e", + "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", + "pageVersion": "sha256:e8702d030f8cc69c70ce0b630321aa0bf139b70e4dfb9e29b469cb5cfd77a53c", "completedBy": "codex", - "completedRunId": "07775097-b8b6-4b52-9321-61697240352d" + "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/operations/build-test-and-release.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index ca104f1a..54746337 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -82,6 +82,8 @@ sources: resource: repo://TFI/App/MobileSceneModel.swift - id: openwiki-source-6c9f797fc6a1e7ce639b72b8 resource: repo://TFI/App/TermindIOSApp.swift + - id: openwiki-source-d44869be46c03fbd0ce9c64c + resource: repo://TFI/Application/Agent/MobileAssistantModel.swift - id: openwiki-source-02810e1f8eac4d0983f8cd44 resource: repo://TFI/Features/Settings/MobileTerminalSettingsView.swift - id: openwiki-source-3c85f29e2d0bdf3d44f4e0e8 @@ -90,16 +92,18 @@ sources: resource: repo://TFI/Features/Vault/MobileVaultView.swift - id: openwiki-source-492f738896225c6b3acf7d2f resource: repo://TFI/Features/Workspaces/MobileWorkspace.swift - - id: openwiki-source-350d4a794cfef67e406ddf9b - resource: repo://TFI/Infrastructure/MobileWorkspaceStore.swift + - id: openwiki-source-34542e0f07e1650e098824c7 + resource: repo://TFI/Platform/MobileBackgroundController.swift - id: openwiki-source-e396438eb6a8a7ca888da5f4 resource: repo://TFI/Platform/TerminalFontCatalog.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-a154b5d36ec59496392d45e4 + resource: repo://TFITests/MobileAssistantTests.swift + - id: openwiki-source-c9618f94f83d3b529a9860e1 + resource: repo://TFITests/MobileBackgroundControllerTests.swift - id: openwiki-source-16ec96bd78162e7e62effd0e resource: repo://TFITests/MobileConnectionPickerTests.swift - - id: openwiki-source-9b78a7e39bfa08bea5192722 - resource: repo://TFITests/MobileWorkspaceStoreTests.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f resource: repo://TFM/App/AppModel.swift - id: openwiki-source-6570374eaac2e0278fa84640 @@ -114,10 +118,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } +generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:54:45.592Z + at: 2026-09-23T07:42:01.096Z --- # Cross-Cutting Engineering Invariants @@ -649,8 +653,8 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo ### Workspaces and activity - One SSH session/SFTP Channel per mobile Workspace, no Welcome/pane composition even with Pro; Assistant independent. Peer switch retains connection, - Recent opens on demand, cross-open retains live paths. Explicit close never retries; failures stay in Journey. Background expiration closes - transports but retains intent; foreground resumes existing Workspaces. + Recent opens on demand, cross-open retains live paths. Explicit close never retries; failures stay in Journey. Background expiration detaches + transports and requests their asynchronous closure while retaining intent; foreground resumes existing Workspaces. - `mobile.workspaces.v1` persists ordered local descriptors only (ID/Host/kind/title), no commands/secrets/paths/content/transfers. Cold launch after Hosts loads Recent only, no sessions/history/network. MainActor open-by-ID reuses or registers/connects once across scenes; failure needs explicit retry. Save retains unopened entries/order. Read legacy arrays/v1 and migrate legacy arrays to the versioned envelope; @@ -672,9 +676,15 @@ factories/Sendable buffer handlers. Timers/workers enter explicit short MainActo - App-wide runtime/conversation store prevents duplicate runs; each scene owns navigation/commands/library selection/New Chat draft. Focused values route keyboards; per-window delegates consume quick actions once. Switching neither reconnects nor claims terminal focus; Find owns F3/Shift-F3 and Escape only while active, otherwise Escape passes to Ghostty. +- Enter global background only when a nonempty set of registered scenes becomes entirely backgrounded, including after scene removal. + Repeated notifications/removals do not acquire another lease; removing the final scene starts none. Platform expiration detaches its lease, + invokes cancellation synchronously and ends that lease's valid identifier exactly once. Stale callbacks cannot expire a replacement; + denied acquisition expires immediately without ending an invalid identifier. Reentrant acquisition cannot leak or overwrite the older lease. - Settings editors use explicit drafts; ephemeral probes cancel on config/editor changes and cannot revive discarded data. Secrets remain transient until atomic save/rollback. Mobile never starts local MCP. Archive persists before navigation/read-only until restored. Injected speech appends - without send and cancels on interruption/conversation change/background. Finite background expiration cancels runs/drains events/closes Agent - transports, never replays or auto-approves on return. + without send and cancels on interruption/conversation change/background. Finite background expiration requests cancellation of every Assistant + conversation before awaiting any retained event drain, then closes Agent background commands and connections. This async cleanup remains best + effort under iOS suspension; it never extends the expired lease or promises durable completion. The composer-send guard stays set until cleanup + returns, including across foreground transitions; returning never replays or auto-approves runs. - Window drop interaction covers visible Workspaces: terminal routes files to uploads, text/links retain Ghostty behavior. Leaving restores original interaction/targets. File-provider data is copied inside callbacks into staging retained by queued transfers after navigation. diff --git a/openwiki/architecture/mobile-ui.md b/openwiki/architecture/mobile-ui.md index 37f136dc..5e974b23 100644 --- a/openwiki/architecture/mobile-ui.md +++ b/openwiki/architecture/mobile-ui.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T02:40:53.789Z + at: 2026-09-23T07:36:39.730Z --- # TFI Mobile UI Architecture diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index 8c9ed3ab..0b23d773 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T07:36:39.730Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 4eaac88a..78810edc 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T07:36:39.730Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index 5ac1d555..f48e0203 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -16,10 +16,20 @@ sources: resource: repo://TFI/App/MobileSceneModel.swift - id: openwiki-source-6c9f797fc6a1e7ce639b72b8 resource: repo://TFI/App/TermindIOSApp.swift + - id: openwiki-source-d44869be46c03fbd0ce9c64c + resource: repo://TFI/Application/Agent/MobileAssistantModel.swift - id: openwiki-source-9c78befae6b387426c603305 resource: repo://TFI/Application/Workspaces/MobileWorkspaceDescriptor.swift + - id: openwiki-source-492f738896225c6b3acf7d2f + resource: repo://TFI/Features/Workspaces/MobileWorkspace.swift + - id: openwiki-source-34542e0f07e1650e098824c7 + resource: repo://TFI/Platform/MobileBackgroundController.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-a154b5d36ec59496392d45e4 + resource: repo://TFITests/MobileAssistantTests.swift + - id: openwiki-source-c9618f94f83d3b529a9860e1 + resource: repo://TFITests/MobileBackgroundControllerTests.swift - id: openwiki-source-2c8d92432bb6dac7ede21141 resource: repo://TFITests/MobileForwardingLifecycleTests.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f @@ -40,10 +50,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-531f6567ecf96fd4b0c5e97a resource: repo://TFMTests/ExternalConnectionURLTests.swift -generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } +generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T07:42:01.096Z --- # Platform Runtime, App Composition, and Lifetimes @@ -163,7 +173,11 @@ Mobile persistence deliberately restores **descriptors, not sessions**. On cold ### Mobile foreground and background boundaries -Scene phases are aggregated by scene ID. The app only enters global background behavior when every tracked scene is backgrounded, so one backgrounded scene cannot suspend work still visible in another. On that transition it stops edit monitoring, suspends memory and sync, pauses metrics and outbox work, suspends recording, and starts a finite platform background task. If the task expires, it asks each live workspace to handle background expiration, stops forwarding, pauses transfers, and expires assistant work. It updates Live Activities but does not use them to retain connections. +Scene phases are aggregated by scene ID. The app enters global background behavior only when a nonempty set of tracked scenes becomes entirely backgrounded. Both phase changes and scene removal check that transition: removing the sole active scene starts background handling for surviving background scenes, while repeated notifications/removals and removal of the final scene do not start another lease. On that transition it stops edit monitoring, suspends memory and sync, pauses metrics and outbox work, suspends recording, and starts a finite platform background task. If the task expires, it asks each live workspace to handle background expiration, stops forwarding, pauses transfers, and expires assistant work. It updates Live Activities but does not use them to retain connections. + +`MobileBackgroundController` keeps one identity-bearing lease around the UIKit identifier. The expiration handler runs synchronously on the MainActor: it detaches the matching lease, invokes the app's cancellation callback, and ends that lease's valid identifier before returning. Finishing or replacing a lease makes its old callbacks inert. A denied acquisition immediately invokes expiration without ending an invalid identifier. If expiration reenters `begin`, the old lease cannot end or overwrite its replacement; if the platform calls expiration before returning an identifier, the outer acquisition retires the returned old identifier afterward. Each valid identifier is ended at most once. + +The callback requests suspension; it does not await network teardown or persistence. Workspaces detach their sessions and start asynchronous disconnects. Assistant synchronously requests cancellation for every conversation before joining retained consumers, then closes background commands and connection pools. Those drains remain best effort under iOS suspension or termination, so an ended platform lease is not evidence that a save or remote cleanup completed. The Assistant composer-send guard remains set until its cleanup task returns, even if a scene becomes active meanwhile. When any scene becomes active, mobile refreshes its current device-directory record, reenables edit monitoring, finishes the background task, asks existing workspaces to resume, reconciles sync according to foreground entitlement, restarts the outbox and metrics, refreshes entitlement asynchronously, and updates activities. Returning before expiration therefore preserves healthy connections; after expiration, workspace recovery follows the workspace's reconnection journey. Expired forwarding and pending interactive authentication are stopped rather than silently restarted on foreground. @@ -189,7 +203,9 @@ For a service that represents navigation, focus, a sheet, an editor draft, a des Tests exercise the lifetime contracts rather than merely constructing views: - `MobileAppTests` verifies saved descriptors reopen on demand without replaying commands or paths, and that two scenes selecting one restored workspace create only one connection. -- `MobileWorkspaceConnectionRecoveryTests` verifies background expiration disconnects affected mobile workspaces and foreground resumes them, while a quick foreground return preserves a healthy connection. +- `MobileWorkspaceConnectionRecoveryTests` verifies background expiration disconnects affected mobile workspaces and foreground resumes them, while a quick foreground return preserves a healthy connection. Scene-removal fixtures also check that removing the sole active scene starts one lease for surviving background scenes and that an empty scene set starts none. +- `MobileBackgroundControllerTests` checks synchronous callback/end ordering, stale and repeated callbacks, denied acquisition, and reentrant expiration during acquisition without losing or ending the replacement lease. +- `MobileAssistantTests` holds a first conversation's terminal save while expiring a second queued run: cancellation is immediate, the queued provider never starts, and suspension remains joinable until the save barrier releases. This verifies application ordering, not an operating-system guarantee of cleanup after suspension. - `MobileForwardingLifecycleTests` verifies all-scenes background is required before expiration, and expired forwarding/authentication does not revive merely on foreground. - `DockMenuTests` verifies Dock-created desktop windows receive distinct `WindowModel`s in the Welcome state and retain per-window UI state independently. - `ExternalConnectionURLTests` verifies external desktop connections are temporary and unsaved, including the Telnet-password confirmation boundary. diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index 55372f0d..a7e22f3c 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:54:45.592Z + at: 2026-09-23T07:36:39.730Z --- # System Boundaries and Dependency Ownership diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index b10fb783..40a4dbd1 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -54,6 +54,8 @@ sources: resource: repo://TFI/Application/Agent/MobileAssistantModel.swift - id: openwiki-source-3dfad0e95bb7d66f292c20c9 resource: repo://TFI/Features/AI/MobileAssistantConversation.swift + - id: openwiki-source-34542e0f07e1650e098824c7 + resource: repo://TFI/Platform/MobileBackgroundController.swift - id: openwiki-source-a154b5d36ec59496392d45e4 resource: repo://TFITests/MobileAssistantTests.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f @@ -80,10 +82,10 @@ sources: resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } +generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:54:45.592Z + at: 2026-09-23T07:42:01.096Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -200,7 +202,11 @@ Once startup has begun, Stop requests runtime cancellation; the consumer checks `stopAndWait()` captures preparation cancellation and all retained run owners before its first await, requests cancellation of the owner active at entry, then joins preparation and those consumers. Consequently `stop()` followed by `stopAndWait()` still joins the stopped run; a replacement accepted during the wait is outside that call's cancellation scope. Completed owners are removed from the retained collection. -The join guarantees that the captured snapshot-save attempt has returned, not that storage succeeded. In pinned AgentKit, terminal events precede the terminal snapshot save, while stream completion follows it; a terminal event alone is therefore an earlier boundary. The separate durable-event recorder and run-secret disposal tasks are not joined by this consumer. These contracts do not promise recovery from a storage failure or process death before a save. Mobile background expiration uses the join before closing its background commands and Agent connections; desktop Stop keeps its immediate presentation behavior without adopting the mobile background lifecycle. +The join guarantees that the captured snapshot-save attempt has returned, not that storage succeeded. In pinned AgentKit, terminal events precede the terminal snapshot save, while stream completion follows it; a terminal event alone is therefore an earlier boundary. The separate durable-event recorder and run-secret disposal tasks are not joined by this consumer. These contracts do not promise recovery from a storage failure or process death before a save. Desktop Stop keeps its immediate presentation behavior without adopting the mobile background lifecycle. + +Mobile background expiration first sets `isSuspending`, cancels pending preparations and recording, snapshots the stored conversations, and calls `stop()` on every conversation synchronously. Only after all cancellation requests are issued does its cleanup task call `stopAndWait()` for each snapshot member, then close background commands and Agent connections. A first conversation's blocked save therefore cannot postpone cancellation of a later queued or active run. Repeated expiration during that cleanup is inert, and composer sends remain blocked until cleanup returns, including when a scene has already returned to the foreground. + +The finite UIKit lease has a separate boundary: its expiration callback requests this cleanup and ends the matching valid lease synchronously, without awaiting those drains. Cleanup may continue when execution is available, but iOS suspension or termination can prevent completion. Neither the lease nor Live Activities guarantee persistence or continued network work. See [Platform Runtime](/openwiki/architecture/platform-runtime.md) for scene aggregation, lease identity and workspace recovery. ## Approval, secrets, and trust boundaries @@ -317,7 +323,7 @@ The focused tests establish behavior that is important when changing this area: - `FrameworkTests/AssistantConversationOperationsTests.swift` verifies persistence-first history mutations and activity guards, plus queued Stop/Stop-and-Wait with zero provider/tool calls, captured cancelled snapshots, Stop followed by a blocked-save join, persistence after presentation release, direct queued-owner displacement, pending approval cancellation, and an old persistence drain finishing while a replacement remains running. Injected review events verify that the stopped run's review state and card do not contaminate that replacement. - `TFMTests/AgentScratchTests.swift` verifies workspace containment, symlink refusal, inter-conversation isolation, quotas, and bounded reads. - `TFMTests/AgentBuiltInToolAvailabilityTests.swift` checks exact Termind planning/acting/reviewing sets, the shared SSH schema with mode-specific preflight, and execution spies proving the surface gate blocks only terminal suggestions. -- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. Mobile's SwiftData restore/edit fixture also verifies same-run prefix preservation and replacement provenance. +- `FrameworkTests/AgentSteeringTests.swift` and `TFITests/MobileAssistantTests.swift` check snapshot preservation, delivery timing, legacy unknown context, mobile send/steer availability, acting-mode plan implementation, and tool-incapable models. Mobile's SwiftData restore/edit fixture also verifies same-run prefix preservation and replacement provenance. Its expiration fixture holds the first run's terminal save while a second run remains queued, verifies immediate cancellation and zero second-provider calls, and uses an entered/completed join probe to show suspension remains pending until the save releases. - `TFMTests/MCPServerToolsTests.swift` checks the exported acting subset against shared descriptors, transfer tools and disabled calls; `AgentHostProtocolListingTests.swift` checks protocol-neutral discovery, and `AgentToolPresentationTests.swift` retains legacy presentation coverage alongside the persistence suite. The queued-cancellation and captured-failure SwiftData regressions use an in-memory container with a fresh read context, not a process restart or an on-disk crash test. Controller barriers establish save-attempt ordering and inject a repository save rejection; they do not simulate a failing SwiftData disk transaction. The queued-runtime barrier and review-state fixtures remain ordering/presentation tests, with injected events rather than a live Guardian provider. Preserve these distinctions when choosing additional validation. diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index 3c368757..32d684ec 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T06:59:53.554Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:59:53.554Z + at: 2026-09-23T07:36:39.730Z --- # Persistent Data, Migrations, and Secret Boundaries diff --git a/openwiki/concepts/ui-design-principles.md b/openwiki/concepts/ui-design-principles.md index 70c2db4c..5c8b976f 100644 --- a/openwiki/concepts/ui-design-principles.md +++ b/openwiki/concepts/ui-design-principles.md @@ -91,7 +91,7 @@ sources: generated: { by: "codex", at: "2026-09-23T05:17:08.701Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T07:36:39.730Z --- # UI Design Principles and Shared Presentation Boundaries diff --git a/openwiki/integrations/mcp-server-and-bridge.md b/openwiki/integrations/mcp-server-and-bridge.md index a2e2b9ff..8712f92b 100644 --- a/openwiki/integrations/mcp-server-and-bridge.md +++ b/openwiki/integrations/mcp-server-and-bridge.md @@ -45,7 +45,7 @@ sources: generated: { by: "codex", at: "2026-09-23T01:34:03.251Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T01:34:03.251Z + at: 2026-09-23T07:36:39.730Z --- # MCP Server, Transfer APIs, and Stdio Bridge From a689b3509cdb4a2936f60da15e873c278be2b520 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 16:21:17 +0800 Subject: [PATCH 22/30] Fix macOS Assistant termination ownership --- TFM/App/AppDependencies.swift | 16 +- TFM/App/AppModel.swift | 59 ++++- TFM/App/TermindApp.swift | 45 ++-- TFMTests/AppTerminationTests.swift | 355 +++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+), 26 deletions(-) create mode 100644 TFMTests/AppTerminationTests.swift diff --git a/TFM/App/AppDependencies.swift b/TFM/App/AppDependencies.swift index c86dac26..22dba657 100644 --- a/TFM/App/AppDependencies.swift +++ b/TFM/App/AppDependencies.swift @@ -7,6 +7,14 @@ import TermindRemoteInfrastructure import struct TermindDomain.Host +@MainActor +struct AppShutdownDependencies { + let stopMCP: @MainActor () async -> Void + let stopSFTP: @MainActor () async -> Void + let closeAgentBackgroundCommands: @MainActor () async -> Void + let closeAgentConnections: @MainActor () async -> Void +} + @MainActor struct AppDependencies { let hostRepository: any HostRepository @@ -21,6 +29,8 @@ struct AppDependencies { let mcpServerSecrets: any SecretStoring let logging: any ApplicationLogManaging let settingsDefaults: UserDefaults + let assistantConversations: AssistantConversationStore? + let shutdown: AppShutdownDependencies? init( hostRepository: any HostRepository, outboxRunner: OutboxRunner?, @@ -33,7 +43,9 @@ struct AppDependencies { metricsPreferences: (any MetricsConfigurationStoring)? = nil, mcpServerSecrets: any SecretStoring = InMemorySecretStore(), logging: any ApplicationLogManaging = DisabledApplicationLogManager.shared, - settingsDefaults: UserDefaults = .standard + settingsDefaults: UserDefaults = .standard, + assistantConversations: AssistantConversationStore? = nil, + shutdown: AppShutdownDependencies? = nil ) { self.hostRepository = hostRepository self.outboxRunner = outboxRunner @@ -47,6 +59,8 @@ struct AppDependencies { self.mcpServerSecrets = mcpServerSecrets self.logging = logging self.settingsDefaults = settingsDefaults + self.assistantConversations = assistantConversations + self.shutdown = shutdown } static func inMemory(hosts: [Host], groups: [HostGroup]) -> Self { diff --git a/TFM/App/AppModel.swift b/TFM/App/AppModel.swift index 04bdbd7c..e8ed3ac0 100644 --- a/TFM/App/AppModel.swift +++ b/TFM/App/AppModel.swift @@ -42,6 +42,7 @@ nonisolated enum HostOpenTarget: Hashable, Sendable { @Observable final class AppModel { private var terminationTask: Task? + private var isTerminating = false private final class WeakWindow { weak var value: WindowModel? init(_ value: WindowModel) { self.value = value } @@ -104,7 +105,7 @@ final class AppModel { let speechTranscription: SpeechTranscriptionCoordinator /// The one set of conversations every window navigates. See /// `AssistantConversationStore`. - private let assistantConversations = AssistantConversationStore() + private let assistantConversations: AssistantConversationStore private let agentRunRepository: any AgentRunPersisting private let agentHosts: AgentHostResolver private let agentConnections: AgentConnectionPool @@ -115,6 +116,7 @@ final class AppModel { private let agentBackgroundCommands: AgentBackgroundCommands private let outboxRunner: OutboxRunner? private let settingsDefaults: UserDefaults + private let shutdownDependencies: AppShutdownDependencies? private var windows: [ObjectIdentifier: WeakWindow] = [:] private var deletedAgentConversationIDs = Set() /// Conversations already handed to the summarizer. Titling is once per @@ -162,6 +164,8 @@ final class AppModel { self.windowSizeStore = windowSizeStore let dependencies = dependencies ?? .inMemory(hosts: hosts.hosts, groups: hosts.groups) settingsDefaults = dependencies.settingsDefaults + assistantConversations = dependencies.assistantConversations ?? AssistantConversationStore() + shutdownDependencies = dependencies.shutdown logging = dependencies.logging let externalAuthentication = ExternalAuthCoordinator( logger: logging.makeLogger(label: "termind.external-mcp")) @@ -393,7 +397,10 @@ final class AppModel { @ObservationIgnored private lazy var assistantExecution = AssistantExecutionController( ai: ai, skills: skills, repository: agentRunRepository, memory: memory, - authorize: { [weak self] in self?.proAccess.require(.assistant) == true }, + authorize: { [weak self] in + guard let self, !self.isTerminating else { return false } + return self.proAccess.require(.assistant) + }, makeRegistry: { [weak self] conversation, model, provider, skills, channel in guard let self else { return AgentToolRegistry([AnyAgentTool]()) } return AssistantToolRegistry.make( @@ -717,9 +724,47 @@ final class AppModel { /// The delegate gives this asynchronous cleanup a bounded termination window. @discardableResult func shutdownForTermination() -> Task { + isTerminating = true + if let terminationTask { return terminationTask } + let conversations = assistantConversations.conversations + let preparationCancellation = assistantExecution.cancelPendingRuns() + for conversation in conversations { conversation.stop() } + let conversationDrains = conversations.map { conversation in + Task { @MainActor in await conversation.stopAndWait() } + } + let task = Task { + [shutdownDependencies, mcpServer, sftpTransfers, agentBackgroundCommands, agentConnections] in + async let stopMCP: Void = { + if let stop = shutdownDependencies?.stopMCP { + await stop() + } else { + await mcpServer.stop() + } + }() + async let stopSFTP: Void = { + if let stop = shutdownDependencies?.stopSFTP { + await stop() + } else { + await sftpTransfers.waitForCancellation() + } + }() + await preparationCancellation.value + for drain in conversationDrains { await drain.value } + _ = await (stopMCP, stopSFTP) + if let close = shutdownDependencies?.closeAgentBackgroundCommands { + await close() + } else { + await agentBackgroundCommands.closeAll() + } + if let close = shutdownDependencies?.closeAgentConnections { + await close() + } else { + await agentConnections.closeAll() + } + } + terminationTask = task memory.suspend() sftpEdits.setMonitoring(false) - if let terminationTask { return terminationTask } serverMetrics.shutdown() speechTranscription.cancelAll() let liveWindows = windows.values.compactMap(\.value) @@ -728,14 +773,6 @@ final class AppModel { sftpTransfers.cancelAll() externalAuthentication.cancelAll() forwarding.stopAll() - let task = Task { [mcpServer, sftpTransfers, agentConnections, agentBackgroundCommands] in - async let stopMCP: Void = mcpServer.stop() - async let stopSFTP: Void = sftpTransfers.waitForCancellation() - _ = await (stopMCP, stopSFTP) - await agentBackgroundCommands.closeAll() - await agentConnections.closeAll() - } - terminationTask = task outboxRunner?.stop() return task } diff --git a/TFM/App/TermindApp.swift b/TFM/App/TermindApp.swift index 14290784..b2410a2c 100644 --- a/TFM/App/TermindApp.swift +++ b/TFM/App/TermindApp.swift @@ -213,6 +213,34 @@ struct TermindApp: App { } #if canImport(AppKit) + @MainActor + final class AppTerminationReplyCoordinator { + typealias Sleep = @Sendable (Duration) async throws -> Void + + private var replied = false + + func begin( + cleanup: Task, timeout: Duration = .seconds(3), + sleep: @escaping Sleep = { try await Task.sleep(for: $0) }, + reply: @escaping @MainActor () -> Void + ) { + Task { @MainActor [weak self] in + await cleanup.value + self?.finish(reply) + } + Task { @MainActor [weak self] in + try? await sleep(timeout) + self?.finish(reply) + } + } + + private func finish(_ reply: @MainActor () -> Void) { + guard !replied else { return } + replied = true + reply() + } + } + /// Owns native application lifecycle; window and pane commands live in `TermindCommands`. final class AppDelegate: NSObject, NSApplicationDelegate { weak var app: AppModel? { @@ -228,7 +256,7 @@ struct TermindApp: App { private let metricsActivity = MetricsActivityObserver() private let metricsMenu = MetricsMenuBarController() private var awaitingTermination = false - private var repliedToTermination = false + private let terminationReply = AppTerminationReplyCoordinator() private var quietLaunchObserver: (any NSObjectProtocol)? private let appearanceController = AppAppearanceController() @@ -381,20 +409,9 @@ struct TermindApp: App { private func beginTermination(_ app: AppModel) { app.mainWindows.saveSizeForTermination() let cleanup = app.shutdownForTermination() - Task { @MainActor [weak self] in - await cleanup.value - self?.finishTermination() + terminationReply.begin(cleanup: cleanup) { + NSApp.reply(toApplicationShouldTerminate: true) } - Task { @MainActor [weak self] in - try? await Task.sleep(for: .seconds(3)) - self?.finishTermination() - } - } - - private func finishTermination() { - guard !repliedToTermination else { return } - repliedToTermination = true - NSApp.reply(toApplicationShouldTerminate: true) } func applicationWillTerminate(_ notification: Notification) { diff --git a/TFMTests/AppTerminationTests.swift b/TFMTests/AppTerminationTests.swift new file mode 100644 index 00000000..f9a00cbe --- /dev/null +++ b/TFMTests/AppTerminationTests.swift @@ -0,0 +1,355 @@ +import AgentKit +import Foundation +import TermindApplication +import TermindDomain +import TermindInfrastructure +import Testing + +@testable import Termind + +@Suite(.timeLimit(.minutes(1))) @MainActor +struct AppTerminationTests { + @Test + func shutdownCancelsEveryStoredConversationBeforeAwaitingAndClosesResourcesAfterDrains() async throws { + let events = TerminationEventLog() + let firstRepository = TerminationRepository( + blocksTerminalSave: true, + terminalSaved: { await events.append("save") }) + let secondRepository = TerminationRepository() + let firstCalls = TerminationModelCalls() + let secondCalls = TerminationModelCalls() + let first = queuedConversation(repository: firstRepository, calls: firstCalls) + let second = queuedConversation(repository: secondRepository, calls: secondCalls) + let store = AssistantConversationStore(conversations: [first, second]) + let app = makeApp( + repository: firstRepository, store: store, + shutdown: AppShutdownDependencies( + stopMCP: { await events.append("mcp") }, + stopSFTP: { await events.append("sftp") }, + closeAgentBackgroundCommands: { await events.append("background") }, + closeAgentConnections: { await events.append("connections") } + )) + let window = WindowModel() + app.registerWindow(window) + #expect(window.assistantLibrary.conversation(id: first.id) === first) + window.openAssistantInSidebar(second.id) + #expect(window.sidebar.assistantConversationID == second.id) + app.unregisterWindow(window) + + let cleanup = app.shutdownForTermination() + let repeatedCleanup = app.shutdownForTermination() + await firstRepository.waitForTerminalSave() + + #expect(first.runState == .cancelled) + #expect(second.runState == .cancelled) + #expect(await firstCalls.count == 0) + #expect(await secondCalls.count == 0) + #expect(!(await events.values).contains("background")) + #expect(!(await events.values).contains("connections")) + + await firstRepository.releaseTerminalSaves() + await cleanup.value + await repeatedCleanup.value + + let values = await events.values + let save = try #require(values.firstIndex(of: "save")) + let background = try #require(values.firstIndex(of: "background")) + let connections = try #require(values.firstIndex(of: "connections")) + #expect(values.filter { $0 == "mcp" }.count == 1) + #expect(values.filter { $0 == "sftp" }.count == 1) + #expect(values.filter { $0 == "background" }.count == 1) + #expect(values.filter { $0 == "connections" }.count == 1) + #expect(save < background) + #expect(background < connections) + #expect(await firstRepository.savedTerminalState == .cancelled) + #expect(await secondRepository.savedTerminalState == .cancelled) + } + + @Test + func shutdownFencesEveryAssistantAdmissionAndInvalidatesAnAlreadyPreparedEdit() async throws { + let repository = TerminationRepository() + let original = AgentTranscriptMessage(role: .user, text: "before", authoredText: "before") + let preparedEdit = ConversationModel( + messages: [ChatMessage(id: original.id, role: .user, text: original.text)], + transcript: [original]) + let draft = ConversationModel() + draft.composer.draft = "draft after shutdown" + let normal = ConversationModel() + let directStart = ConversationModel() + let planConversation = ConversationModel() + let plan = AgentPlan( + title: "Shutdown plan", body: "Must not run", scratchPath: "/tmp/shutdown-plan.md") + planConversation.isPlanning = true + planConversation.handle(.planPresented(plan)) + let postFenceEditMessage = AgentTranscriptMessage(role: .user, text: "edit me", authoredText: "edit me") + let postFenceEdit = ConversationModel( + messages: [ChatMessage(id: postFenceEditMessage.id, role: .user, text: postFenceEditMessage.text)], + transcript: [postFenceEditMessage]) + let store = AssistantConversationStore( + conversations: [preparedEdit, draft, normal, directStart, planConversation, postFenceEdit]) + let app = makeApp(repository: repository, store: store) + let window = WindowModel() + app.registerWindow(window) + + window.assistantCommands?.edit( + preparedEdit.id, original.id, "prepared before shutdown", .none, false) + #expect(preparedEdit.isPreparingAgentRun) + let cleanup = app.shutdownForTermination() + + app.sendAgentDraft(draft) + let newConversation = ConversationModel() + newConversation.composer.draft = "new conversation after shutdown" + #expect(app.sendNewAgentConversation(newConversation, in: window.assistantLibrary) == nil) + window.assistantLibrary.draftConversation.composer.draft = "new after shutdown" + #expect(app.sendNewAgentDraft(in: window.assistantLibrary) == nil) + #expect(!app.sendAgentText("normal after shutdown", in: normal)) + + let planCard = try #require(planConversation.plans.first) + #expect(!app.sendPlanImplementation(planCard, in: planConversation)) + + app.startAgentRun( + directStart, prompt: "explicit start after shutdown", + authoredPrompt: "explicit start after shutdown") + window.assistantCommands?.edit( + postFenceEdit.id, postFenceEditMessage.id, "edit after shutdown", .none, false) + + await cleanup.value + for _ in 0..<10 { await Task.yield() } + + #expect(preparedEdit.transcript == [original]) + #expect(draft.transcript.isEmpty) + #expect(draft.composer.draft == "draft after shutdown") + #expect(normal.transcript.isEmpty) + #expect(directStart.transcript.isEmpty) + #expect(planConversation.plans.first?.outcome == .pending) + #expect(planConversation.isPlanning) + #expect(postFenceEdit.transcript == [postFenceEditMessage]) + #expect(newConversation.transcript.isEmpty) + #expect(await repository.truncationCount == 0) + #expect(await repository.savedSnapshots.map(\.state) == [.interrupted]) + } + + @Test + func shutdownJoinsAnUnstoredCapturedFailureBeforeClosingResources() async { + let events = TerminationEventLog() + let repository = TerminationRepository( + blocksTerminalSave: true, + terminalSaved: { await events.append("save") }) + let app = makeApp( + repository: repository, + shutdown: AppShutdownDependencies( + stopMCP: {}, stopSFTP: {}, + closeAgentBackgroundCommands: { await events.append("background") }, + closeAgentConnections: { await events.append("connections") })) + let conversation = ConversationModel() + + #expect(app.sendAgentText("captured failure", in: conversation)) + await repository.waitForTerminalSave() + #expect(conversation.isPersistingAgentPreparationFailure) + + let cleanup = app.shutdownForTermination() + for _ in 0..<10 { await Task.yield() } + #expect(!(await events.values).contains("background")) + #expect(await repository.savedSnapshots.map(\.state) == [.failed]) + + await repository.releaseTerminalSaves() + await cleanup.value + + #expect(conversation.runState == .failed) + #expect(await repository.savedSnapshots.map(\.state) == [.failed]) + #expect(await events.values == ["save", "background", "connections"]) + } + + @Test + func terminationReplyFallsBackWhenCleanupHangsAndRepliesOnlyOnce() async { + let cleanupGate = TerminationGate() + let fallbackGate = TerminationGate() + let replies = TerminationReplyCount() + let cleanup = Task { await cleanupGate.wait() } + let coordinator = AppTerminationReplyCoordinator() + + coordinator.begin( + cleanup: cleanup, timeout: .seconds(3), + sleep: { duration in + #expect(duration == .seconds(3)) + await fallbackGate.wait() + }, + reply: { replies.value += 1 }) + + await fallbackGate.waitUntilEntered() + #expect(replies.value == 0) + await fallbackGate.release() + await waitUntil { replies.value == 1 } + + await cleanupGate.release() + await cleanup.value + for _ in 0..<10 { await Task.yield() } + #expect(replies.value == 1) + + let lateFallback = TerminationGate() + let cleanupFirstReplies = TerminationReplyCount() + let cleanupFirst = AppTerminationReplyCoordinator() + cleanupFirst.begin( + cleanup: Task {}, + sleep: { _ in await lateFallback.wait() }, + reply: { cleanupFirstReplies.value += 1 }) + await waitUntil { cleanupFirstReplies.value == 1 } + await lateFallback.release() + for _ in 0..<10 { await Task.yield() } + #expect(cleanupFirstReplies.value == 1) + } + + private func queuedConversation( + repository: any AgentRunPersisting, calls: TerminationModelCalls + ) -> ConversationModel { + let conversation = ConversationModel() + let runtime = AgentRuntime( + model: TerminationCountingModel(calls: calls), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .deny("test") }), + repository: repository) + conversation.start( + runtime: runtime, + request: AgentRunRequest( + conversationID: conversation.id, prompt: "queued", + permissionMode: .askForApproval), + repository: repository) + return conversation + } + + private func makeApp( + repository: any AgentRunPersisting, + store: AssistantConversationStore = AssistantConversationStore(), + shutdown: AppShutdownDependencies? = nil + ) -> AppModel { + let defaults = UserDefaults(suiteName: "termind.tests.termination.\(UUID().uuidString)")! + return AppModel( + hosts: HostLibraryModel(), + dependencies: AppDependencies( + hostRepository: InMemoryHostRepository(), + outboxRunner: nil, + agentRunRepository: repository, + settingsDefaults: defaults, + assistantConversations: store, + shutdown: shutdown)) + } + + private func waitUntil(_ condition: @escaping @MainActor () -> Bool) async { + while !condition() { await Task.yield() } + } +} + +private struct TerminationCountingModel: AgentModelStreaming { + let calls: TerminationModelCalls + + func stream(_ request: AgentModelRequest) -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + await calls.called() + continuation.yield(.finished(.completed)) + continuation.finish() + } + } + } +} + +private actor TerminationModelCalls { + private(set) var count = 0 + func called() { count += 1 } +} + +private actor TerminationEventLog { + private(set) var values: [String] = [] + func append(_ value: String) { values.append(value) } +} + +private actor TerminationGate { + private var entered = false + private var released = false + private var entryWaiters: [CheckedContinuation] = [] + private var releaseWaiters: [CheckedContinuation] = [] + + func wait() async { + entered = true + let waitingForEntry = entryWaiters + entryWaiters.removeAll() + for waiter in waitingForEntry { waiter.resume() } + guard !released else { return } + await withCheckedContinuation { releaseWaiters.append($0) } + } + + func waitUntilEntered() async { + guard !entered else { return } + await withCheckedContinuation { entryWaiters.append($0) } + } + + func release() { + released = true + let waitingForRelease = releaseWaiters + releaseWaiters.removeAll() + for waiter in waitingForRelease { waiter.resume() } + } +} + +@MainActor +private final class TerminationReplyCount { + var value = 0 +} + +private actor TerminationRepository: AgentRunPersisting { + private let blocksTerminalSave: Bool + private let terminalSaved: (@Sendable () async -> Void)? + private var terminalSaveEntered = false + private var terminalSaveWaiters: [CheckedContinuation] = [] + private var terminalReleaseWaiters: [CheckedContinuation] = [] + private(set) var savedTerminalState: AgentRunState? + private(set) var savedSnapshots: [AgentRunSnapshot] = [] + private(set) var truncationCount = 0 + + init( + blocksTerminalSave: Bool = false, + terminalSaved: (@Sendable () async -> Void)? = nil + ) { + self.blocksTerminalSave = blocksTerminalSave + self.terminalSaved = terminalSaved + } + + func save(_ snapshot: AgentRunSnapshot) async { + savedSnapshots.append(snapshot) + guard snapshot.state.isTerminal else { return } + terminalSaveEntered = true + let waiters = terminalSaveWaiters + terminalSaveWaiters.removeAll() + for waiter in waiters { waiter.resume() } + if blocksTerminalSave { + await withCheckedContinuation { terminalReleaseWaiters.append($0) } + } + await terminalSaved?() + savedTerminalState = snapshot.state + } + + func waitForTerminalSave() async { + guard !terminalSaveEntered else { return } + await withCheckedContinuation { terminalSaveWaiters.append($0) } + } + + func releaseTerminalSaves() { + let waiters = terminalReleaseWaiters + terminalReleaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func record(_ event: AgentEvent, runID: UUID) {} + func persistCompaction(_ record: AgentCompactionRecord, conversationID: UUID) {} + func loadConversations() -> [StoredAgentConversation] { [] } + func deleteConversation(_ id: UUID) {} + func setTitle(_ title: String, conversationID: UUID) {} + func setConversationMetadata(_ metadata: StoredAgentConversationMetadata, conversationID: UUID) {} + func reorderConversations(_ updates: [StoredAgentConversationOrder]) {} + func truncateConversation( + _ id: UUID, fromUserMessageID messageID: UUID, + replacingWith message: AgentTranscriptMessage + ) { + truncationCount += 1 + } + func createConversation(_ conversation: StoredAgentConversation) {} +} From b3f220b7717c5117b36c88d04e1672b7a38fd97a Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 16:27:32 +0800 Subject: [PATCH 23/30] Document macOS Assistant termination ownership --- openwiki/.claims/architecture/desktop-ui.json | 10 +- .../architecture/engineering-invariants.json | 70 +++++++++++- openwiki/.claims/architecture/module-map.json | 8 +- .../architecture/native-app-directories.json | 6 +- .../architecture/platform-runtime.json | 98 ++++++++++++++-- .../architecture/system-boundaries.json | 8 +- .../concepts/agent-runtime-and-assistant.json | 108 +++++++++++++++++- .../concepts/persistence-and-secrets.json | 8 +- openwiki/.claims/quickstart.json | 8 +- .../workflows/ssh-session-lifecycle.json | 6 +- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 80 ++++++------- openwiki/architecture/desktop-ui.md | 2 +- .../architecture/engineering-invariants.md | 31 ++++- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 29 ++++- openwiki/architecture/system-boundaries.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 25 +++- openwiki/concepts/persistence-and-secrets.md | 2 +- openwiki/quickstart.md | 2 +- openwiki/workflows/ssh-session-lifecycle.md | 2 +- 22 files changed, 414 insertions(+), 99 deletions(-) diff --git a/openwiki/.claims/architecture/desktop-ui.json b/openwiki/.claims/architecture/desktop-ui.json index 1bbdfeaf..c7e979f8 100644 --- a/openwiki/.claims/architecture/desktop-ui.json +++ b/openwiki/.claims/architecture/desktop-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:a6cde418a9982f9a4a6cd86e4848c36ef88b6ce715d11715d06b6a50f9fef8d7", + "pageVersion": "sha256:fb2e8faf240307b667e408bf1a3ffe425899dcf6d34f9dae8cccab4e30a70c88", "claims": [ { "id": "claim_5a53c5a47b7445f7a7b6f8865297a805", @@ -8,11 +8,11 @@ "evidence": [ { "resource": "repo://TFM/App/RootView.swift#L189-L212", - "version": "repo-lines-v1:sha256:d71f166db375946ef90321a74b2b3943f9137b43d68003f6991b1c9c56dbc5e9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmNTg3OGE2YmZmYTQ4OTg5M2U1ZDljYjAzNjU1Y2QyMzBkNTNjODI1MzNlNmM1MzllNjcyMzVjN2ZhOTQ3NDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3NzRiNTdmMDI4YzVkNTlhYTZiNWJjMzJhN2Y3ODhiY2NlZTk3Y2QzYzRmNGJmZjJlNjRmNjI0MjVlMGYwYWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQwODBmODZmZDI2OGM5MDgyMDQzM2EwYTU1Zjk5MWFhM2M3NWMyZmRmNjc0NTRkYzA0MDgwNzUwNjk0ZjM2MzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVmZjY1OGMwNzA0YjlkODZkZWVjOTc1M2EyODI3YWM3YzJlYTE5ZmRjMGQwYWU5ZDBiM2YyMzdmMzZhN2M5ZWIifQ" + "version": "repo-lines-v1:sha256:e77fcfd602c9ed5c3d3a216bc9d676c95f8d620a7f9303c6973384587336ef03:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0YzgyNjI5YzkzNzdhMTFmYTA4NDQwNGYxNTJjZDM4OTEzOGYwYTBjMTRiNjlkYjI4MDIyMzY2NTIyMTVhOTQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQwMWVkMmU3NTI2MWMzMjhhOTUzYzI4NTAwNjUxNDJhZmEzZjk2ZDBhYTY2MjYzN2IzZTlhMWQzZDA1YzQyMTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk3MWQ2ZGNmZjA2MDU3MWJiN2U1MGEwNWY5YmRkZGJhZDgwZTk3ZjY4NmU2ZjJkNzhlNGNmY2NjNjdhOGJmNWIifQ" }, { - "resource": "repo://TFM/App/TermindApp.swift#L150-L194", - "version": "repo-lines-v1:sha256:00c8269a3e30e0a6c21609b9611182d7db6ea597b5034e06084b9777a951d26b:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2YzczNjhlYWI2MWJhOGIxYWJlYTEwZmIxMTE3OTIzZjk5ODA2NjU4ZDFiNjcyODkyMjU5ODAxOTIxYWI0N2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" + "resource": "repo://TFM/App/TermindApp.swift#L166-L211", + "version": "repo-lines-v1:sha256:113e8755323a192725241af71c9310d03a5b1e3f42973265e2d01c758a025d35:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiOWVlODU3NGEyYzJmNTVjNmE5NjdmNmFhM2Q1NzM0NGFiZmFmNzgyZjMzZTBkMDk1YzYwNTdkZGMzNjdhOTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUyOWM5Nzc5MTJhZTRkOWI4ZGJlYzYyMDU4YTQyNzk4ZTc3Yjk4MGI4NjdhZjE5Y2MzNTEyMjc3YTMxNDdjZGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0OGM3OGRiZTdjN2M0ZGNkNjNiYTQ1NDhkZWMzYmFmNGM3N2E4YWU3YzI0MjVhMGI2YjYxYjIwYTNjNzc0MmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" } ] }, @@ -251,6 +251,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index a3360c44..faf78a73 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:24879797730fe459acba1497911aff236d56a55e1ff25ed0b17487461df1506a", + "pageVersion": "sha256:ced917378133d346e25f9793e04d360a19ff15bd567435b4183562c3dc9fab09", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -52,7 +52,7 @@ }, { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:5284fc38208bb02af017031469b91288fedd0383d59f391622aa4c32a8450f42" + "version": "repo-file-v1:sha256:0029784efe4588e87c3dc18317fdfaad46bed6b3a32bcf72dd0dd9b1c9da256e" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", @@ -455,10 +455,74 @@ "version": "repo-lines-v1:sha256:9c7f365e523b2d28fcfa76ef2f04bc330d1eaad9369561d1ac24393cc8a1a9e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NjIxN2JhNDkzNWU3Y2YwNDllYWNmYjNiNmQ5YWE3M2ExYzkzNjFlNDdmNGJlMDljODVlZTJhNTQ0Zjk2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWUyNTFjZGNkY2ZlNTFlNGY1M2E5ZGZhMjQxMjM4ZGNmZGNiNmNiYTdhNDEwZDdmOWJkMjY3NTg0ZGFlZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmNGJhMmVjOTU2MDNlZThiOWFlZmZiYmQ3MzY1NzEyOGNlYmJmNWNmYmFlZDczZmY5ZmEzZTI0ZTI0MzhjMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZWE3MTMwOWQ1YjQzYjZiZTIyZTQyZmIyOWFiNjAwYTIxZDY0ODcyOWVjMTIzYjk0MTVjOTA2MzJlMDQ3M2YifQ" } ] + }, + { + "id": "claim_8f03e675a7894b24a4289e7206079329", + "statement": "Desktop termination fences controller-authorized Assistant admissions, synchronously cancels captured preparation and all app-wide stored conversations, and joins preparation/failure-save attempts and retained runtime consumers with MCP/SFTP before closing Agent background commands and then connections; repeated shutdown returns the same task.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift#L4-L18", + "version": "repo-lines-v1:sha256:45fa98cb6369c0d5e24d7cd83156c377234b36dea574274c08357935974ed454:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyYjVhZDkxNWY4ZThmM2Y3MzMxZDRlMzk2Mzk4OGU5MzRhNzViZDIyODVjMDA2ZWFjMTVlM2Y3MGRhZjQ1ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0NWExOGI2NjVjOTRlNzFjOWQ5Y2VmOWQ3YTU5NmZmMzQ2ZmY0Y2FkM2E2ODU2YjNjZGNkZGNiZjY0MTI3YzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgyMmNlYzE5ZWY2MmE5MzE0YzM3MjM0YjA3NWEyNjY0MThlMDFkMzU3OTJjYzNiZWVhNzdkNjI1YmIzNTE0ZTUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2241-L2257", + "version": "repo-lines-v1:sha256:10265098d3b40def1fdadf83d0e0a2ecebe2e14e921219ac83bbeb31eae12afe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NGMwNjM0YWI1Nzk0YTg2OTJlN2U2ZDI0M2FhYzEyYmEyOTM2ZDc2ODY5NTdkZDcxYWI0NGUzNDYyNzc1OGUifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L397-L405", + "version": "repo-lines-v1:sha256:c0399ffe32b3df50115f6baf147b0e3ea783b32ea6ed124bb7bd80b19745daeb:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWVhY2E1YTY3ZDBiMzZmMDAxZjkwMGYzNmMwYTk0NWY0ZTg5ZmJmOTZmY2FmNGIxZTcyYzkzNzFlODIzMTM4YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYzZiNTlkMjQ1NjM2YmE0MGE2MjIwYjk5OGQ3Yjg4ODk0YWMyZjA5ZGIwNTNiMjE2OTIxZjU3MDE0MTc4NWJkZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDI3MTc2MDM2Y2U1MWQxODMxODBjYjdlMDRlMWM3YWY1NGQzMGI4NGI1ZTUwYjgzZjIzNTZhNjM1YTBhM2M2MyJ9" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L12-L159", + "version": "repo-lines-v1:sha256:434861a84b6d70d9f19c8f80c5101a32e22b8aa19322db6805f1b182c8bb7982:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIzN2NiYWQyODQ3MTI3N2MwODJhNDYyNDkzNWQyYmRjZmVhZjdlOTJmYjY0MzhjMjcxZjRjNGNiY2JkZDQ2YTYxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5NjYyM2QxMTVkMDFmNmYzYjViYWQxOTU4MDgzZGJmNGNlNDllYTQyMWU2Njg2ZjU4ZTg0OWZlOTU4Yjc4ZmJlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmM2IyODMwZWVmYjVlOGNmMDhhMmZlMDNhYmNjNzIxODUyNTA5YzM5MTViNjAxMjRkNWFkMWRkNmUwODE1MjhkIn0" + } + ] + }, + { + "id": "claim_0041eaa146234943af67a02b8797e991", + "statement": "The macOS termination reply independently falls back after three seconds and replies once; captured-turn joins do not guarantee save success, SSH setup completion, remote-command settlement, or drainage of manual compaction, title generation and memory indexing, and successful terminal saves may schedule memory indexing after suspension.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", + "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", + "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", + "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", + "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", + "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", + "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L215-L244", + "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:42:01.096Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 69b44247..3e673f61 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:bca342add2ba08c7d23d8e2249dbd1fc90d80da4cd53969a03f04507a81c9e8e", + "pageVersion": "sha256:eebf0a77f06515f063a173fdf53ac96cca81aeae0519a99c3c2697d94c59429d", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -40,7 +40,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", @@ -144,13 +144,13 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index a111028b..ac96cbca 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:46bd31b52ab67eba8321c1885f18fb3436a08178dbccd92f5b6405559b9c3212", + "pageVersion": "sha256:7b943e2d155437d61aafb7681db058ef94efb0afbdab534a552eeaee29dc8413", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -16,7 +16,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index 6eaf31d8..9f794aae 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:11fb1a6bc89cdf3a069ac0842a06a6b2f060dff04e708d76584daa857818c42c", + "pageVersion": "sha256:ed3f410b516491c6b31b18037bf8c9a0274dc08a50a13080e5d7906c041da1af", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -30,7 +30,7 @@ "evidence": [ { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] }, @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFM/App/AppModel.swift", - "version": "repo-file-v1:sha256:5284fc38208bb02af017031469b91288fedd0383d59f391622aa4c32a8450f42" + "version": "repo-file-v1:sha256:0029784efe4588e87c3dc18317fdfaad46bed6b3a32bcf72dd0dd9b1c9da256e" }, { "resource": "repo://TFM/App/RootView.swift", @@ -78,15 +78,27 @@ }, { "id": "claim_5365b20c970945918fcbd2cc865bc5c6", - "statement": "macOS termination asks the user before quitting with pending external edits, then performs idempotent app-wide cleanup that closes windows and cancels/stops runtime services; the AppKit delegate replies when cleanup finishes or after a three-second fallback.", + "statement": "macOS termination checks pending external edits, then fences new controller-authorized Assistant work, synchronously cancels captured preparations and every app-wide stored conversation, and joins their preparation/failure-save attempts and retained consumers with MCP/SFTP before closing Agent background commands then connections; repeated shutdown returns one task and the native delegate replies once on cleanup completion or a three-second fallback.", "evidence": [ { - "resource": "repo://TFM/App/AppModel.swift#L669-L693", - "version": "repo-lines-v1:sha256:3fdd3e3a61b4f0fac27a94144beb60ee81bea2eb973de02975ed94e17607bcf9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYzNmM5MGE0M2ZiOWQ0NDg2NzI0NmE0MzY5ODk4YWZkOTFiOWUyYjM0OTZmMjU3OTg1YTk0MDFhZjY4ZDI1YWYifQ" + "resource": "repo://TFM/App/AppModel.swift#L397-L405", + "version": "repo-lines-v1:sha256:c0399ffe32b3df50115f6baf147b0e3ea783b32ea6ed124bb7bd80b19745daeb:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWVhY2E1YTY3ZDBiMzZmMDAxZjkwMGYzNmMwYTk0NWY0ZTg5ZmJmOTZmY2FmNGIxZTcyYzkzNzFlODIzMTM4YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYzZiNTlkMjQ1NjM2YmE0MGE2MjIwYjk5OGQ3Yjg4ODk0YWMyZjA5ZGIwNTNiMjE2OTIxZjU3MDE0MTc4NWJkZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDI3MTc2MDM2Y2U1MWQxODMxODBjYjdlMDRlMWM3YWY1NGQzMGI4NGI1ZTUwYjgzZjIzNTZhNjM1YTBhM2M2MyJ9" }, { - "resource": "repo://TFM/App/TermindApp.swift#L331-L380", - "version": "repo-lines-v1:sha256:dba035b03cf3758c5dd56a198c82d72e009987583daf5432a41a80018be9c9f2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMmJlMTBlOWQxYjNjMGE2OTYxNzZmMGYyMDQ3MGVhODMzN2FmYzI4MWZmMTIzYmNmMmRmMjkzMTNkMDIwMTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg2MmZlMmQ0YmFjM2JhZjAxN2NiYmY5MmFiMTcwZTQ0NWExMWQxNzYyODBkZDEyOTdjZmM2ZDQxZjI2Y2NlMGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4NWYxZjhkMWY1ODkwZmNjMTE1ZWMwNWRlNGU3NzA1ODdiOWRjNDlkZDhlM2IxNTViYmUwYzI4NWI2ZWQyYmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdlMjAyMDc0MmMzM2IxYjkxZWIyMjYxNmJmYTg4NWMxNjQ2NThlM2Y5YzAzZDNkODZhYzllMjgwMGRkYmNhMzcifQ" + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L215-L244", + "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L383-L422", + "version": "repo-lines-v1:sha256:3584a07a98a3152e8413b2ca3dab7ec5452440020bc87aa0c1a67dad6ee6df69:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMmJlMTBlOWQxYjNjMGE2OTYxNzZmMGYyMDQ3MGVhODMzN2FmYzI4MWZmMTIzYmNmMmRmMjkzMTNkMDIwMTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4NWYxZjhkMWY1ODkwZmNjMTE1ZWMwNWRlNGU3NzA1ODdiOWRjNDlkZDhlM2IxNTViYmUwYzI4NWI2ZWQyYmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiMTg2M2Q4Njg3OTcxYTY0ZjViOTdkMmI1YTMzNmYyYWE0NTIzYWU3MjU2MDIxOTg2YTQ2MjdlY2ViMTUxMzEifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L12-L204", + "version": "repo-lines-v1:sha256:33bc27e8e9dc5011f4f095da99d1605358022b9a832de6a30795ea654b62d8d8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTkzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0Y2YwOWViMGM1NzQ0ZmI5NzMzMWI5OGZmM2ZiZjA1YTYwMmJhM2Y3ZjY3ZTVkYTViZTkwOTUwMzNmNjI1ODI1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJmYzhiYmY2YTYyM2E5YjQ0YzA4NjhmNDU1OWIyOTNmNjg3MGVkMjMzMjEzMWFlZDEzMjFmODJhMzFkYjdiN2JkIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5NjYyM2QxMTVkMDFmNmYzYjViYWQxOTU4MDgzZGJmNGNlNDllYTQyMWU2Njg2ZjU4ZTg0OWZlOTU4Yjc4ZmJlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI1ZDE1NWVjZmM0ZTZkYWQxOTU5YjY1OTViMjI5MjdjZDc1NjEwZWQ2ZTNlYzQ3NzZiMTE3OGQxODI2M2M2Nzk0In0" } ] }, @@ -293,10 +305,78 @@ "version": "repo-lines-v1:sha256:9c7f365e523b2d28fcfa76ef2f04bc330d1eaad9369561d1ac24393cc8a1a9e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4NjIxN2JhNDkzNWU3Y2YwNDllYWNmYjNiNmQ5YWE3M2ExYzkzNjFlNDdmNGJlMDljODVlZTJhNTQ0Zjk2ZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzZWUyNTFjZGNkY2ZlNTFlNGY1M2E5ZGZhMjQxMjM4ZGNmZGNiNmNiYTdhNDEwZDdmOWJkMjY3NTg0ZGFlZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhmNGJhMmVjOTU2MDNlZThiOWFlZmZiYmQ3MzY1NzEyOGNlYmJmNWNmYmFlZDczZmY5ZmEzZTI0ZTI0MzhjMDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZWE3MTMwOWQ1YjQzYjZiZTIyZTQyZmIyOWFiNjAwYTIxZDY0ODcyOWVjMTIzYjk0MTVjOTA2MzJlMDQ3M2YifQ" } ] + }, + { + "id": "claim_fb96ba2d427a4df0a3d741d0c6bcbb1d", + "statement": "Desktop stored Assistant conversations are app-owned across full-screen/sidebar surfaces and survive window unregistration; shutdown drains retained queued, active and already-stopped runtime owners rather than relying on the live-window registry.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift#L4-L18", + "version": "repo-lines-v1:sha256:45fa98cb6369c0d5e24d7cd83156c377234b36dea574274c08357935974ed454:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyYjVhZDkxNWY4ZThmM2Y3MzMxZDRlMzk2Mzk4OGU5MzRhNzViZDIyODVjMDA2ZWFjMTVlM2Y3MGRhZjQ1ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0NWExOGI2NjVjOTRlNzFjOWQ5Y2VmOWQ3YTU5NmZmMzQ2ZmY0Y2FkM2E2ODU2YjNjZGNkZGNiZjY0MTI3YzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgyMmNlYzE5ZWY2MmE5MzE0YzM3MjM0YjA3NWEyNjY0MThlMDFkMzU3OTJjYzNiZWVhNzdkNjI1YmIzNTE0ZTUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2241-L2257", + "version": "repo-lines-v1:sha256:10265098d3b40def1fdadf83d0e0a2ecebe2e14e921219ac83bbeb31eae12afe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NGMwNjM0YWI1Nzk0YTg2OTJlN2U2ZDI0M2FhYzEyYmEyOTM2ZDc2ODY5NTdkZDcxYWI0NGUzNDYyNzc1OGUifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L495-L511", + "version": "repo-lines-v1:sha256:ae35dbc3e8aaf3f5da0d2b62d5e152fb56bc95a4be9df6357df9d6f44f62cc92:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NmNiNzJjNThmMjRjYzg3ZjhkYzk0NmY1NmU5YmVhODA2OGNlNTcyNDlhN2MyNTc5NmEzMDhkM2FiODMyNjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmNjVmNGQzYWQ2MjM0MTlhNThkMTYyM2FhM2RiNTcyM2RjNjcxYWU2Yjc1NDA4MzRjZTYwYTk4NzhhZGZiMTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRmOWQzYjMxYzE4NzkzOTU1NDRjOTRjYjliMjc0NGM2MTk0ZGQ4NTUzMjZjMDc0N2JlZmFjMWFmODgxMWFhMTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkMDA5YWViYmJkYzAxMzQ5MDcwNmRkMjJkODI0ZjIwN2UxODVhNWFhZWZjYTRiODdlODRlNjRlN2ZmYWFlOGYifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L12-L64", + "version": "repo-lines-v1:sha256:014f6ff91401778bcf5ebb85de93c18988980dd39576e39fff70e4d83f663ac1:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5NjFkZmMzMDA5YjdkMmMxODRkYmE3MmM3NDcyN2E3Nzc0ZjQ3MGVhZTU4NWRjYzcxNzk3ZTc4NWIzYWJmMGMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2NjIzZDExNWQwMWY2ZjNiNWJhZDE5NTgwODNkYmY0Y2U0OWVhNDIxZTY2ODZmNThlODQ5ZmU5NThiNzhmYmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3YjNiOWQ0M2E5Mjc5MDI4Mjc3OTJkYjYzYjY4Y2M3NzFmZWY4NzA1M2ViZmUxMDQ1MmI3NDk1ZGY5YjZjOWQifQ" + } + ] + }, + { + "id": "claim_d0076b157c24486fadd4f8c45203ef7f", + "statement": "The desktop captured-turn drain is best effort: a joined save attempt may fail, shared SSH authentication/pool setup may not settle from runtime cancellation, background-command close only requests cancellation, and existing compaction/title/index tasks are not joined; successful terminal saves can schedule indexing after memory suspension, while fallback can reply before later cleanup runs.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L784-L820", + "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", + "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", + "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", + "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", + "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", + "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", + "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L215-L244", + "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:42:01.096Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index ca80b087..cb4c6b08 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:b01cc417a1a0c5a773b8d9629cd44db7d365627da3cd7e699150cd184d1cb82f", + "pageVersion": "sha256:2552fa8187812755085075681b5a5148ef62a16224616146ec131bd1fb880d64", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -74,7 +74,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] }, @@ -180,7 +180,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" }, { "resource": "repo://TFM/Platform/MacDeviceName.swift#L5-L29", @@ -191,6 +191,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index e655a3af..c33af32a 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3401ab14c4112a9f5b713fed880da0ba98d8262566bc33d5e1f8288d540d7835", + "pageVersion": "sha256:ee7c8c1d70af9d78c4effdc07f9f5b65fb08c92a14abee40c88be00a9df41c87", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -941,10 +941,114 @@ "version": "repo-lines-v1:sha256:ea3406fa5773e70a02548ed6337f82053c4e9639713f34e7042ec70b11e2603d:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzMGE3YjMwNzMwMWQ5Yzk4MWZhNjFmMjJkNDhkMTE3NmQzMjBhZjdkYmM3MTJmOTk4NzM1ZDc1NGE2ZTJkN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0YmUwZTMwOGMxYjUwMzI5MGNmOGNkYjQwM2Q0ZTFmYzQwYzFmOTk0MzdjNmRmNTk4ZjZiYjlkOWYwYzMxYWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" } ] + }, + { + "id": "claim_66dd42358f2c4964b25e33023f22b0c0", + "statement": "Desktop shutdown permanently fences new controller-authorized draft, text, first-conversation, edit, plan, explicit-start and compaction entries and synchronously invalidates previously captured edits before their deferred history task can run.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L232-L298", + "version": "repo-lines-v1:sha256:bf92f4e7bde34f1968a6d2c9ac5b9e0254338266f00d10a06a3390339bd4290d:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1YmZlZjAzZjI2N2RkZmVkZjAxNDc1YzljYTZhMTg1ZDQ4NWY2ODkwOTE0YjMyNWRmMDFlZTA4NGExOWZlMWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4MDE4NTc2MjI1ZGM2M2YzYjQ4ZTc5NjA4OTVmYzFiMTVhNTAzMjc0MWQ2NWI3ZmVhOWE3NWQ5ZmZkYTljMTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdhMjEzN2YyMzlhMGIzMTEzYWQ4MzY5NjhhMjA5YzliYWQzMmFiZTIzZTdjYTg0MzJiZThiMzU0NWM2MmVmNTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM1NGNkNTFmNDEyYTA2ODdiNTBmODEwZjg1ZDE3NmUzNmM4ZGZjNjBlZDg1NWFkMDFmMWIwZTc5ZjMyYjI4YmMifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L703-L741", + "version": "repo-lines-v1:sha256:7755471e6bc957d91341065eb4d25afdab483226e00ea68ad3ac19b3f1a0367b:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3Y2E2MTcyMWRmMDEyN2QyOGQ1YmI2ODZlZGRlZGM5Y2YyMzZkMDFjOWNhOGRkYTdiOTA5ZjNkOTBiYWJiNTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI2ZWUwYjQ0MThlZjBiODQ0ZmNmM2NiY2U4Yjk1MjliNjdjNzFkMjM2MzFmZDI2NWU0Y2ZiMGIwNzE1NWU2YmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZjNWNlYzU4NzU4MTkxZjNkNDUyN2E2ZTVlMWUzYTlhOTQxOWU4OTQ4YjViOWFlN2RlNzIyYTM1NTIyZTM5MTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFhMzAxM2JkNmVhZDYzMzk2ZTg4NzdjZGExZjZhMGRhMmI4YTE0YTczYWZkZGEwMzRmODgyMmViYmYzY2ZkNGYifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L397-L405", + "version": "repo-lines-v1:sha256:c0399ffe32b3df50115f6baf147b0e3ea783b32ea6ed124bb7bd80b19745daeb:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWVhY2E1YTY3ZDBiMzZmMDAxZjkwMGYzNmMwYTk0NWY0ZTg5ZmJmOTZmY2FmNGIxZTcyYzkzNzFlODIzMTM4YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYzZiNTlkMjQ1NjM2YmE0MGE2MjIwYjk5OGQ3Yjg4ODk0YWMyZjA5ZGIwNTNiMjE2OTIxZjU3MDE0MTc4NWJkZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDI3MTc2MDM2Y2U1MWQxODMxODBjYjdlMDRlMWM3YWY1NGQzMGI4NGI1ZTUwYjgzZjIzNTZhNjM1YTBhM2M2MyJ9" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L733", + "version": "repo-lines-v1:sha256:10147de063bdd0d8ee92d9105374f28075edea1653df262c72c8ed0e7d12cb80:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZlYzAyYjdkMjBjNTBhOWI4ZWRjNzllZWFiOWMxM2QzNDI5MTBhNDBkODY1ZTNmZGY2MzY3ODE4NDhlMjNjYTciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzOGQyNThmZmIxN2E4YmFjMGI2MjIyZWRlYzEzYjA2ZDUyZTE3NWQyZTUwNjgyZDI5MTc3NjRhNDdhMjUzYmEifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L67-L129", + "version": "repo-lines-v1:sha256:7cda1e2c2d2729ad5bb266903aef36bcf8c8002d192f4fbd0c9059692d68f0a9:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhNGFlZDcyNGNiNDRhZWEwNzgwMmYyMWQxNmU2NTdhMzFmNTAyY2FiYmEwNGQ0NzMwN2RhY2UzMGVmYjkyZjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiM2Q0YmFhNjBhMDM1MjM0NGIxODkxNTczODQyNWE3NThjN2Y4Y2JiNWEyYjBkNzk1OWJlZDM1OTQ0ZmFiNzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAifQ" + } + ] + }, + { + "id": "claim_60eec50080114cc1b0d5202def03f77c", + "statement": "Desktop termination snapshots the app-wide conversation store and requests cancellation for all captured preparations and stored conversations before awaiting; it joins the separate preparation/failure-save task and retained conversation consumers alongside MCP/SFTP before closing Agent background commands then connections, and reuses one cleanup task across repeated calls.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L82-L97", + "version": "repo-lines-v1:sha256:04c44840f274985def67d41dcc36634837dbff491f98da6a5137fa3a0f7a4a0e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxOTY1OWM4ZDM4MWNhY2NmMDg0NjZiNzc4NTk4M2E4Zjc2OWE1MjU0MjA5ODYyODA4ZWRhNTkzNDQ2ZTIzYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmM2MyYmU5Yzg4ODZjMmM1YzI4MjYzOTk4MTBmZTYyMDY0YWZhZWEyN2RmMTNmNzQ5OGI4M2QyMmVlODBjODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc4ZDNiMjYwYjYyNDBkODViZDgwMjNhZmRjMTFkMzc0ZjZlOTAxYzA4YjY1NzBkYzM3YzU0OWUwNTBmNWE5MmIifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift#L4-L18", + "version": "repo-lines-v1:sha256:45fa98cb6369c0d5e24d7cd83156c377234b36dea574274c08357935974ed454:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyYjVhZDkxNWY4ZThmM2Y3MzMxZDRlMzk2Mzk4OGU5MzRhNzViZDIyODVjMDA2ZWFjMTVlM2Y3MGRhZjQ1ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0NWExOGI2NjVjOTRlNzFjOWQ5Y2VmOWQ3YTU5NmZmMzQ2ZmY0Y2FkM2E2ODU2YjNjZGNkZGNiZjY0MTI3YzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgyMmNlYzE5ZWY2MmE5MzE0YzM3MjM0YjA3NWEyNjY0MThlMDFkMzU3OTJjYzNiZWVhNzdkNjI1YmIzNTE0ZTUifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/Agent/ConversationModel.swift#L2241-L2257", + "version": "repo-lines-v1:sha256:10265098d3b40def1fdadf83d0e0a2ecebe2e14e921219ac83bbeb31eae12afe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZmM0MzNjZDM1OTM3MTAxZjgyZTM2MzI0MGZiMDkzMWJlMjZkYTcwYjg1MjRlMDEwYjVjMzlkNzdkNWYyNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4MDU1NTIyN2IxNGM1ZmEwYTQ0NTA1NjhmM2U0N2RmNzAxMDg1NjFlZDU4NjM3ZTZiODlkZDc5ZmVhOGE3NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NGMwNjM0YWI1Nzk0YTg2OTJlN2U2ZDI0M2FhYzEyYmEyOTM2ZDc2ODY5NTdkZDcxYWI0NGUzNDYyNzc1OGUifQ" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L12-L64", + "version": "repo-lines-v1:sha256:014f6ff91401778bcf5ebb85de93c18988980dd39576e39fff70e4d83f663ac1:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5NjFkZmMzMDA5YjdkMmMxODRkYmE3MmM3NDcyN2E3Nzc0ZjQ3MGVhZTU4NWRjYzcxNzk3ZTc4NWIzYWJmMGMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2NjIzZDExNWQwMWY2ZjNiNWJhZDE5NTgwODNkYmY0Y2U0OWVhNDIxZTY2ODZmNThlODQ5ZmU5NThiNzhmYmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3YjNiOWQ0M2E5Mjc5MDI4Mjc3OTJkYjYzYjY4Y2M3NzFmZWY4NzA1M2ViZmUxMDQ1MmI3NDk1ZGY5YjZjOWQifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L132-L159", + "version": "repo-lines-v1:sha256:e4f5146a63708a4219c1ba5b9c134b099692cc16c4a034957baa2ce640830ad5:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3Y2JhZDI4NDcxMjc3YzA4MmE0NjI0OTM1ZDJiZGNmZWFmN2U5MmZiNjQzOGMyNzFmNGM0Y2JjYmRkNDZhNjEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZmZlMDJiYzc1MDBjNzA1OTkwMDdmNWUwMDkyZTliYTFjZmNkNzcxYWVlY2NhOTk2YTM0ODhkOTZjNDQ2MmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYzYjI4MzBlZWZiNWU4Y2YwOGEyZmUwM2FiY2M3MjE4NTI1MDljMzkxNWI2MDEyNGQ1YWQxZGQ2ZTA4MTUyOGQifQ" + } + ] + }, + { + "id": "claim_750079dc18ec48c1be4abb0842993534", + "statement": "An independent three-second macOS reply fallback can end quit waiting before save/resource cleanup finishes; shared unstructured SSH authentication/pool tasks may not settle on runtime cancellation and background-command close only requests cancellation.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", + "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", + "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", + "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" + }, + { + "resource": "repo://TFM/App/TermindApp.swift#L215-L244", + "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" + }, + { + "resource": "repo://TFMTests/AppTerminationTests.swift#L162-L204", + "version": "repo-lines-v1:sha256:bddb02cc604e5d5d1abba86692a8b4ad945cd272bd56d954871b6219d4bf3597:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZjOGJiZjZhNjIzYTliNDRjMDg2OGY0NTU5YjI5M2Y2ODcwZWQyMzMyMTMxYWVkMTMyMWY4MmEzMWRiN2I3YmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjNzM4N2E1ODUyMDVkYzBlZjVjOGRlMzQ3ZTMwNmExZDIwZjRkMTY0MjA1M2Y0MzhiMDM0MGNlYWRiZWQ3YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVkMTU1ZWNmYzRlNmRhZDE5NTliNjU5NWIyMjkyN2NkNzU2MTBlZDZlM2VjNDc3NmIxMTc4ZDE4MjYzYzY3OTQifQ" + } + ] + }, + { + "id": "claim_1dc38eea858b41789f6c9b273549ec5b", + "statement": "Captured-turn termination joins exclude existing manual compaction, title generation and memory indexing; MemoryModel.suspend cancels current tasks, but successful terminal repository saves invoke historyChanged and can start indexing again.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", + "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", + "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", + "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + }, + { + "resource": "repo://TFM/App/AppModel.swift#L724-L777", + "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:42:01.096Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index 922b7d09..3831d6de 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:9f1996861cee13d1379365982ac5f68c358ecf77f48759dfd86c90246c8c1b1a", + "pageVersion": "sha256:2c7b5e94e1ecc8ef99eb9c4ea83b9ffb4420e8a9229b17a20f93376b48e8633f", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", @@ -16,7 +16,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] }, @@ -30,7 +30,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] }, @@ -457,6 +457,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index f1c1f8ae..1f59b1a5 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:dfca6fed3c974476cc25cd9f575515138b3897864e77e682e4f791d86f56756e", + "pageVersion": "sha256:255121c2ef0130528b47855870bb8f42015ef543c8d3f140d6f7673f0051a3bb", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -34,7 +34,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", @@ -48,7 +48,7 @@ "evidence": [ { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" } ] }, @@ -143,6 +143,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:54:45.592Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.claims/workflows/ssh-session-lifecycle.json b/openwiki/.claims/workflows/ssh-session-lifecycle.json index babc957f..77834b44 100644 --- a/openwiki/.claims/workflows/ssh-session-lifecycle.json +++ b/openwiki/.claims/workflows/ssh-session-lifecycle.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:d57d02019bbac973232c7889a9cd1d1192b0c70a6dd91c12a3a867b578b6fee0", + "pageVersion": "sha256:c2af67d394cef803d994353ab5c3de83d224bf76ea40f58ab3cc274a8ec6329b", "claims": [ { "id": "claim_69a9de4ce918417c8a44c6c87c9f6564", @@ -142,7 +142,7 @@ }, { "resource": "repo://TFM/App/TermindApp.swift", - "version": "repo-file-v1:sha256:4bc8de73959fb7b78ab29e7936fbeab9a5f13a0e0f7555d520d99ff2bd731cec" + "version": "repo-file-v1:sha256:721bea277227a69f102c1b505cf41245ddf5d568b96d6ff229cb440e53106ac0" }, { "resource": "repo://TFM/Features/Workspaces/WindowModel.swift", @@ -175,6 +175,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T05:17:08.701Z" + "at": "2026-09-23T08:21:59.182Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index 31077290..d63103f4 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T07:43:18.006Z", + "updatedAt": "2026-09-23T08:26:51.160Z", "command": "update", - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index fed5d5fa..c3638c44 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,18 +2,18 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:a6cde418a9982f9a4a6cd86e4848c36ef88b6ce715d11715d06b6a50f9fef8d7", + "pageVersion": "sha256:fb2e8faf240307b667e408bf1a3ffe425899dcf6d34f9dae8cccab4e30a70c88", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:24879797730fe459acba1497911aff236d56a55e1ff25ed0b17487461df1506a", + "pageVersion": "sha256:ced917378133d346e25f9793e04d360a19ff15bd567435b4183562c3dc9fab09", "completedBy": "codex", - "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/architecture/mobile-ui.md": { "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", @@ -23,25 +23,25 @@ "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" }, "/openwiki/architecture/module-map.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:bca342add2ba08c7d23d8e2249dbd1fc90d80da4cd53969a03f04507a81c9e8e", + "pageVersion": "sha256:eebf0a77f06515f063a173fdf53ac96cca81aeae0519a99c3c2697d94c59429d", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/architecture/native-app-directories.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:46bd31b52ab67eba8321c1885f18fb3436a08178dbccd92f5b6405559b9c3212", + "pageVersion": "sha256:7b943e2d155437d61aafb7681db058ef94efb0afbdab534a552eeaee29dc8413", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/architecture/platform-runtime.md": { - "pageVersion": "sha256:11fb1a6bc89cdf3a069ac0842a06a6b2f060dff04e708d76584daa857818c42c", + "pageVersion": "sha256:ed3f410b516491c6b31b18037bf8c9a0274dc08a50a13080e5d7906c041da1af", "completedBy": "codex", - "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/architecture/shared-module-directories.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -58,18 +58,18 @@ "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64" }, "/openwiki/architecture/system-boundaries.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:b01cc417a1a0c5a773b8d9629cd44db7d365627da3cd7e699150cd184d1cb82f", + "pageVersion": "sha256:2552fa8187812755085075681b5a5148ef62a16224616146ec131bd1fb880d64", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "pageVersion": "sha256:3401ab14c4112a9f5b713fed880da0ba98d8262566bc33d5e1f8288d540d7835", + "pageVersion": "sha256:ee7c8c1d70af9d78c4effdc07f9f5b65fb08c92a14abee40c88be00a9df41c87", "completedBy": "codex", - "completedRunId": "2245f9b4-2dd4-47f8-a570-9d2d94c5dcf4", - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -79,11 +79,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/concepts/persistence-and-secrets.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:9f1996861cee13d1379365982ac5f68c358ecf77f48759dfd86c90246c8c1b1a", + "pageVersion": "sha256:2c7b5e94e1ecc8ef99eb9c4ea83b9ffb4420e8a9229b17a20f93376b48e8633f", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -156,11 +156,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/quickstart.md": { - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", - "pageVersion": "sha256:dfca6fed3c974476cc25cd9f575515138b3897864e77e682e4f791d86f56756e", + "pageVersion": "sha256:255121c2ef0130528b47855870bb8f42015ef543c8d3f140d6f7673f0051a3bb", "completedBy": "codex", - "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { - "gitHead": "82ba6dc050bf2e3f907717bba4dd30bb9198b6a3", - "sourceFingerprint": "sha256:f625b15b2999e260a4375e9254de0e5fc1e622bbc3d1daf39aad616d6ff28ccc", - "pageVersion": "sha256:d57d02019bbac973232c7889a9cd1d1192b0c70a6dd91c12a3a867b578b6fee0", + "pageVersion": "sha256:c2af67d394cef803d994353ab5c3de83d224bf76ea40f58ab3cc274a8ec6329b", "completedBy": "codex", - "completedRunId": "60f8c625-152f-4c3f-8755-bcc5d849e8d9" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" } } } diff --git a/openwiki/architecture/desktop-ui.md b/openwiki/architecture/desktop-ui.md index a799c6e5..ee6b3af5 100644 --- a/openwiki/architecture/desktop-ui.md +++ b/openwiki/architecture/desktop-ui.md @@ -5,7 +5,7 @@ description: "How the macOS app composes its native window shell, persistent wor tags: [macos, tfm, ui, navigation, workspaces, swiftui, appkit, design] verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T08:21:59.182Z sources: - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index 54746337..afed5594 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -10,14 +10,20 @@ sources: resource: repo://Frameworks/TermindApplication/Agent/AssistantConversationOperations.swift - id: openwiki-source-5f99e8af445e073ffe9bc257 resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift + - id: openwiki-source-160f180df5d3cb1457febfee + resource: repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift - id: openwiki-source-1c48f72c0c7a3164d7d5c715 resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift + - id: openwiki-source-2d98a2885dc21f508f49b749 + resource: repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift - id: openwiki-source-5c91b796f7e6d651aedf3023 resource: repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f resource: repo://Frameworks/TermindApplication/FeatureModels/ConnectionHistoryModel.swift - id: openwiki-source-e09fb7bc8e7ca0579f6d446e resource: repo://Frameworks/TermindApplication/FeatureModels/DeviceDirectoryModel.swift + - id: openwiki-source-7397a04f40a393c81b0ca52d + resource: repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift - id: openwiki-source-7ca30f0e781e8c2df826b47a resource: repo://Frameworks/TermindApplication/HistoryLimits.swift - id: openwiki-source-4f78037bc69b6dfc293aca3d @@ -52,6 +58,10 @@ sources: resource: repo://Frameworks/TermindPresentation/Terminal/TerminalThemePicker.swift - id: openwiki-source-87b90af0bd504e35083a8414 resource: repo://Frameworks/TermindPresentation/VaultSyncStatusLabel.swift + - id: openwiki-source-ee4e94c8b189e0881ec4fd15 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift + - id: openwiki-source-0c82bf45bc197f879a54fd3a + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift - id: openwiki-source-1c845e5af67c400566be83bd resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AssistantToolRegistry.swift - id: openwiki-source-d2d4c06f734241db3badc1f5 @@ -114,14 +124,16 @@ sources: resource: repo://TFM/Features/Workspaces/WindowModel.swift - id: openwiki-source-385c953ef1c6bf095d1c1244 resource: repo://TFM/Platform/TerminalFontCatalog.swift + - id: openwiki-source-73c75477db0674192248aa00 + resource: repo://TFMTests/AppTerminationTests.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } +generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:42:01.096Z + at: 2026-09-23T08:21:59.182Z --- # Cross-Cutting Engineering Invariants @@ -194,9 +206,20 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r - Desktop Vault/Assistant, Metrics, dynamic Workspaces and Settings routes are window-local. Settings data is app-wide; closing it restores the replaced destination. Width changes may adapt panels, never routes/editor identity/session composition/monitor ownership; covered panels retain last size, native table identity, sorting and scroll. -- `AssistantLibraryModel` owns standalone conversations, not panes. Workspaces own SSH/SFTP/serial panes and session - conversations; sessions own transports/terminal/browser state. Ordering/pinning commits metadata before publication +- Desktop `AssistantConversationStore` is app-wide; each window's `AssistantLibraryModel` navigates the same stored + conversation instances, including full-screen and sidebar surfaces. Closing a window does not retire those conversations. + Workspaces own SSH/SFTP/serial panes; sessions own transports/terminal/browser state. Ordering/pinning commits metadata before publication and preserves instances during runs/approvals/compaction. Archive/delete keep run guards; archived chats cannot move. +- Desktop termination permanently fences controller-authorized draft/text/new-conversation/edit/plan/start/compaction + admissions. It cancels captured preparation and stops every stored conversation synchronously before window teardown + or the first await, then joins the controller's preparation/failure-save task and retained conversation consumers alongside + MCP/SFTP cleanup. Only after those joins return does it close Agent background commands, then the connection pool. + Repeated shutdown returns the same cleanup task; an independent three-second native fallback replies once even if a save + or drain remains blocked. Joining an attempt does not prove save success or completion before process exit. + Shared unstructured SSH authentication/pool setup can outlive runtime cancellation. Existing manual compaction, titles and + memory indexing are outside these captured-turn joins; memory suspension cancels current tasks but successful terminal + saves can schedule indexing again. Background-command close requests cancellation without proving remote settlement. + See [Platform Runtime](/openwiki/architecture/platform-runtime.md) and `AppTerminationTests` for scope and verification. - Connection/authentication/forwarding state is memory-only, excluded from domain records, sync and UserDefaults. Serial is local/temporary, never persisted or an Agent host. Vault-synced Workspace templates contain named SSH/SFTP/empty split trees/fractions; only TFM materializes them, preserving empty panes for missing Hosts. diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index 0b23d773..8e1afee5 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T08:21:59.182Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 78810edc..8b26dde1 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T08:21:59.182Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index f48e0203..8eada4e8 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -4,6 +4,20 @@ title: Platform Runtime, App Composition, and Lifetimes description: How Termind composes shared services and persistence on macOS and iOS, and where app-wide, window-local, scene-local, and session state begin and end. tags: [architecture, runtime, macos, ios, lifecycle, dependency-injection] sources: + - id: openwiki-source-5f99e8af445e073ffe9bc257 + resource: repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift + - id: openwiki-source-160f180df5d3cb1457febfee + resource: repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift + - id: openwiki-source-1c48f72c0c7a3164d7d5c715 + resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift + - id: openwiki-source-2d98a2885dc21f508f49b749 + resource: repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift + - id: openwiki-source-7397a04f40a393c81b0ca52d + resource: repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift + - id: openwiki-source-ee4e94c8b189e0881ec4fd15 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift + - id: openwiki-source-0c82bf45bc197f879a54fd3a + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift - id: openwiki-source-cd0be716a2446029f345525a resource: repo://Termind.xcodeproj/project.pbxproj - id: openwiki-source-dc9295278cc250ed20567540 @@ -46,14 +60,16 @@ sources: resource: repo://TFM/Platform/MacDeviceName.swift - id: openwiki-source-22e92f96394f292a731fc6fb resource: repo://TFM/Platform/MainWindowCoordinator.swift + - id: openwiki-source-73c75477db0674192248aa00 + resource: repo://TFMTests/AppTerminationTests.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-531f6567ecf96fd4b0c5e97a resource: repo://TFMTests/ExternalConnectionURLTests.swift -generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } +generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:42:01.096Z + at: 2026-09-23T08:21:59.182Z --- # Platform Runtime, App Composition, and Lifetimes @@ -129,7 +145,13 @@ A host-opening action is centralized in `AppModel`, which chooses the requested The AppKit delegate starts native appearance/login handling and, outside tests, starts metrics activity and the metrics menu once `AppModel` is available. On app activation it refreshes both entitlement state and the current `DeviceRecord` display name from `MacDeviceName.current`; the synchronized directory can therefore update without rewriting connection events. TFI refreshes its name directly from DeviceKit's `Device.current.safeDescription` on active-scene transitions. On termination the desktop app first checks for pending external edits; after user confirmation, it saves window sizing and invokes `AppModel.shutdownForTermination()`. -Shutdown is deliberately idempotent and bounded by the delegate. It suspends memory and edit monitoring, stops metrics, speech, windows, SFTP work, external authentication, forwarding, and the outbox; concurrently awaits MCP and SFTP cancellation, then closes agent background commands and connection pools. The delegate replies to macOS when cleanup completes or after a three-second fallback, preventing an unbounded quit wait. +Shutdown first sets a permanent desktop Assistant admission fence. The execution controller's shared authorization callback rejects new draft/text sends, first-conversation promotion, edits, plan implementation, explicit starts and compaction requests. It snapshots the app-wide conversation store, cancels captured preparations, and calls `stop()` on every stored conversation synchronously before window teardown or any await. This includes full-screen and sidebar conversations whose window was already closed or unregistered: closing a window does not remove them from the store. + +The returned cleanup task separately joins the controller's captured preparation and failure-save attempts, plus each conversation's retained runtime consumers. The latter include queued, active and previously stopped owners still draining. Every cancellation request precedes these waits, so one blocked save cannot postpone stopping another conversation. MCP shutdown and SFTP cancellation run alongside the Assistant drains. Only after all those joins return does cleanup call `AgentBackgroundCommands.closeAll()`, then `AgentConnectionPool.closeAll()`. + +The app publishes that one cleanup task before suspending memory/edit monitoring and stopping metrics, speech, window-owned workspaces, SFTP work, external authentication, forwarding and the outbox. Repeated calls return the same task. `AppTerminationReplyCoordinator` independently races cleanup completion against a three-second fallback and replies to AppKit once. The fallback can permit exit while persistence or resource cleanup is still pending; returning from a save attempt does not establish that it succeeded. + +This is a captured-turn shutdown boundary, not a join of every Assistant task. Already-running manual compaction, title generation and memory indexing are outside it. `memory.suspend()` cancels current work, but a successful terminal save through `MemoryIndexedAgentRepository` can schedule indexing again. Shared unstructured SSH authentication and pool-opening tasks may not inherit runtime cancellation, leaving a consumer drain blocked and later resource closes unreached before fallback. Background-command close itself requests cancellation rather than proving remote settlement. These limitations make the native fallback necessary; the iOS lifecycle remains separate. ## Mobile composition @@ -202,6 +224,7 @@ For a service that represents navigation, focus, a sheet, an editor draft, a des Tests exercise the lifetime contracts rather than merely constructing views: +- `AppTerminationTests` uses queued conversations retained after window unregistration, a blocked terminal save, an unstored captured failure, and eligible post-fence sends/edits to verify cancellation, admission and save-attempt-before-resource-close ordering. An injected clock gate checks both cleanup-first and fallback-first replies occur once; these are application ordering tests, not proof of successful disk persistence or production SSH authentication settlement. - `MobileAppTests` verifies saved descriptors reopen on demand without replaying commands or paths, and that two scenes selecting one restored workspace create only one connection. - `MobileWorkspaceConnectionRecoveryTests` verifies background expiration disconnects affected mobile workspaces and foreground resumes them, while a quick foreground return preserves a healthy connection. Scene-removal fixtures also check that removing the sole active scene starts one lease for surviving background scenes and that an empty scene set starts none. - `MobileBackgroundControllerTests` checks synchronous callback/end ordering, stale and repeated callbacks, denied acquisition, and reentrant expiration during acquisition without losing or ending the replacement lease. diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index a7e22f3c..c8baf8e3 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T08:21:59.182Z --- # System Boundaries and Dependency Ownership diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index 40a4dbd1..57e6481a 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -16,6 +16,8 @@ sources: resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift - id: openwiki-source-b62626c07828480fcd2b71b7 resource: repo://Frameworks/TermindApplication/Agent/ConversationTitleController.swift + - id: openwiki-source-2d98a2885dc21f508f49b749 + resource: repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift - id: openwiki-source-76220f10b4e23e3c61a811be resource: repo://Frameworks/TermindApplication/Agent/TermindAgentPolicy.swift - id: openwiki-source-7397a04f40a393c81b0ca52d @@ -24,6 +26,8 @@ sources: resource: repo://Frameworks/TermindPersistence/GRDBMemoryStore.swift - id: openwiki-source-28d6772acb1a409d29ff1973 resource: repo://Frameworks/TermindPersistence/SwiftDataAgentRunRepository.swift + - id: openwiki-source-ee4e94c8b189e0881ec4fd15 + resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift - id: openwiki-source-1fc00d0407016efa5a0378eb resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBuiltInSSHTools.swift - id: openwiki-source-0c82bf45bc197f879a54fd3a @@ -60,6 +64,8 @@ sources: resource: repo://TFITests/MobileAssistantTests.swift - id: openwiki-source-4dbea0ff2808163cb4ecdd8f resource: repo://TFM/App/AppModel.swift + - id: openwiki-source-6570374eaac2e0278fa84640 + resource: repo://TFM/App/TermindApp.swift - id: openwiki-source-76b1cdca4a02038756187f92 resource: repo://TFM/Application/MCPServer/MCPServerTools.swift - id: openwiki-source-16db5d5459897d40fe080701 @@ -78,14 +84,16 @@ sources: resource: repo://TFMTests/AgentScratchTests.swift - id: openwiki-source-7e117096f381ecc32caff068 resource: repo://TFMTests/AgentSkillTests.swift + - id: openwiki-source-73c75477db0674192248aa00 + resource: repo://TFMTests/AppTerminationTests.swift - id: openwiki-source-78797a18dd3153d23a2c42e2 resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T07:42:01.096Z" } +generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:42:01.096Z + at: 2026-09-23T08:21:59.182Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -204,6 +212,18 @@ Once startup has begun, Stop requests runtime cancellation; the consumer checks The join guarantees that the captured snapshot-save attempt has returned, not that storage succeeded. In pinned AgentKit, terminal events precede the terminal snapshot save, while stream completion follows it; a terminal event alone is therefore an earlier boundary. The separate durable-event recorder and run-secret disposal tasks are not joined by this consumer. These contracts do not promise recovery from a storage failure or process death before a save. Desktop Stop keeps its immediate presentation behavior without adopting the mobile background lifecycle. +### Desktop termination ownership + +Desktop `AppModel.shutdownForTermination()` permanently fences the execution controller's authorization callback before cancellation. New draft/text sends, first-conversation promotion, prepared edits, plan implementation, explicit starts and compaction requests are refused at that boundary. A prepared edit accepted before shutdown is invalidated synchronously, so its later UI task cannot enter history truncation or runtime preparation. + +The app snapshots its `AssistantConversationStore`, separately requests controller-wide preparation cancellation, then stops every stored conversation before any await or window teardown. Full-screen and sidebar surfaces navigate that same store; an unregistered or closed window does not remove its conversations. Each conversation receives a drain task, and the app also retains the controller cancellation task so an accepted preparation outside the store is joined. Ordinary preparations finish their interruption path; `savingFailure` keeps its existing save attempt. Retained queued, active and previously stopped runtime owners use the existing `stopAndWait()` contract. + +MCP shutdown and SFTP cancellation proceed alongside those Assistant joins. Agent background commands are closed only after all these waits return, followed by the Agent connection pool. Repeated shutdown returns the same cleanup task. The native reply coordinator separately replies once when cleanup returns or its three-second fallback fires; termination can therefore happen before a blocked save or later resource closure completes. A joined save attempt remains distinct from save success. + +Cancellation is cooperative across the remote stack. `AgentSSHAuthenticationContext.configurationAttempt()` and shared pool opening await unstructured tasks; cancelling the runtime does not necessarily cancel those tasks or settle their interaction continuations. Such a drain may remain blocked until exit, before background-command or pool closure is reached. `AgentBackgroundCommands.closeAll()` itself requests command-task cancellation without awaiting remote settlement. These joins also exclude already-running manual compaction, title generation and memory indexing. `memory.suspend()` cancels current work, but successful terminal saves call `historyChanged()` and can schedule indexing again. The shutdown contract therefore covers captured turns and retained consumers, with the native fallback bounding the quit reply. + +### Mobile background expiration + Mobile background expiration first sets `isSuspending`, cancels pending preparations and recording, snapshots the stored conversations, and calls `stop()` on every conversation synchronously. Only after all cancellation requests are issued does its cleanup task call `stopAndWait()` for each snapshot member, then close background commands and Agent connections. A first conversation's blocked save therefore cannot postpone cancellation of a later queued or active run. Repeated expiration during that cleanup is inert, and composer sends remain blocked until cleanup returns, including when a scene has already returned to the foreground. The finite UIKit lease has a separate boundary: its expiration callback requests this cleanup and ends the matching valid lease synchronously, without awaiting those drains. Cleanup may continue when execution is available, but iOS suspension or termination can prevent completion. Neither the lease nor Live Activities guarantee persistence or continued network work. See [Platform Runtime](/openwiki/architecture/platform-runtime.md) for scene aggregation, lease identity and workspace recovery. @@ -316,6 +336,7 @@ New submissions capture whether they have a memory-session identity before prepa The focused tests establish behavior that is important when changing this area: +- `TFMTests/AppTerminationTests.swift` checks queued store-owned conversations after window unregistration, blocked cancellation saves before resource closure, post-fence eligible submissions and prepared-edit invalidation, and an unstored captured failure whose save must be joined. Injected clock gates exercise cleanup-first and fallback-first replies exactly once. These fixtures establish app ordering; they do not simulate process death, failing disk writes or a production SSH authentication drain. - `TFMTests/AgentRuntimeTests.swift` covers schema-before-approval/execution, approval mode routing, Guardian timeout/failure fail-closed behavior, single-use secret handles, steering delivery, tool cancellation, registry shadowing, and durable-event filtering. - `TFMTests/AgentPersistenceTests.swift` covers redaction, interrupted-run recovery, replay deduplication, tool-result repair, graph deletion, same-run edit prefix/tool preservation, canonical equal-time ordering, empty retained runs, staging adoption, late-write rejection, fork persistence and compaction restoration. `ChatComposerStateTests.swift` checks the compacted live prefix, tool cards, title, plans and adopted run summaries. - `FrameworkTests/AgentReplayPersistenceTests.swift` checks failed/interrupted staging adoption, queued cancellation, and controller-owned configuration failure of an edited replacement. The cancellation and captured-failure fixtures reload through a fresh `ModelContext` and repository, checking one adopted run, captured text/images/attachments/context, permission mode, and staging/terminal chronology. diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index 32d684ec..9d52e45f 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T06:59:53.554Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T08:21:59.182Z --- # Persistent Data, Migrations, and Secret Boundaries diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index 8ea107de..00701ef8 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -27,7 +27,7 @@ sources: generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:54:45.592Z + at: 2026-09-23T08:21:59.182Z --- # Termind Engineering Quickstart diff --git a/openwiki/workflows/ssh-session-lifecycle.md b/openwiki/workflows/ssh-session-lifecycle.md index ad982278..1bdbe607 100644 --- a/openwiki/workflows/ssh-session-lifecycle.md +++ b/openwiki/workflows/ssh-session-lifecycle.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T05:17:08.701Z + at: 2026-09-23T08:21:59.182Z --- # SSH, Telnet, Trust, and Workspace Session Lifecycle From 70c41b9225a4286b93936a18d44c6784b2361197 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 16:52:13 +0800 Subject: [PATCH 24/30] Make Agent connection waits cancellation-aware --- .../Agent/AgentConnections.swift | 294 +++++++++++++++--- TFMTests/AgentConnectionPoolTests.swift | 71 +++++ TFMTests/AgentHostAuthenticationTests.swift | 93 ++++++ 3 files changed, 410 insertions(+), 48 deletions(-) diff --git a/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift b/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift index d41f4ed3..c9638a97 100644 --- a/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift +++ b/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift @@ -561,8 +561,10 @@ public actor AgentSSHAuthenticationContext { } private struct Pending { + var id: UUID var generation: UInt64 var task: Task + var waiters: [UUID: CheckedContinuation] } private let resolver: AgentHostResolver @@ -597,12 +599,16 @@ public actor AgentSSHAuthenticationContext { } public func configurationAttempt() async throws -> Attempt { + try Task.checkCancellation() if let cached { return makeAttempt(configuration: cached, generation: generation) } - let active: Pending + let pendingID: UUID + let pendingGeneration: UInt64 if let pending, pending.generation == generation { - active = pending + pendingID = pending.id + pendingGeneration = pending.generation } else { + let id = UUID() let currentGeneration = generation let resolver = resolver let hostID = hostID @@ -622,30 +628,33 @@ public actor AgentSSHAuthenticationContext { forcedHostID: forcedHostID, forcedProxy: forcedProxy, failure: failure ) } - active = Pending(generation: currentGeneration, task: task) - pending = active + pending = Pending( + id: id, generation: currentGeneration, task: task, waiters: [:] + ) + pendingID = id + pendingGeneration = currentGeneration + Task { [weak self] in + do { + let resolution = try await task.value + await self?.finishConfigurationAttempt( + id: id, generation: currentGeneration, result: .success(resolution) + ) + } catch { + await self?.finishConfigurationAttempt( + id: id, generation: currentGeneration, result: .failure(error) + ) + } + } } - do { - let resolution = try await active.task.value - if generation == active.generation { - submissions = resolution.submissions - proxySubmissions = resolution.proxySubmissions - temporaryHostKeys = resolution.temporaryHostKeys - cached = resolution.configuration - forcedHostID = nil - forcedProxy = false - failure = nil - if pending?.generation == active.generation { pending = nil } - } - return makeAttempt( - configuration: resolution.configuration, - generation: active.generation - ) - } catch { - if pending?.generation == active.generation { pending = nil } - throw error - } + let resolution = try await waitForConfigurationAttempt( + id: pendingID, generation: pendingGeneration + ) + try Task.checkCancellation() + return makeAttempt( + configuration: resolution.configuration, + generation: pendingGeneration + ) } /// Convenience for focused resolver tests that do not need to simulate a @@ -673,8 +682,7 @@ public actor AgentSSHAuthenticationContext { generation &+= 1 cached?.clearSecrets() cached = nil - pending?.task.cancel() - pending = nil + cancelPendingConfigurationAttempt() forcedHostID = target forcedProxy = error.isProxyAuthentication failure = error.localizedDescription @@ -686,8 +694,85 @@ public actor AgentSSHAuthenticationContext { proxySubmissions.removeAll() cached?.clearSecrets() cached = nil - pending?.task.cancel() + cancelPendingConfigurationAttempt() + } + + private func waitForConfigurationAttempt( + id: UUID, generation: UInt64 + ) async throws -> AgentHostResolver.InteractiveResolution { + let waiterID = UUID() + return try await withTaskCancellationHandler { + try await withCheckedThrowingContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(throwing: CancellationError()) + return + } + guard var active = pending, + active.id == id, + active.generation == generation + else { + continuation.resume(throwing: CancellationError()) + return + } + active.waiters[waiterID] = continuation + pending = active + } + } onCancel: { + Task { [weak self] in + await self?.cancelConfigurationWaiter( + id: id, generation: generation, waiterID: waiterID + ) + } + } + } + + private func finishConfigurationAttempt( + id: UUID, generation completedGeneration: UInt64, + result: Result + ) { + guard let active = pending, + active.id == id, + active.generation == completedGeneration + else { return } pending = nil + if case .success(let resolution) = result, generation == completedGeneration { + submissions = resolution.submissions + proxySubmissions = resolution.proxySubmissions + temporaryHostKeys = resolution.temporaryHostKeys + cached = resolution.configuration + forcedHostID = nil + forcedProxy = false + failure = nil + } + for continuation in active.waiters.values { + continuation.resume(with: result) + } + } + + private func cancelConfigurationWaiter( + id: UUID, generation: UInt64, waiterID: UUID + ) { + guard var active = pending, + active.id == id, + active.generation == generation, + let continuation = active.waiters.removeValue(forKey: waiterID) + else { return } + continuation.resume(throwing: CancellationError()) + if active.waiters.isEmpty { + pending = nil + active.task.cancel() + } else { + pending = active + } + } + + private func cancelPendingConfigurationAttempt() { + guard let active = pending else { return } + pending = nil + active.task.cancel() + for continuation in active.waiters.values { + continuation.resume(throwing: CancellationError()) + } } private func makeAttempt( @@ -1077,9 +1162,19 @@ public actor AgentConnectionPool { var lastUsedAt: ContinuousClock.Instant } - private struct InFlight { + private struct ConnectionOpenKey: Hashable, Sendable { + var hostID: UUID var fingerprint: String + } + + private struct OpenedConnection: Sendable { + var value: any SSHConnectionProtocol + } + + private struct InFlight { + var id: UUID var task: Task + var waiters: [UUID: CheckedContinuation] } private let configuration: ConfigurationResolving @@ -1089,7 +1184,7 @@ public actor AgentConnectionPool { private let idleTimeout: Duration private let sweeper: AgentIdleSweeper private var entries: [UUID: Entry] = [:] - private var pending: [UUID: InFlight] = [:] + private var pending: [ConnectionOpenKey: InFlight] = [:] private var activeExecLeases: [UUID: Int] = [:] struct SFTPLease: Sendable { @@ -1449,7 +1544,7 @@ public actor AgentConnectionPool { @discardableResult public func disconnect(hostID: UUID) async -> Bool { - pending.removeValue(forKey: hostID)?.task.cancel() + cancelConnectionOpenings(hostID: hostID) let stateClosed = await disconnectSFTPState(hostID: hostID) activeExecLeases[hostID] = nil let entry = entries.removeValue(forKey: hostID) @@ -1461,7 +1556,7 @@ public actor AgentConnectionPool { public func closeAll() async { let hostIDs = Set(entries.keys) .union(sftpStates.keys) - .union(pending.keys) + .union(pending.keys.map(\.hostID)) .union(sftpPrimaryOpens.keys) for hostID in hostIDs { _ = await disconnect(hostID: hostID) } } @@ -1545,12 +1640,11 @@ public actor AgentConnectionPool { ) async throws -> any SSHConnectionProtocol { // Without this join, two tool calls that both miss the cache would each // build a connection and one of them would never be pooled — or closed. - if let inFlight = pending[hostID], inFlight.fingerprint == fingerprint { - let connection = try await inFlight.task.value - let committedFingerprint = try await routeFingerprint(hostID) - commit(connection, hostID: hostID, fingerprint: committedFingerprint) - return connection + let key = ConnectionOpenKey(hostID: hostID, fingerprint: fingerprint) + if let inFlight = pending[key] { + return try await waitForConnectionOpening(key: key, id: inFlight.id) } + let id = UUID() let task = Task { [configuration, openConnection] () async throws -> any SSHConnectionProtocol in let request: SSHConnectionRequest if let override { @@ -1562,18 +1656,122 @@ public actor AgentConnectionPool { } return try await openConnection(request) } - pending[hostID] = InFlight(fingerprint: fingerprint, task: task) - defer { if pending[hostID]?.fingerprint == fingerprint { pending[hostID] = nil } } - // Committed on every path that receives a client, not only on the path - // that started the task: whoever started it may have been cancelled - // while a second caller was still waiting on the same connection. - let connection = try await task.value - // Saving/binding an Identity while the connection prompt is open - // legitimately changes the route fingerprint. Bind the authenticated - // connection to that post-save route so the next command can reuse it. - let committedFingerprint = try await routeFingerprint(hostID) - commit(connection, hostID: hostID, fingerprint: committedFingerprint) - return connection + pending[key] = InFlight(id: id, task: task, waiters: [:]) + Task { [weak self] in + do { + let connection = try await task.value + await self?.finishConnectionOpening( + key: key, id: id, result: .success(connection) + ) + } catch { + await self?.finishConnectionOpening( + key: key, id: id, result: .failure(error) + ) + } + } + return try await waitForConnectionOpening(key: key, id: id) + } + + private func waitForConnectionOpening( + key: ConnectionOpenKey, id: UUID + ) async throws -> any SSHConnectionProtocol { + let waiterID = UUID() + let connection: OpenedConnection = try await withTaskCancellationHandler { + try await withCheckedThrowingContinuation { + (continuation: CheckedContinuation) in + guard !Task.isCancelled else { + continuation.resume(throwing: CancellationError()) + return + } + guard var opening = pending[key], opening.id == id else { + continuation.resume(throwing: CancellationError()) + return + } + opening.waiters[waiterID] = continuation + pending[key] = opening + } + } onCancel: { + Task { [weak self] in + await self?.cancelConnectionOpeningWaiter( + key: key, id: id, waiterID: waiterID + ) + } + } + try Task.checkCancellation() + return connection.value + } + + private func finishConnectionOpening( + key: ConnectionOpenKey, id: UUID, + result initialResult: Result + ) async { + guard let opening = pending[key], opening.id == id else { + if case .success(let connection) = initialResult { + await connection.disconnect() + } + return + } + + let result: Result + switch initialResult { + case .failure(let error): + result = .failure(error) + case .success(let connection): + do { + // Saving/binding an Identity while the connection prompt is open + // legitimately changes the route fingerprint. Bind the authenticated + // connection to that post-save route so the next command can reuse it. + let committedFingerprint = try await routeFingerprint(key.hostID) + guard pending[key]?.id == id else { + await connection.disconnect() + return + } + commit( + connection, hostID: key.hostID, + fingerprint: committedFingerprint + ) + result = .success(OpenedConnection(value: connection)) + } catch { + await connection.disconnect() + result = .failure(error) + } + } + + guard let completed = pending.removeValue(forKey: key), completed.id == id else { + if case .success(let connection) = result { + await connection.value.disconnect() + } + return + } + for continuation in completed.waiters.values { + continuation.resume(with: result) + } + } + + private func cancelConnectionOpeningWaiter( + key: ConnectionOpenKey, id: UUID, waiterID: UUID + ) { + guard var opening = pending[key], opening.id == id, + let continuation = opening.waiters.removeValue(forKey: waiterID) + else { return } + continuation.resume(throwing: CancellationError()) + if opening.waiters.isEmpty { + pending[key] = nil + opening.task.cancel() + } else { + pending[key] = opening + } + } + + private func cancelConnectionOpenings(hostID: UUID) { + let keys = pending.keys.filter { $0.hostID == hostID } + for key in keys { + guard let opening = pending.removeValue(forKey: key) else { continue } + opening.task.cancel() + for continuation in opening.waiters.values { + continuation.resume(throwing: CancellationError()) + } + } } private func commit( diff --git a/TFMTests/AgentConnectionPoolTests.swift b/TFMTests/AgentConnectionPoolTests.swift index fc4fc68e..3e6f2215 100644 --- a/TFMTests/AgentConnectionPoolTests.swift +++ b/TFMTests/AgentConnectionPoolTests.swift @@ -78,6 +78,63 @@ struct AgentConnectionPoolTests { #expect(await backend.execOpens == 1) } + @Test + func cancellingTheOnlyConnectionWaiterDoesNotWaitForTheSharedOpen() async throws { + let backend = StubBackend() + let gate = Gate() + let pool = makePool(backend, openerGate: gate) + let host = UUID() + let completion = ConnectionCompletionProbe() + let opening = Task { + defer { Task { await completion.finish() } } + return try await pool.connection(hostID: host) + } + while await gate.arrivals == 0 { await Task.yield() } + + opening.cancel() + let completedBeforeOpenFinished = await waitForCompletion(completion) + await gate.open() + + #expect(completedBeforeOpenFinished) + await #expect(throws: CancellationError.self) { + _ = try await opening.value + } + for _ in 0..<1_000 where await backend.execDisconnects == 0 { + await Task.yield() + } + #expect(await backend.execDisconnects == 1) + #expect(await pool.openConnectionCount == 0) + } + + @Test + func cancellingOneConnectionWaiterPreservesTheSharedOpenForAnotherWaiter() async throws { + let backend = StubBackend() + let gate = Gate() + let pool = makePool(backend, openerGate: gate) + let host = UUID() + let firstCompletion = ConnectionCompletionProbe() + let first = Task { + defer { Task { await firstCompletion.finish() } } + return try await pool.connection(hostID: host) + } + while await gate.arrivals == 0 { await Task.yield() } + let second = Task { try await pool.connection(hostID: host) } + for _ in 0..<100 { await Task.yield() } + + first.cancel() + let firstCompletedBeforeOpenFinished = await waitForCompletion(firstCompletion) + await gate.open() + + #expect(firstCompletedBeforeOpenFinished) + await #expect(throws: CancellationError.self) { + _ = try await first.value + } + #expect(try serial(await second.value) == 1) + #expect(await backend.execOpens == 1) + #expect(await backend.execDisconnects == 0) + #expect(await pool.openConnectionCount == 1) + } + @Test func execAndSFTPShareOneConnectionAndCloseTogether() async throws { let backend = StubBackend() @@ -285,6 +342,20 @@ struct AgentConnectionPoolTests { } } +private func waitForCompletion(_ completion: ConnectionCompletionProbe) async -> Bool { + for _ in 0..<1_000 { + if await completion.isFinished { return true } + await Task.yield() + } + return await completion.isFinished +} + +private actor ConnectionCompletionProbe { + private(set) var isFinished = false + + func finish() { isFinished = true } +} + /// Which stub connection the pool handed back. Distinguishing "reused" from /// "reconnected" is the whole subject of these tests. private func serial(_ connection: any SSHConnectionProtocol) throws -> Int { diff --git a/TFMTests/AgentHostAuthenticationTests.swift b/TFMTests/AgentHostAuthenticationTests.swift index 00db0256..b58fc3c4 100644 --- a/TFMTests/AgentHostAuthenticationTests.swift +++ b/TFMTests/AgentHostAuthenticationTests.swift @@ -64,6 +64,75 @@ struct AgentHostAuthenticationTests { #expect(configuration.credentials.isEmpty) } + @Test + func cancellingTheOnlyConfigurationWaiterClearsItsAuthenticationCard() async throws { + let host = Host(name: "Server", host: "server.example") + let store = HostLibraryModel(hosts: [host]) + let conversation = ConversationModel() + let session = AgentSSHAuthenticationContext( + resolver: AgentHostResolver(store: store), hostID: host.id, + invocationID: UUID(), interaction: WeakAgentUserInteraction(conversation).sshInteraction + ) + let completion = AuthenticationCompletionProbe() + let attempt = Task { + defer { Task { await completion.finish() } } + return try await session.configurationAttempt() + } + try await waitForAuthentication(in: conversation) + + attempt.cancel() + let completedBeforeAnswer = await waitForCompletion(completion) + let cardCleared = conversation.pendingSSHAuthentication == nil + if !cardCleared { + conversation.respondToSSHAuthentication(.cancelled) + } + + #expect(completedBeforeAnswer) + #expect(cardCleared) + await #expect(throws: CancellationError.self) { + _ = try await attempt.value + } + } + + @Test + func cancellingOneSharedConfigurationWaiterKeepsTheOtherOwnerRunning() async throws { + let host = Host(name: "Server", host: "server.example") + let store = HostLibraryModel(hosts: [host]) + let conversation = ConversationModel() + let session = AgentSSHAuthenticationContext( + resolver: AgentHostResolver(store: store), hostID: host.id, + invocationID: UUID(), interaction: WeakAgentUserInteraction(conversation).sshInteraction + ) + let firstCompletion = AuthenticationCompletionProbe() + let first = Task { + defer { Task { await firstCompletion.finish() } } + return try await session.configurationAttempt() + } + try await waitForAuthentication(in: conversation) + let second = Task { try await session.configurationAttempt() } + for _ in 0..<100 { await Task.yield() } + + first.cancel() + let firstCompletedBeforeAnswer = await waitForCompletion(firstCompletion) + let request = try #require(conversation.pendingSSHAuthentication) + conversation.respondToSSHAuthentication( + .submit( + AgentSSHAuthenticationAnswer( + username: "deploy", password: "one-use", save: false + )), + requestID: request.id + ) + + #expect(firstCompletedBeforeAnswer) + await #expect(throws: CancellationError.self) { + _ = try await first.value + } + let retained = try await second.value + #expect(retained.configuration.username == "deploy") + #expect(password(in: retained.configuration, matches: "one-use")) + #expect(conversation.pendingSSHAuthentication == nil) + } + @Test func concurrentConnectionChallengesAreNeverCoalescedWithSavedCredentials() async throws { let identity = Identity(name: "OTP", username: "deploy") let host = Host(name: "Server", host: "server.example", identityID: identity.id) @@ -1177,6 +1246,30 @@ struct AgentHostAuthenticationTests { } return false } + + private func waitForAuthentication(in conversation: ConversationModel) async throws { + for _ in 0..<1_000 { + if conversation.pendingSSHAuthentication != nil { return } + await Task.yield() + } + throw AuthenticationCancellationTestError.timedOut + } + + private func waitForCompletion(_ completion: AuthenticationCompletionProbe) async -> Bool { + for _ in 0..<1_000 { + if await completion.isFinished { return true } + await Task.yield() + } + return await completion.isFinished + } +} + +private enum AuthenticationCancellationTestError: Error { case timedOut } + +private actor AuthenticationCompletionProbe { + private(set) var isFinished = false + + func finish() { isFinished = true } } private final class MemoryHostSecretStore: SecretStoring, @unchecked Sendable { From 568e9b73272d45f70e041acc08ca797f611b06bf Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 16:48:07 +0800 Subject: [PATCH 25/30] Fence Hermes memory work while suspended --- FrameworkTests/HermesMemoryTests.swift | 139 ++++++++++++++++++ .../FeatureModels/MemoryModel.swift | 28 ++-- TFI/App/MobileAppModel.swift | 1 + 3 files changed, 157 insertions(+), 11 deletions(-) diff --git a/FrameworkTests/HermesMemoryTests.swift b/FrameworkTests/HermesMemoryTests.swift index 27c13fa6..cbc5a2cd 100644 --- a/FrameworkTests/HermesMemoryTests.swift +++ b/FrameworkTests/HermesMemoryTests.swift @@ -113,6 +113,66 @@ struct HermesMemoryTests { await model.setEnabled(false) } + @Test func suspendedMemoryDefersTerminalHistoryIndexUntilResume() async throws { + let store = MemoryLifecycleStore() + let model = MemoryModel(storage: store) + let history = InMemoryAgentRunRepository() + let repository = MemoryIndexedAgentRepository(base: history, memory: model) + await model.setEnabled(true) + try await waitUntil { await store.historyReplacementCount == 1 && !model.isIndexing } + + model.suspend() + let conversationID = UUID() + try await repository.save( + .init( + conversationID: conversationID, state: .completed, permissionMode: .askForApproval, + messages: [.init(role: .user, text: "Saved after suspension")])) + + #expect(!model.isIndexing) + #expect(await store.historyReplacementCount == 1) + + model.resume() + try await waitUntil { await store.historyReplacementCount == 2 && !model.isIndexing } + #expect(await store.lastHistory?.contains(where: { $0.id == conversationID }) == true) + } + + @Test func suspendInvalidatesExistingMemoryAccessAcrossResume() async throws { + let store = MemoryLifecycleStore() + let model = MemoryModel(storage: store) + await model.setEnabled(true) + let conversationID = UUID() + let original = try #require(await model.session(conversationID)) + + model.suspend() + #expect(await model.session(conversationID) == nil) + model.resume() + + await #expect(throws: CancellationError.self) { try await original.access.memoryState() } + let resumed = try #require(await model.session(conversationID)) + _ = try await resumed.access.memoryState() + } + + @Test func suspendPreventsStaleReviewFromApplyingAfterResume() async throws { + let store = MemoryLifecycleStore() + let model = MemoryModel(storage: store) + await model.setEnabled(true) + let reviewModel = BlockingMemoryReviewModel() + let review = Task { + await model.review( + conversationID: UUID(), messages: [.init(role: .user, text: "Remember concise replies")], + model: reviewModel, wait: true) + } + await reviewModel.waitUntilEntered() + + model.suspend() + model.resume() + await reviewModel.release() + await review.value + + #expect(await store.reviewApplyCount == 0) + #expect(model.state.entries.isEmpty) + } + @Test func skillPackagesPersistWithoutChangingLegacySkillRows() throws { let container = try TermindPersistence.makeContainer(inMemory: true) let persistence = SkillPersistence(context: container.mainContext) @@ -183,6 +243,85 @@ struct HermesMemoryTests { } } +private actor MemoryLifecycleStore: MemoryPersisting { + private var memory = AgentMemoryState() + private var historyReplacements: [[StoredAgentConversation]] = [] + private(set) var reviewApplyCount = 0 + + var historyReplacementCount: Int { historyReplacements.count } + var lastHistory: [StoredAgentConversation]? { historyReplacements.last } + + func rebuildHistoryIndex() {} + func memoryState() -> AgentMemoryState { memory } + func applyMemory(_ operations: [AgentMemoryOperation]) throws -> AgentMemoryState { + memory = try memory.applying(operations) + return memory + } + func snapshot(for id: UUID) -> String { memory.prompt } + func replaceHistory(_ conversations: [StoredAgentConversation]) { historyReplacements.append(conversations) } + func searchSessions(_ request: AgentSessionSearchRequest) -> AgentJSONValue { .array([]) } + func lastReviewedMessage(_ id: UUID) -> UUID? { nil } + func markReviewed(_ messageID: UUID, conversationID: UUID) {} + func applyReview( + _ operations: [AgentMemoryOperation], expected: AgentMemoryState, messageID: UUID, conversationID: UUID + ) throws -> AgentMemoryState? { + reviewApplyCount += 1 + guard memory == expected else { return nil } + memory = try memory.applying(operations) + return memory + } + func replaceTarget(_ target: AgentMemoryTarget, text: String) -> AgentMemoryState { memory } + func close() {} + func destroy() { memory = .init() } +} + +private actor BlockingMemoryReviewModel: AgentModelCompleting { + private var entered = false + private var released = false + private var entryWaiters: [CheckedContinuation] = [] + private var releaseWaiters: [CheckedContinuation] = [] + + func waitUntilEntered() async { + if entered { return } + await withCheckedContinuation { entryWaiters.append($0) } + } + + func release() { + released = true + let waiters = releaseWaiters + releaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func complete(_ request: AgentModelRequest) async throws -> [AgentModelStreamEvent] { + entered = true + let waiters = entryWaiters + entryWaiters.removeAll() + for waiter in waiters { waiter.resume() } + if !released { + await withCheckedContinuation { releaseWaiters.append($0) } + } + return [ + .textDelta(#"{"operations":[{"action":"add","target":"user","content":"Likes concise replies"}]}"#), + .finished(.completed), + ] + } +} + +@MainActor +private func waitUntil( + timeout: Duration = .seconds(2), _ condition: @escaping @MainActor () async -> Bool +) async throws { + let clock = ContinuousClock() + let deadline = clock.now.advanced(by: timeout) + while !(await condition()) { + guard clock.now < deadline else { throw MemoryLifecycleTestTimeout() } + try await Task.sleep(for: .milliseconds(1)) + } +} + +private struct MemoryLifecycleTestTimeout: Error {} + private struct FailingDestroyStore: MemoryPersisting { let base: GRDBMemoryStore func rebuildHistoryIndex() async throws { try await base.rebuildHistoryIndex() } diff --git a/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift b/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift index c884a2a0..c6b661a9 100644 --- a/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift +++ b/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift @@ -33,6 +33,7 @@ public final class MemoryModel { private var indexDirty = false private var reviews: [UUID: Task] = [:] private var pendingReviews: [UUID: (@Sendable () async -> Void)] = [:] + private var isSuspended = false public init(storage: (any MemoryPersisting)? = nil, defaults: UserDefaults? = nil) { self.storage = storage @@ -75,7 +76,7 @@ public final class MemoryModel { } catch { self.error = error.localizedDescription } } public func refresh() async { - guard isEnabled, let storage else { return } + guard isEnabled, !isSuspended, let storage else { return } let token = generation do { let value = try await storage.memoryState() @@ -86,7 +87,7 @@ public final class MemoryModel { } catch { if token == generation { self.error = error.localizedDescription } } } public func edit(_ target: AgentMemoryTarget, text: String) async { - guard isEnabled, let storage else { return } + guard isEnabled, !isSuspended, let storage else { return } let token = generation do { let updated = try await storage.replaceTarget(target, text: text) @@ -96,7 +97,7 @@ public final class MemoryModel { } catch { if token == generation { self.error = error.localizedDescription } } } public func session(_ id: UUID) async -> (context: AgentMemoryContext, access: any AgentMemoryAccessing)? { - guard isEnabled, let storage else { return nil } + guard isEnabled, !isSuspended, let storage else { return nil } let token = generation do { let context: AgentMemoryContext @@ -116,7 +117,7 @@ public final class MemoryModel { } private func require(_ token: UUID) throws -> any MemoryPersisting { try Task.checkCancellation() - guard isEnabled, token == generation, !isBusy, let storage else { throw CancellationError() } + guard isEnabled, !isSuspended, token == generation, !isBusy, let storage else { throw CancellationError() } return storage } fileprivate func read(token: UUID) async throws -> AgentMemoryState { @@ -140,7 +141,7 @@ public final class MemoryModel { return result } public func rebuildHistoryIndex() async { - guard isEnabled else { return } + guard isEnabled, !isSuspended else { return } let token = generation historyChanged() await indexTask?.value @@ -151,7 +152,7 @@ public final class MemoryModel { } public func historyChanged() { - guard isEnabled, let storage, let history else { return } + guard isEnabled, !isSuspended, let storage, let history else { return } indexDirty = true guard indexTask == nil else { return } let token = generation @@ -161,7 +162,7 @@ public final class MemoryModel { defer { indexTask = nil isIndexing = false - if indexDirty, isEnabled { historyChanged() } + if indexDirty, isEnabled, !isSuspended { historyChanged() } } do { while indexDirty, token == generation, isEnabled, !Task.isCancelled { @@ -183,7 +184,7 @@ public final class MemoryModel { conversationID: UUID, messages: [AgentTranscriptMessage], model: any AgentModelCompleting, wait: Bool = false ) async { - guard isEnabled, reviewEnabled, let storage, let last = messages.last?.id else { return } + guard isEnabled, !isSuspended, reviewEnabled, let storage, let last = messages.last?.id else { return } let token = generation if let running = reviews[conversationID] { if wait { @@ -249,9 +250,14 @@ public final class MemoryModel { isBusy = false } public func suspend() { - indexTask?.cancel() - for task in reviews.values { task.cancel() } - pendingReviews.removeAll() + guard !isSuspended else { return } + isSuspended = true + invalidate() + } + public func resume() { + guard isSuspended else { return } + isSuspended = false + historyChanged() } } diff --git a/TFI/App/MobileAppModel.swift b/TFI/App/MobileAppModel.swift index 896d8ad6..8c4f86df 100644 --- a/TFI/App/MobileAppModel.swift +++ b/TFI/App/MobileAppModel.swift @@ -566,6 +566,7 @@ final class MobileAppModel { } else if phase == .active { devices.updateCurrentDeviceName(Device.current.safeDescription) sftpEdits.setMonitoring(true) + memory.resume() Task { await sftpEdits.recheck() } Task { [weak self] in await self?.pro.refresh() } background.finish() From edb315e81fea13854b122c4b8fc0a4cd37187cf6 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 17:04:43 +0800 Subject: [PATCH 26/30] Reconcile memory state after stale writes --- FrameworkTests/HermesMemoryTests.swift | 61 ++++++++++++++++++- .../FeatureModels/MemoryModel.swift | 35 +++++++++-- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/FrameworkTests/HermesMemoryTests.swift b/FrameworkTests/HermesMemoryTests.swift index cbc5a2cd..3d37a4da 100644 --- a/FrameworkTests/HermesMemoryTests.swift +++ b/FrameworkTests/HermesMemoryTests.swift @@ -152,6 +152,31 @@ struct HermesMemoryTests { _ = try await resumed.access.memoryState() } + @Test func resumeReconcilesACommittedWriteFromTheSuspendedGeneration() async throws { + let store = MemoryLifecycleStore(blocksApply: true) + let model = MemoryModel(storage: store) + await model.setEnabled(true) + let original = try #require(await model.session(UUID())) + let write = Task { + try await original.access.applyMemory([ + .init(action: .add, target: .memory, content: "Committed during suspension") + ]) + } + await store.waitUntilApplyEntered() + + model.suspend() + model.resume() + try await waitUntil { await store.memoryStateCallCount >= 2 } + await store.releaseApply() + + await #expect(throws: CancellationError.self) { try await write.value } + try await waitUntil { + await store.memoryStateCallCount >= 3 + && model.state.text(.memory) == "Committed during suspension" + } + await #expect(throws: CancellationError.self) { try await original.access.memoryState() } + } + @Test func suspendPreventsStaleReviewFromApplyingAfterResume() async throws { let store = MemoryLifecycleStore() let model = MemoryModel(storage: store) @@ -246,14 +271,46 @@ struct HermesMemoryTests { private actor MemoryLifecycleStore: MemoryPersisting { private var memory = AgentMemoryState() private var historyReplacements: [[StoredAgentConversation]] = [] + private let blocksApply: Bool + private var applyEntered = false + private var applyReleased = false + private var applyEntryWaiters: [CheckedContinuation] = [] + private var applyReleaseWaiters: [CheckedContinuation] = [] private(set) var reviewApplyCount = 0 + private(set) var memoryStateCallCount = 0 var historyReplacementCount: Int { historyReplacements.count } var lastHistory: [StoredAgentConversation]? { historyReplacements.last } + init(blocksApply: Bool = false) { self.blocksApply = blocksApply } + + func waitUntilApplyEntered() async { + if applyEntered { return } + await withCheckedContinuation { applyEntryWaiters.append($0) } + } + + func releaseApply() { + applyReleased = true + let waiters = applyReleaseWaiters + applyReleaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + func rebuildHistoryIndex() {} - func memoryState() -> AgentMemoryState { memory } - func applyMemory(_ operations: [AgentMemoryOperation]) throws -> AgentMemoryState { + func memoryState() -> AgentMemoryState { + memoryStateCallCount += 1 + return memory + } + func applyMemory(_ operations: [AgentMemoryOperation]) async throws -> AgentMemoryState { + if blocksApply { + applyEntered = true + let waiters = applyEntryWaiters + applyEntryWaiters.removeAll() + for waiter in waiters { waiter.resume() } + if !applyReleased { + await withCheckedContinuation { applyReleaseWaiters.append($0) } + } + } memory = try memory.applying(operations) return memory } diff --git a/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift b/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift index c6b661a9..299d6001 100644 --- a/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift +++ b/Frameworks/TermindApplication/FeatureModels/MemoryModel.swift @@ -91,8 +91,7 @@ public final class MemoryModel { let token = generation do { let updated = try await storage.replaceTarget(target, text: text) - guard token == generation, isEnabled else { return } - state = updated + try publishCommittedState(updated, token: token) error = nil } catch { if token == generation { self.error = error.localizedDescription } } } @@ -120,6 +119,31 @@ public final class MemoryModel { guard isEnabled, !isSuspended, token == generation, !isBusy, let storage else { throw CancellationError() } return storage } + private func publishCommittedState(_ updated: AgentMemoryState, token: UUID) throws { + do { + _ = try require(token) + } catch { + reloadStateForCurrentGeneration() + throw error + } + state = updated + } + private func reloadStateForCurrentGeneration() { + guard isEnabled, !isSuspended, !isBusy, let storage else { return } + let token = generation + Task { [weak self] in + guard let self else { return } + do { + let value = try await storage.memoryState() + guard token == generation, isEnabled, !isSuspended, !isBusy else { return } + state = value + error = nil + } catch { + guard token == generation, isEnabled, !isSuspended, !isBusy else { return } + self.error = error.localizedDescription + } + } + } fileprivate func read(token: UUID) async throws -> AgentMemoryState { let result = try await require(token).memoryState() _ = try require(token) @@ -128,8 +152,7 @@ public final class MemoryModel { @discardableResult fileprivate func apply(_ operations: [AgentMemoryOperation], token: UUID) async throws -> AgentMemoryState { let result = try await require(token).applyMemory(operations) - _ = try require(token) - state = result + try publishCommittedState(result, token: token) return result } fileprivate func search(_ request: AgentSessionSearchRequest, token: UUID) async throws -> AgentJSONValue { @@ -221,8 +244,7 @@ public final class MemoryModel { if let updated = try await storage.applyReview( operations, expected: current, messageID: last, conversationID: conversationID) { - _ = try require(token) - state = updated + try publishCommittedState(updated, token: token) } } catch is CancellationError { } catch { if token == generation { self.error = error.localizedDescription } } @@ -257,6 +279,7 @@ public final class MemoryModel { public func resume() { guard isSuspended else { return } isSuspended = false + reloadStateForCurrentGeneration() historyChanged() } } From ad7ecb5ec6707dcd3d2bbdf26c054820f3aa9a52 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 16:55:39 +0800 Subject: [PATCH 27/30] Fence mobile Assistant work during suspension --- .../Agent/MobileAssistantModel.swift | 3 + TFITests/MobileAssistantTests.swift | 86 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/TFI/Application/Agent/MobileAssistantModel.swift b/TFI/Application/Agent/MobileAssistantModel.swift index e4255e04..f1d0336b 100644 --- a/TFI/Application/Agent/MobileAssistantModel.swift +++ b/TFI/Application/Agent/MobileAssistantModel.swift @@ -215,6 +215,7 @@ final class MobileAssistantModel { } func edit(_ conversation: ConversationModel, messageID: UUID, text: String) { + guard !isSuspending else { return } guard let prepared = execution.prepareAgentEdit( conversation, messageID: messageID, text: text, @@ -229,12 +230,14 @@ final class MobileAssistantModel { } func compact(_ conversation: ConversationModel, instructions: String = "") async { + guard !isSuspending else { return } _ = await execution.compact(conversation, instructions: instructions) } fileprivate func implement( _ card: AgentPlanCard, in conversation: ConversationModel, library: AssistantLibraryModel ) { + guard !isSuspending else { return } guard execution.sendPlanImplementation( card, in: conversation, diff --git a/TFITests/MobileAssistantTests.swift b/TFITests/MobileAssistantTests.swift index 066bf9d5..99e4a3d4 100644 --- a/TFITests/MobileAssistantTests.swift +++ b/TFITests/MobileAssistantTests.swift @@ -485,11 +485,97 @@ struct MobileAssistantTests { #expect(app.assistant.isSuspending) assistant.send() #expect(assistant.conversation.composer.draft == "preserve") + await assistant.compact(assistant.conversation) + #expect(assistant.conversation.compactionActivity == nil) await app.assistant.waitForSuspension() #expect(!app.assistant.isSuspending) #expect(!app.assistant.isRunning) } + @Test func backgroundExpirationBlocksEditsUntilCleanupCompletesThenAllowsRecovery() async throws { + let app = try MobileAppModel(inMemory: true) + let assistant = app.assistant.makeSceneModel() + let conversation = assistant.conversation + conversation.composer.draft = "Original question" + assistant.send() + #expect(await eventually { conversation.runState == .failed }) + let original = try #require(conversation.messages.first { $0.role == .user }) + let blocker = ConversationModel() + assistant.library.insertAndSelect(blocker) + let blockerRepository = MobileTerminalSaveGateRepository() + blocker.start( + runtime: AgentRuntime( + model: MobileImmediateAssistantModel(calls: MobileAssistantModelCalls()), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: blockerRepository + ), + request: AgentRunRequest( + conversationID: blocker.id, prompt: "block cleanup", permissionMode: .askForApproval), + repository: blockerRepository + ) + await blockerRepository.waitForTerminalSave() + + app.assistant.expireBackground() + #expect(app.assistant.isSuspending) + assistant.edit(conversation, messageID: original.id, text: "Replacement question") + await Task.yield() + #expect(conversation.messages.first(where: { $0.role == .user })?.text == "Original question") + + await blockerRepository.releaseTerminalSaves() + await app.assistant.waitForSuspension() + #expect(!app.assistant.isSuspending) + assistant.edit(conversation, messageID: original.id, text: "Replacement question") + #expect( + await eventually { + conversation.messages.first(where: { $0.role == .user })?.text == "Replacement question" + } + ) + #expect(await eventually { conversation.runState == .failed }) + } + + @Test func backgroundExpirationBlocksPlanImplementationUntilCleanupCompletesThenAllowsRecovery() async throws { + let app = try MobileAppModel(inMemory: true) + let assistant = app.assistant.makeSceneModel() + let conversation = ConversationModel() + _ = assistant.library.startConversation(conversation) + let plan = AgentPlan( + title: "Implementation plan", body: "Apply the requested change.", + scratchPath: "/tmp/plan.md") + conversation.isPlanning = true + conversation.handle(.planPresented(plan)) + let card = try #require(conversation.plans.first) + let blocker = ConversationModel() + assistant.library.insertAndSelect(blocker) + let blockerRepository = MobileTerminalSaveGateRepository() + blocker.start( + runtime: AgentRuntime( + model: MobileImmediateAssistantModel(calls: MobileAssistantModelCalls()), tools: [], + approval: AgentApprovalBroker(reviewer: nil, manualApproval: { _ in .allow }), + repository: blockerRepository + ), + request: AgentRunRequest( + conversationID: blocker.id, prompt: "block cleanup", permissionMode: .askForApproval), + repository: blockerRepository + ) + await blockerRepository.waitForTerminalSave() + + app.assistant.expireBackground() + #expect(app.assistant.isSuspending) + assistant.implement(card, in: conversation) + #expect(conversation.plans.first?.outcome == .pending) + #expect(conversation.transcript.isEmpty) + + await blockerRepository.releaseTerminalSaves() + await app.assistant.waitForSuspension() + #expect(!app.assistant.isSuspending) + assistant.implement(card, in: conversation) + #expect(conversation.plans.first?.outcome == .implemented) + let implementation = try #require(conversation.transcript.last) + #expect(implementation.role == .user) + #expect(implementation.contextSnapshot?.planContract == nil) + #expect(await eventually { conversation.runState == .failed }) + } + @Test func backgroundExpirationStopsEveryConversationBeforeJoiningTheFirstDrain() async throws { let app = try MobileAppModel(inMemory: true) let assistant = app.assistant.makeSceneModel() From 29bad8b676f3af117217e12d70a24cba478d51b3 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 17:32:31 +0800 Subject: [PATCH 28/30] Stabilize mobile integration tests --- AGENTS.md | 2 ++ .../PortForwardingPresentationTests.swift | 22 ++++++++++++++++--- TFI/App/MobileAppModel.swift | 2 +- TFITests/MobileAdaptiveLayoutTests.swift | 5 +++-- ...MobileCommandGeneratorAndUploadTests.swift | 2 +- TFITests/MobileEditorToolbarTests.swift | 7 +++++- TFITests/MobileForwardingRenderingTests.swift | 8 ++++++- .../MobileHostIntegrationRenderingTests.swift | 8 ++++++- TFITests/MobileSavedNameRenderingTests.swift | 8 ++++++- 9 files changed, 53 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f9d64cba..008e45d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,8 @@ macOS tests that present windows or activate the app must use `.requiresVisibleW StoreKit tests require manual authorization and are also skipped by default. Run them explicitly with `TERMIND_STOREKIT_TESTS=1`, preferably with `-only-testing:TFMTests/StoreKitTrialTests`. +Mobile key-window integration suites are skipped by default because they share UIKit's process-wide presentation state. Run them explicitly with `TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1` (Xcode forwards it as `TERMIND_MOBILE_UI_TESTS=1`), `-parallel-testing-enabled NO`, and narrow `-only-testing:TFITests/` filters. + ## Localization Guidelines Put user-facing app text in `Frameworks/Resources/Localizable.xcstrings` and bundle metadata in `InfoPlist.xcstrings`. Text owned by the agent runtime lives in `../AgentKit/Localizations/Localizable.xcstrings`; after editing it run `../AgentKit/Scripts/build-localizations.sh`, because `swift build` copies resources verbatim instead of compiling string catalogs. New or changed English strings require finished `zh-Hans` and `zh-Hant` translations in the same commit. Preserve placeholders and Chinese punctuation conventions. Before committing, run `Scripts/check-zh-localization.sh` (requires `jq`); it rejects invalid catalogs, missing or unfinished translations, and spacing errors. Run `AppLanguageTests` for localization behavior changes. diff --git a/FrameworkTests/PortForwardingPresentationTests.swift b/FrameworkTests/PortForwardingPresentationTests.swift index 0067920f..daf401ed 100644 --- a/FrameworkTests/PortForwardingPresentationTests.swift +++ b/FrameworkTests/PortForwardingPresentationTests.swift @@ -26,14 +26,30 @@ struct PortForwardingPresentationTests { #expect(PortForwardingFieldPresentation.showsDestination(for: kind)) #expect(PortForwardingFieldPresentation.directionDescription(for: kind).contains("Destination")) #expect(!PortForwardingFieldPresentation.directionDescription(for: kind).contains("Target")) - #expect(PortForwardingFieldPresentation.directionDescription(for: kind).contains("This Mac")) - #expect(!PortForwardingFieldPresentation.directionDescription(for: kind).contains("This Device")) + #expect(PortForwardingFieldPresentation.directionDescription(for: kind).contains(localDeviceName)) + #expect(!PortForwardingFieldPresentation.directionDescription(for: kind).contains(otherDeviceName)) } @Test func dynamicForwardingUsesARequestedDestinationWithoutFixedDestinationFields() { #expect(!PortForwardingFieldPresentation.showsDestination(for: .dynamic)) #expect( PortForwardingFieldPresentation.directionDescription(for: .dynamic) - == "SOCKS5 on This Mac → SSH Host → Requested Destination") + == "SOCKS5 on \(localDeviceName) → SSH Host → Requested Destination") + } + + private var localDeviceName: String { + #if os(macOS) + "This Mac" + #else + "This Device" + #endif + } + + private var otherDeviceName: String { + #if os(macOS) + "This Device" + #else + "This Mac" + #endif } } diff --git a/TFI/App/MobileAppModel.swift b/TFI/App/MobileAppModel.swift index 8c4f86df..efc4a2a6 100644 --- a/TFI/App/MobileAppModel.swift +++ b/TFI/App/MobileAppModel.swift @@ -122,7 +122,7 @@ final class MobileAppModel { private var scenePhases: [UUID: ScenePhase] = [:] convenience init( - hosts: (any HostRepository)? = nil, inMemory: Bool = false, defaults: UserDefaults? = .standard, + hosts: (any HostRepository)? = nil, inMemory: Bool = false, defaults: UserDefaults? = nil, background: any MobileBackgroundTaskManaging = MobileBackgroundController(), workspaceConnectionServices: MobileWorkspaceConnectionServices = .live, appReview: AppReviewController = AppReviewController() diff --git a/TFITests/MobileAdaptiveLayoutTests.swift b/TFITests/MobileAdaptiveLayoutTests.swift index 31299110..acf0eef3 100644 --- a/TFITests/MobileAdaptiveLayoutTests.swift +++ b/TFITests/MobileAdaptiveLayoutTests.swift @@ -1,3 +1,4 @@ +import ListViewKit import SwiftUI import TermindApplication import TermindDomain @@ -86,7 +87,7 @@ struct MobileAdaptiveLayoutTests { try await withRoot(app: app, scene: scene, language: language, regions: regions) { controller in let composer = try #require(textInput(containing: conversation.composer.draft, in: controller.view)) let input = try #require(composer as? any UITextInput) - let transcript = try #require(find(UICollectionView.self, in: controller.view)) + let transcript = try #require(find(ListScrollView.self, in: controller.view)) #expect(composer.becomeFirstResponder()) input.setMarkedText("输入中", selectedRange: NSRange(location: 3, length: 0)) try await settle() @@ -99,7 +100,7 @@ struct MobileAdaptiveLayoutTests { regions.regions = [region] try await settle() #expect(scene.assistant.conversation === conversation) - #expect(find(UICollectionView.self, in: controller.view) === transcript) + #expect(find(ListScrollView.self, in: controller.view) === transcript) #expect(composer.isFirstResponder) #expect(composer.window != nil) let marked = try #require(input.markedTextRange) diff --git a/TFITests/MobileCommandGeneratorAndUploadTests.swift b/TFITests/MobileCommandGeneratorAndUploadTests.swift index 69a37603..e565c102 100644 --- a/TFITests/MobileCommandGeneratorAndUploadTests.swift +++ b/TFITests/MobileCommandGeneratorAndUploadTests.swift @@ -537,7 +537,7 @@ struct MobileCommandGeneratorTests { } } -@Suite @MainActor +@Suite(.serialized) @MainActor struct MobileUploadBatchTests { @Test func usernameOnlyTerminalUploadRejectsInteractionWithoutOpeningASheet() async throws { let identity = Identity(name: "OTP", username: "user") diff --git a/TFITests/MobileEditorToolbarTests.swift b/TFITests/MobileEditorToolbarTests.swift index 76611776..a277831b 100644 --- a/TFITests/MobileEditorToolbarTests.swift +++ b/TFITests/MobileEditorToolbarTests.swift @@ -5,7 +5,12 @@ import UIKit @testable import TermindIOS -@Suite(.serialized) @MainActor +@Suite( + .enabled( + if: ProcessInfo.processInfo.environment["TERMIND_MOBILE_UI_TESTS"] == "1", + "Set TERMIND_MOBILE_UI_TESTS=1 and disable parallel testing for key-window integration tests"), + .serialized) +@MainActor struct MobileEditorToolbarTests { @Test(arguments: ["en", "zh-Hans", "zh-Hant"]) func saveDisablesActionsUntilCompletionAndThenDismisses(language: String) async throws { diff --git a/TFITests/MobileForwardingRenderingTests.swift b/TFITests/MobileForwardingRenderingTests.swift index 2f6df162..dc140082 100644 --- a/TFITests/MobileForwardingRenderingTests.swift +++ b/TFITests/MobileForwardingRenderingTests.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftUI import TermindApplication import TermindDomain @@ -6,7 +7,12 @@ import UIKit @testable import TermindIOS -@Suite(.serialized) @MainActor +@Suite( + .enabled( + if: ProcessInfo.processInfo.environment["TERMIND_MOBILE_UI_TESTS"] == "1", + "Set TERMIND_MOBILE_UI_TESTS=1 and disable parallel testing for key-window integration tests"), + .serialized) +@MainActor struct MobileForwardingRenderingTests { @Test(arguments: ["en", "zh-Hans", "zh-Hant"]) func forwardingUsesVaultCardsAndInPageAuthenticationWithoutASheet(_ language: String) async throws { diff --git a/TFITests/MobileHostIntegrationRenderingTests.swift b/TFITests/MobileHostIntegrationRenderingTests.swift index d2f18344..267ecb6e 100644 --- a/TFITests/MobileHostIntegrationRenderingTests.swift +++ b/TFITests/MobileHostIntegrationRenderingTests.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftUI import TermindApplication import TermindDomain @@ -7,7 +8,12 @@ import UIKit @testable import TermindIOS -@Suite(.serialized) @MainActor +@Suite( + .enabled( + if: ProcessInfo.processInfo.environment["TERMIND_MOBILE_UI_TESTS"] == "1", + "Set TERMIND_MOBILE_UI_TESTS=1 and disable parallel testing for key-window integration tests"), + .serialized) +@MainActor struct MobileHostIntegrationRenderingTests { @Test(arguments: ["en", "zh-Hans", "zh-Hant"]) func integrationPickerKeepsItsDraftAtPhoneWidth(language: String) async throws { diff --git a/TFITests/MobileSavedNameRenderingTests.swift b/TFITests/MobileSavedNameRenderingTests.swift index 307a6213..00527db4 100644 --- a/TFITests/MobileSavedNameRenderingTests.swift +++ b/TFITests/MobileSavedNameRenderingTests.swift @@ -1,4 +1,5 @@ import AgentKit +import Foundation import SwiftUI import TermindApplication import TermindDomain @@ -8,7 +9,12 @@ import UIKit @testable import TermindIOS -@Suite(.serialized) @MainActor +@Suite( + .enabled( + if: ProcessInfo.processInfo.environment["TERMIND_MOBILE_UI_TESTS"] == "1", + "Set TERMIND_MOBILE_UI_TESTS=1 and disable parallel testing for key-window integration tests"), + .serialized) +@MainActor struct MobileSavedNameRenderingTests { @Test func forwardingNameHasNoPlaceholderUntilAHostIsSelected() async throws { let draft = ForwardingNameDraft() From 4feec3e02111741992e56aaa928496d3aa39aedf Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 17:50:45 +0800 Subject: [PATCH 29/30] Fix upload disconnect cancellation race --- FrameworkTests/HermesMemoryTests.swift | 26 +++++++++++++--- .../MCPServer/MCPUploadCoordinator.swift | 30 ++++++++++++++----- TFMTests/MCPUploadTests.swift | 18 ++++++++--- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/FrameworkTests/HermesMemoryTests.swift b/FrameworkTests/HermesMemoryTests.swift index 3d37a4da..ba021eea 100644 --- a/FrameworkTests/HermesMemoryTests.swift +++ b/FrameworkTests/HermesMemoryTests.swift @@ -119,7 +119,8 @@ struct HermesMemoryTests { let history = InMemoryAgentRunRepository() let repository = MemoryIndexedAgentRepository(base: history, memory: model) await model.setEnabled(true) - try await waitUntil { await store.historyReplacementCount == 1 && !model.isIndexing } + await store.waitForHistoryReplacementCount(1) + await waitForIndexingToFinish(model) model.suspend() let conversationID = UUID() @@ -132,7 +133,8 @@ struct HermesMemoryTests { #expect(await store.historyReplacementCount == 1) model.resume() - try await waitUntil { await store.historyReplacementCount == 2 && !model.isIndexing } + await store.waitForHistoryReplacementCount(2) + await waitForIndexingToFinish(model) #expect(await store.lastHistory?.contains(where: { $0.id == conversationID }) == true) } @@ -274,6 +276,7 @@ private actor MemoryLifecycleStore: MemoryPersisting { private let blocksApply: Bool private var applyEntered = false private var applyReleased = false + private var historyReplacementWaiters: [Int: [CheckedContinuation]] = [:] private var applyEntryWaiters: [CheckedContinuation] = [] private var applyReleaseWaiters: [CheckedContinuation] = [] private(set) var reviewApplyCount = 0 @@ -284,6 +287,11 @@ private actor MemoryLifecycleStore: MemoryPersisting { init(blocksApply: Bool = false) { self.blocksApply = blocksApply } + func waitForHistoryReplacementCount(_ count: Int) async { + guard historyReplacements.count < count else { return } + await withCheckedContinuation { historyReplacementWaiters[count, default: []].append($0) } + } + func waitUntilApplyEntered() async { if applyEntered { return } await withCheckedContinuation { applyEntryWaiters.append($0) } @@ -315,7 +323,12 @@ private actor MemoryLifecycleStore: MemoryPersisting { return memory } func snapshot(for id: UUID) -> String { memory.prompt } - func replaceHistory(_ conversations: [StoredAgentConversation]) { historyReplacements.append(conversations) } + func replaceHistory(_ conversations: [StoredAgentConversation]) { + historyReplacements.append(conversations) + let readyCounts = historyReplacementWaiters.keys.filter { $0 <= historyReplacements.count } + let waiters = readyCounts.flatMap { historyReplacementWaiters.removeValue(forKey: $0) ?? [] } + for waiter in waiters { waiter.resume() } + } func searchSessions(_ request: AgentSessionSearchRequest) -> AgentJSONValue { .array([]) } func lastReviewedMessage(_ id: UUID) -> UUID? { nil } func markReviewed(_ messageID: UUID, conversationID: UUID) {} @@ -367,7 +380,7 @@ private actor BlockingMemoryReviewModel: AgentModelCompleting { @MainActor private func waitUntil( - timeout: Duration = .seconds(2), _ condition: @escaping @MainActor () async -> Bool + timeout: Duration = .seconds(10), _ condition: @escaping @MainActor () async -> Bool ) async throws { let clock = ContinuousClock() let deadline = clock.now.advanced(by: timeout) @@ -377,6 +390,11 @@ private func waitUntil( } } +@MainActor +private func waitForIndexingToFinish(_ model: MemoryModel) async { + while model.isIndexing { await Task.yield() } +} + private struct MemoryLifecycleTestTimeout: Error {} private struct FailingDestroyStore: MemoryPersisting { diff --git a/TFM/Application/MCPServer/MCPUploadCoordinator.swift b/TFM/Application/MCPServer/MCPUploadCoordinator.swift index fdc222ab..a3307a80 100644 --- a/TFM/Application/MCPServer/MCPUploadCoordinator.swift +++ b/TFM/Application/MCPServer/MCPUploadCoordinator.swift @@ -177,13 +177,20 @@ final class MCPUploadCoordinator { runID: ticket.info.runID, clientName: ticket.info.clientName, hostID: ticket.request.hostID, remotePath: ticket.request.remotePath, overwrite: ticket.request.overwrite ) + let cancellation = ticket.info.cancellation + // The transfer cancels its own token on any failure, so track HTTP disconnect provenance separately. + let disconnection = TransferCancellationToken() tickets[id] = ticket return .accept( { [weak self] read in guard let self else { return Self.error(503, "upload_unavailable") } - return await self.receive(id: id, attempt: attempt, read: read) + return await self.receive(id: id, attempt: attempt, disconnection: disconnection, read: read) }, - disconnected: { [weak self] in Task { @MainActor in self?.interrupt(id, attempt: attempt) } } + disconnected: { [weak self] in + disconnection.cancel() + cancellation.cancel() + Task { @MainActor in self?.interrupt(id, attempt: attempt) } + } ) } @@ -220,9 +227,10 @@ final class MCPUploadCoordinator { tickets.removeAll() } - private func receive(id: UUID, attempt: UUID, read: @escaping SFTPStreamUploadOperation.ReadChunk) async - -> HTTPResponse - { + private func receive( + id: UUID, attempt: UUID, disconnection: TransferCancellationToken, + read: @escaping SFTPStreamUploadOperation.ReadChunk + ) async -> HTTPResponse { guard let ticket = tickets[id], ticket.attemptID == attempt, ticket.info.state == "connecting", !ticket.info.cancellation.isCancelled else { @@ -244,8 +252,14 @@ final class MCPUploadCoordinator { return Self.json(200, payload(completed)) } catch { let cleanup = error as? SFTPTemporaryFileCleanupError + let cancelRequested = tickets[id]?.info.cancelRequested == true + let interrupted = + tickets[id]?.interrupted == true + || disconnection.isCancelled + if interrupted { tickets[id]?.interrupted = true } let (status, code) = Self.failure( - cleanup?.cause ?? error, cancelled: tickets[id]?.info.cancelRequested == true) + cleanup?.cause ?? error, + cancelled: cancelRequested || disconnection.isCancelled) tickets[id]?.temporaryPath = cleanup?.path if let failure = (cleanup?.cause ?? error) as? SFTPStreamUploadOperation.Failure, case .digestMismatch(let actual) = failure @@ -253,9 +267,9 @@ final class MCPUploadCoordinator { tickets[id]?.actualSHA256 = actual } let canResume = - tickets[id]?.info.cancelRequested != true + !cancelRequested && !Self.integrityFailure(cleanup?.cause ?? error) - && (tickets[id]?.interrupted == true || Self.recoverable(cleanup?.cause ?? error)) + && (interrupted || Self.recoverable(cleanup?.cause ?? error)) && ticket.info.expiresAt > now() if !canResume { await cleanupTicket(id) } finish( diff --git a/TFMTests/MCPUploadTests.swift b/TFMTests/MCPUploadTests.swift index f6ce6a12..4264ee69 100644 --- a/TFMTests/MCPUploadTests.swift +++ b/TFMTests/MCPUploadTests.swift @@ -359,10 +359,7 @@ struct MCPUploadTests { return Data([1]) } } - for _ in 0..<100 { - if await fixture.client.started { break } - await Task.yield() - } + #expect(await waitUntil { await fixture.client.started }) switch reason { case "disabled": fixture.uploads.setEnabled(false) case "session": await fixture.uploads.endSession(runID: fixture.runID) @@ -375,6 +372,19 @@ struct MCPUploadTests { #expect(await fixture.client.committed == nil) #expect(!fixture.uploads.hasActiveUpload(runID: fixture.runID)) #expect(fixture.history.entries(tool: "http_sftp_upload").first?.status == .error) + if reason == "disconnect" { + #expect( + try fixture.uploads.status(id: fixture.id(ticket), runID: fixture.runID).objectValue?["status"] + == .string("paused")) + } + } + + private func waitUntil(_ condition: @escaping () async -> Bool) async -> Bool { + for _ in 0..<200 { + if await condition() { return true } + try? await Task.sleep(for: .milliseconds(10)) + } + return false } @Test(arguments: [true, false]) From 1ffd25eebeb83980b334e7cf1d616f4fa58f0504 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Wed, 23 Sep 2026 18:25:51 +0800 Subject: [PATCH 30/30] Refresh architecture documentation --- .../architecture/engineering-invariants.json | 62 ++++--- openwiki/.claims/architecture/mobile-ui.json | 72 +++++++- openwiki/.claims/architecture/module-map.json | 8 +- .../architecture/native-app-directories.json | 8 +- .../architecture/platform-runtime.json | 74 ++++++--- .../supporting-system-directories.json | 44 ++++- .../architecture/system-boundaries.json | 10 +- .../concepts/agent-runtime-and-assistant.json | 106 ++++++++---- ...rce-localization-and-product-surfaces.json | 18 +- .../concepts/persistence-and-secrets.json | 8 +- .../ssh-multiplexing-and-scheduling.json | 36 +++- .../concepts/ui-design-principles.json | 30 +++- .../integrations/mcp-server-and-bridge.json | 32 +++- .../operations/build-test-and-release.json | 44 ++++- .../storefront-commerce-release.json | 18 +- openwiki/.claims/quickstart.json | 40 ++++- .../behavioral-boundaries-and-fixtures.json | 58 ++++++- openwiki/.last-update.json | 4 +- openwiki/.page-manifest.json | 156 +++++++++--------- .../architecture/engineering-invariants.md | 22 ++- openwiki/architecture/mobile-ui.md | 20 ++- openwiki/architecture/module-map.md | 2 +- .../architecture/native-app-directories.md | 2 +- openwiki/architecture/platform-runtime.md | 14 +- .../supporting-system-directories.md | 18 +- openwiki/architecture/system-boundaries.md | 2 +- .../concepts/agent-runtime-and-assistant.md | 12 +- ...merce-localization-and-product-surfaces.md | 6 +- openwiki/concepts/persistence-and-secrets.md | 2 +- .../ssh-multiplexing-and-scheduling.md | 14 +- openwiki/concepts/ui-design-principles.md | 14 +- .../integrations/mcp-server-and-bridge.md | 8 +- openwiki/operations/build-test-and-release.md | 32 +++- .../operations/storefront-commerce-release.md | 8 +- openwiki/quickstart.md | 14 +- .../behavioral-boundaries-and-fixtures.md | 27 ++- 36 files changed, 781 insertions(+), 264 deletions(-) diff --git a/openwiki/.claims/architecture/engineering-invariants.json b/openwiki/.claims/architecture/engineering-invariants.json index faf78a73..8282065d 100644 --- a/openwiki/.claims/architecture/engineering-invariants.json +++ b/openwiki/.claims/architecture/engineering-invariants.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:ced917378133d346e25f9793e04d360a19ff15bd567435b4183562c3dc9fab09", + "pageVersion": "sha256:35da0200127e36e0161d9ebfabc103070580eff4d348f77b92091549c2d15e27", "claims": [ { "id": "claim_72f9329a158e418cbf0fd6394f82815e", @@ -44,7 +44,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -440,7 +440,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L577", - "version": "repo-lines-v1:sha256:a270cc13b36412458b74942420cae0e8abaa6638637ae686fba94b336f5c53ed:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + "version": "repo-lines-v1:sha256:b0fa484bcb19ae7f887f203d3b438c9bccc7b5936706cd85d285e53c0f9b31d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L158", @@ -484,32 +484,20 @@ }, { "id": "claim_0041eaa146234943af67a02b8797e991", - "statement": "The macOS termination reply independently falls back after three seconds and replies once; captured-turn joins do not guarantee save success, SSH setup completion, remote-command settlement, or drainage of manual compaction, title generation and memory indexing, and successful terminal saves may schedule memory indexing after suspension.", + "statement": "The macOS termination reply independently falls back after three seconds and replies once; captured-turn joins do not guarantee save success or remote-command settlement and exclude manual compaction and title generation, while memory suspension now remains an admission fence until resume reloads state and backfills history.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", - "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L177-L204", + "version": "repo-lines-v1:sha256:463d4609d3eaa2a49b28fa86063677b92b26cb06a7c30dc499260f0aa1a5278c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", - "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", - "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" }, - { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", - "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" - }, - { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", - "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" - }, { "resource": "repo://TFM/App/AppModel.swift#L724-L777", "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" @@ -519,10 +507,42 @@ "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" } ] + }, + { + "id": "claim_70b3d256afb7415ba484d3c1e0cd7e02", + "statement": "Shared SSH configuration and pooled connection openings register one continuation per waiter; cancelling one waiter leaves shared work alive for the others, and only the last waiter or explicit Host retirement cancels the underlying opening task.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1638-L1774", + "version": "repo-lines-v1:sha256:23aaca80cf4e2215a2f75373a0dcf3b4d6ee051aa3b90f8a3ad089893590f8a8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTM3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhZTE1M2Y3ZDQ0ZmFiNGE1NDY3NzdmOTE0YjZkZDBlMDIyMjEwNGYzOTk2NDk3YTA2MzQyZmExYmVlM2FlNTVmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmIwNDQ5MTQ2OTcyMmU4MzYzYTk0ZTQwNDQ1MzhlMDEzYTJhMjE3ZTM2YjFlNTY4YmE2MGFjYjdiNGM4ZjJlIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkNmMyYzFhM2IyZWMzOGYxOTBiZDc5ZTNjNjczYjhjZDVhOTFjMmRhMmE4ZDgzODM3ZWY4MzRlYTc1N2Y2YjA3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3NDcyMmRmYjk2MDYxMDVlNTFhZjZkZDllYzI0YjNjZGE4ZDQ1MDU0NzA1YzUxN2UwNGU4OTJlM2U1YzQ2MjZhIn0" + }, + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L700-L774", + "version": "repo-lines-v1:sha256:9bfcbf42450fdb97123b2ad5a7f98b81d110c3f3314600403d93ad7b04860dab:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwMzg4ZDNjZDU0YTVlOGYxZmVkZjlmOGY0ZjYxYTBiYWNkMDI1YWU0NGE1MDUzMDMxYjExNzU1YTQ5MmI1NzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZTI3M2YyMzE3MjgwZjUwYWJkMzkyMzQxNTg4YTNiOWVmOGRhZjA3YjI1YTkxNmMyMmFlZGQxYWVhMDdlNzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhNzc4Yjc5MjgwMzkyYWQyMjYzNjJmYzk5OGQwNGJhNzdhZjU4ZmYzYWQ2MWYyZjc2NzA0ZWZjMjNkNWU2YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" + } + ] + }, + { + "id": "claim_2db38fa50ac84875baa964c4ad074ab2", + "statement": "An MCP upload disconnect synchronously cancels the attempt's transfer token and separately records disconnect provenance, so the response maps to cancellation and remains resumable without treating unrelated SFTP failures as HTTP disconnects.", + "evidence": [ + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L177-L197", + "version": "repo-lines-v1:sha256:09225b7b105df303acc8396e48c42c5251ddf2feff12b03d2c52d91c79fb352e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJkOTVmZTFiOTA3OGRkZDkzZWExMWM5MjlhODc4MzAyMzQwOWVmYWIyMDdmMDcwNmRkZDllZGM4NDJlZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI2NWU4NzRiZTc5MTIwY2ExMDZjMzcyZmM5ODlkNjZiZWQxOWMzZTQ0ZjRjMmIzNjJiZjUxMmU5ZWZhYWI4MjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyZTVkNGJlYjE4YmNmZTY3NjVlZTBlYzQ4MjZhODg2ZWY0ZTFlMGNiYjc3NTE0ZmQ2M2ZhOTIwNDRmNzEzODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MTFhMDA0NWFiYzgzOWNjYzM0YjVkZjRkYWNiY2ZiYTFjMmUyMjBlOTQ0NDM1MTM0ZTY1NjA1YmJmNGU0MmYifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L251-L276", + "version": "repo-lines-v1:sha256:3792a7f25d2ec146b7b352c37c6e4a5fd9883de3c0451915c4f7da84dbc9887e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMWQ3MjAzODMyYmQ5ZTZlNDY5ZjUzYTdkMWQ2MWE5YjM3NTQxOTIyNzliZTY0OTYyYTg5MDVlNzAyNWMzNjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5ZDk1ZWE2MmVhNjlkZmZkZTJmYmI1ODE4MDZhNzgyMTViODcxYzU3ZTliZWZmNjMxN2YxN2YwYjVmMmM1YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiODM5NTZmZDAzNzU5M2UyNzUyMGYwN2I1ZDQwYzhiZWRjOTkyMTVlNDMxMTA0ODA0NWZiYjJmY2IwMGZlMzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUxODUyZWMwMmU4N2ZiYTQ5MDQ1OGJlNDc0YWY1ZDliYmJhZjk0NWJmYjQ4MzhjNmIxY2Y5NGVmYjRmMWZmYmYifQ" + }, + { + "resource": "repo://TFMTests/MCPUploadTests.swift#L349-L385", + "version": "repo-lines-v1:sha256:abaaf8c46f62023236e935d4b7d44779766e48bbd0cc68f16f6592abfb97c637:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRlMDQzYTczOTBlMWNhN2FhMDU2ZTk1YWFmMjE2NTM5YTIyNTkwNjU2ZGI4ZmIwNmI2ZWU5YzMyMWMxMjNlYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlZDdlYjk1NWQzOTAwMmZhMTlmM2MzNTJmYzhmNDNiMTg1MDNiYjRjYjIyNzhiNTFlMjFlMjdkMWFiYjU3ZTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWZjNmY2NzhjN2U5Y2EwY2YxOTY1YmVjZWUzMjA5OWEzNzRlZTc0N2Q2NTE2ZWViZWFiMzQ2NDU0NGVhMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3N2U0MDI5NjM5ZmIzOGQxZGRmYzZjMjllOWJiZTRkNGMxMWVmMjBlOTRiZmM4ZWE5YTdjYzI3NmVlYjUxOTMifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/mobile-ui.json b/openwiki/.claims/architecture/mobile-ui.json index 944c8fad..db20fcee 100644 --- a/openwiki/.claims/architecture/mobile-ui.json +++ b/openwiki/.claims/architecture/mobile-ui.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3e4fffdec19e331d34ec12f5b4ead52c83e7d4d17e8f0937e8ba884b0d26ed34", + "pageVersion": "sha256:6f758688dba0fd6ecc5da1905671f8372325879f816a81755431c0cc4469c04e", "claims": [ { "id": "claim_fbb919f62a2d4ab88aafcc4c1186731b", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -160,7 +160,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", @@ -178,7 +178,7 @@ "evidence": [ { "resource": "repo://TFITests/MobileAdaptiveLayoutTests.swift#L9-L180", - "version": "repo-lines-v1:sha256:fd1dfb3439ec991b16afb53f24aebc218d39fe34cb89144a79d75a4fa14cad8c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2MTcxN2I0ZjY1MWU3NTQ5ZTUzN2QxZDg3ZGNmODNhNWRiYjkxODAzNmJjZDExMDRkYWM4M2QxMmNkNzZlNTYzIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMGE4ODZkMjJlMjhmYThjZmFkMzg2ZTUzMzlmN2JhNGFmNDEzM2U4NGRmMjU5YTYwNTNjNjJiMzI4ZjQ2Y2U3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJiY2JlNmZkZjFkNDEyOTVmNjUwMmJhODFjNWQyYjdmZTdkOWZhMDFmZTU0NWQ3ZTZhMmFiY2M5YmUzMjRiOGI0IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJjMTNhOTQ2OThjNjNhZjM5MmJkMWI0YTZkZWIwZWI5ZjdmYzFlNWVjZTQ0MGM1OTU3MTdkYmEyYjM2ZGJjYTU4In0" + "version": "repo-lines-v1:sha256:81dac028bbcfd23568873033feb932627182fdd7dba20e6b887a28fc7cbd83ca:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI2MTcxN2I0ZjY1MWU3NTQ5ZTUzN2QxZDg3ZGNmODNhNWRiYjkxODAzNmJjZDExMDRkYWM4M2QxMmNkNzZlNTYzIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMGE4ODZkMjJlMjhmYThjZmFkMzg2ZTUzMzlmN2JhNGFmNDEzM2U4NGRmMjU5YTYwNTNjNjJiMzI4ZjQ2Y2U3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJiY2JlNmZkZjFkNDEyOTVmNjUwMmJhODFjNWQyYjdmZTdkOWZhMDFmZTU0NWQ3ZTZhMmFiY2M5YmUzMjRiOGI0IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJjMTNhOTQ2OThjNjNhZjM5MmJkMWI0YTZkZWIwZWI5ZjdmYzFlNWVjZTQ0MGM1OTU3MTdkYmEyYjM2ZGJjYTU4In0" } ] }, @@ -252,21 +252,79 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFITests/MobileAdaptiveLayoutTests.swift", - "version": "repo-file-v1:sha256:6062bded1a57eef49e3f62a7e11af7921c06564a64b679eb0347da7f09237aeb" + "version": "repo-file-v1:sha256:e9acaf0cde5c2fc6c94b78739cfcdd483f74e97266a4a750b1743e93b05de665" }, { "resource": "repo://TFITests/MobileAppTests.swift", "version": "repo-file-v1:sha256:b2c528520ad5272155355404f91af52e490c6f17780a845d9e8a596c523484ef" } ] + }, + { + "id": "claim_506fae6d86df4792826c6839024000b4", + "statement": "Mobile backgrounding keeps Hermes memory suspended until an active scene resumes it, and resume reloads committed state and schedules history backfill; the fence rejects sessions, edits, reviews and index starts while suspended.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L166-L210", + "version": "repo-lines-v1:sha256:01dacfbd3e9893e55161de481ae823baa1c00522c74314ff218ba47cdcdfc670:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlZjA2ZTY0Y2I2ODdiNjc5NDFhMjAxMWYxNDkwNGM4YTFlNmU2NjViYzc1M2FmNjhhMTBiMzQ1NmVjNjA4NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJhMTAwZmFlNmRmYzYyMDcxYmM0ZWU4NzMyNDkxZDM0Zjc0ZTBlNDQ1YjU0MDIwODVhOWNmMjY1MTg5ZTA4M2YiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRkZmIwY2MzNmY4OTdhZjM4NGY1ZDlkODVlZDU4MjMwOTBjNTcxYjBkNjZkMjUyODZmMjM0ZGVjMWQ5MDY2Y2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiY2ZlZTE2NTRhNWI0ZmJmOGVjMGMzOTEzZmUyOTUyZmUyNGZlZmIyYWZhOGVlNzBkM2QzZDI0ZjMyOTFhM2YifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:b0fa484bcb19ae7f887f203d3b438c9bccc7b5936706cd85d285e53c0f9b31d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L647-L663", + "version": "repo-lines-v1:sha256:be3d7ace4663375024627ad072542aab75a2ee635ebaf20ee80f0a63f51e0d0d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZDY0ZTIzNWJjNzZlYTM1MDI3NmE4MGZlMGM1NjM0ZTYwZDFkZmUwN2FjYWQ4NTJlMTFhMWQ2MjkzM2ViMTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhjYWNjN2E0NzlkMjI5N2FiYTI4NWJlYWFlMThhZmI4ZWE3YmQzODEwMTkxNTA3MTIwYmRhNzVjNjBhYTBhZTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMyMWFjNTE4NDc5MWIzNWNkZTM4MDkzNDU1MTVjMGFjMDEwZjA3MGVhZTAyMTRjZGY0MzIwZDE4NzI5N2M3MDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI5ZjIwMmJmNzJkZjZiODUwNjM5ZmNjZmRiZjY4ZDJhMDdmZWY0ZjMzYThlZjQ3MWM0OWUwMDEwY2Y0YmRhYWQifQ" + } + ] + }, + { + "id": "claim_537842bf0ce14cf58f3ec5a37228eaba", + "statement": "The four mobile UIKit key-window integration suites are opt-in and serialized; they require TERMIND_MOBILE_UI_TESTS=1 and a test invocation with parallel testing disabled.", + "evidence": [ + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] + }, + { + "id": "claim_85460ef46a6b4179ba50bff519dab4a9", + "statement": "Mobile Assistant suspension blocks sending while its retained-owner drain is active and synchronously requests cancellation for every conversation before awaiting persistence and resource cleanup.", + "evidence": [ + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L155", + "version": "repo-lines-v1:sha256:8c82421c493d71a46a31cea25dee42618562d9e3080c889c363ebef1d1f47b25:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzNjFmYmU2YTJkNzg3MDAzYjc5NDliMDY4NTFjOGJlNDczNTI3MDNiZjAwYmZkYjM5YTVlZmEwZDAxYTdkNmQifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:65515a4e9bdd6ebad25be3bccb372ded2a1a7af650a3a47ab525c5daa87a2c49:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxNWNkMzA5ODJjNzNiNzE4ZGVlNzZmNTU0MmRlNTY4NjBmYzMzYjdkM2QzOTk5NTA3YWVmNjRiYTNiNTgyOTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQxOWUxMzBmOWU5ZWJmMzY5ODJhNzdjYzg1M2M3NGQzNWNlZjVmNDgyOWEyYTA5NmM0OWM3ODBmZmFjNjFlZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjODI0MDgxOWY3NzlmNjQ5ODAwYjY3ODkwMjEzOWQzNDM0MTFhZTI3YjA4OWU3YWJiOTFlYThlYTAzYTQ3NjgifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/module-map.json b/openwiki/.claims/architecture/module-map.json index 3e673f61..8a931c09 100644 --- a/openwiki/.claims/architecture/module-map.json +++ b/openwiki/.claims/architecture/module-map.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:eebf0a77f06515f063a173fdf53ac96cca81aeae0519a99c3c2697d94c59429d", + "pageVersion": "sha256:4f2820877ec3b78a77319f48a9cfee8f397b3bf2216d9de35fce3ebdf601f2c9", "claims": [ { "id": "claim_34230e88036241baad093fb8de5bac8e", @@ -32,7 +32,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -140,7 +140,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -151,6 +151,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/native-app-directories.json b/openwiki/.claims/architecture/native-app-directories.json index ac96cbca..1b23852a 100644 --- a/openwiki/.claims/architecture/native-app-directories.json +++ b/openwiki/.claims/architecture/native-app-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:7b943e2d155437d61aafb7681db058ef94efb0afbdab534a552eeaee29dc8413", + "pageVersion": "sha256:71a3c6e74feb945c25eb8f6d8025534596d9d7cdb41b67b79887004ca006d40d", "claims": [ { "id": "claim_8e9cf2adb50847f98fcd4516cb0ed521", @@ -60,7 +60,7 @@ }, { "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift", - "version": "repo-file-v1:sha256:303d0a53d1bfa67f4ce602a193bc9c1e917b9f2075f5654acf19e9166dee9f1f" + "version": "repo-file-v1:sha256:d4b9205cc908ce2c38eb1e82ca1ea0a84a45794fa0613b0e794177ae9e48a48a" }, { "resource": "repo://TFM/Infrastructure/MCP/MCPServerSession.swift", @@ -216,7 +216,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFM/App/TermindApp.swift#L285-L294", @@ -231,6 +231,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/platform-runtime.json b/openwiki/.claims/architecture/platform-runtime.json index 9f794aae..ce42143d 100644 --- a/openwiki/.claims/architecture/platform-runtime.json +++ b/openwiki/.claims/architecture/platform-runtime.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:ed3f410b516491c6b31b18037bf8c9a0274dc08a50a13080e5d7906c041da1af", + "pageVersion": "sha256:63336e85b6d432a53dd31486e99e84d3cb86d592f42771810bab0fc8e9e52392", "claims": [ { "id": "claim_bd9f5ddda6b6466eb79c622b02fd73f2", @@ -128,7 +128,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" } ] }, @@ -156,7 +156,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFI/App/MobileSceneModel.swift", @@ -192,7 +192,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", @@ -228,7 +228,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/Features/Workspaces/Pane.swift", @@ -250,7 +250,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFM/App/TermindApp.swift#L285-L294", @@ -286,7 +286,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L158", @@ -334,35 +334,27 @@ }, { "id": "claim_d0076b157c24486fadd4f8c45203ef7f", - "statement": "The desktop captured-turn drain is best effort: a joined save attempt may fail, shared SSH authentication/pool setup may not settle from runtime cancellation, background-command close only requests cancellation, and existing compaction/title/index tasks are not joined; successful terminal saves can schedule indexing after memory suspension, while fallback can reply before later cleanup runs.", + "statement": "The desktop captured-turn drain is best effort: joined save attempts may fail, background-command close only requests cancellation, manual compaction and title generation are not joined, and shared SSH openings remain alive while other waiters exist; memory suspension now stays fenced until resume reloads state and backfills history rather than allowing terminal saves to restart indexing.", "evidence": [ { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L784-L820", "version": "repo-lines-v1:sha256:3a8d4faf591d7bce9a67365af3b6cc4e062c09c1dbfd76139ac4ecaa6412c6f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5NDc3Yzk3MjM2ZjdjMzNlNGNlYmM4Y2Y0MzQ4NmEwYzU3YmQ1ZjFhMWFlZTY1ZTc4YzM1ODZkNmIxM2Q3MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3NWRkNzE5N2NkYzMzYzYwMDAwODY3MmM2NjEyZGFmNWUyMmMzMzAyNDdhM2FmMGJmODJlNDgwMmYzNmVlMjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MTVmNGViNzI5YTZmMGMwMDk2MWMyN2JhMzFjNmU2ZGQxNWIwNTFkOTRhOGI5NzY3YWQ4Zjg3NGZmODBjZDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjNmM3Y2RjOGM0ZWMwYmM5ZGU4ZTY5MThmYWUwYWE5ODQ0MTJiNDJjZTVjMzY5ZWExZTMzMjg0N2Y4ZjIyOWIifQ" }, { - "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", - "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L177-L204", + "version": "repo-lines-v1:sha256:463d4609d3eaa2a49b28fa86063677b92b26cb06a7c30dc499260f0aa1a5278c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", - "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" - }, - { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", - "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" }, { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", - "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" - }, - { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", - "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1675-L1774", + "version": "repo-lines-v1:sha256:4847b31059874bdbaee91f89d037bedc3d8dd27b943965835fd217cd28a0a7e3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxYjhiOGI5NmRlMjNiYjM5ODI5OGU1MjIxODE1ZGI2MjViZGYyYzgxYjYwYTQ2ZGJiM2VjMmQzNDJmMzU0NTUwIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmIwNDQ5MTQ2OTcyMmU4MzYzYTk0ZTQwNDQ1MzhlMDEzYTJhMjE3ZTM2YjFlNTY4YmE2MGFjYjdiNGM4ZjJlIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5NjQ1MzUwNzkyMTM0YjdhMWIwYjM0YzM5MWU4Nzc3YTI3Mjk4NmEyNjQyYWI0M2E3M2M3YThiYjI5MTcwZTRlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3NDcyMmRmYjk2MDYxMDVlNTFhZjZkZDllYzI0YjNjZGE4ZDQ1MDU0NzA1YzUxN2UwNGU4OTJlM2U1YzQ2MjZhIn0" }, { "resource": "repo://TFM/App/AppModel.swift#L724-L777", @@ -373,10 +365,46 @@ "version": "repo-lines-v1:sha256:95d9d41a8e9a63b0bbdf8354b715f24f754a017b597e2b2228218077a6ed4d25:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkNjgzYjc5NGVkOWI1NjQ2Y2Q2ZTZjMGEyYmMzYTk4YTFjN2NiYmMzMDUxZWZiN2E0NWQzYWI5MWI0MDRhZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwMWZjNjEwMWEwZjJiMGNiNmFkNTEyYzg2MzExYzEyNzllM2VlZjMwNWExMDQwMTc5MzUzMTQ4N2MxYTllNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZTJhYTI2ZTg3N2YxZWE5MzA4NjE5NmU0Nzg2N2U5MGYzNTk4ODI2MDA0MmY2YTM1YzEzZTlkNDRlOTVhM2MifQ" } ] + }, + { + "id": "claim_debf1f28fcf94eaeacdf2c9260ce0947", + "statement": "Mobile Assistant background expiration blocks send, edit, plan implementation and manual compaction until its retained-consumer and connection cleanup task returns, even if a scene becomes active first.", + "evidence": [ + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L155", + "version": "repo-lines-v1:sha256:8c82421c493d71a46a31cea25dee42618562d9e3080c889c363ebef1d1f47b25:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzNjFmYmU2YTJkNzg3MDAzYjc5NDliMDY4NTFjOGJlNDczNTI3MDNiZjAwYmZkYjM5YTVlZmEwZDAxYTdkNmQifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L180-L260", + "version": "repo-lines-v1:sha256:c377573e0d98f0eb75f9769c7e3753d2c3bb1fdc6bb040c7a00bc27da2b71ae6:eyJzZWxlY3RlZExpbmVDb3VudCI6ODEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5NmFiZTAwOGM2OTQ0YjBhMzJiZTcyZDM2Y2ZmMjNkMDc0MmE0MDEwMDIzY2I4NmFhNjYwNjAxMGFmMWM3NmQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmM2UzN2Y2NzljZDY2NzAyYjcxZjNmOThkN2IyYjYyZTAwMGEzM2FkYjQ4OTE1NDdjNjZhOTJkNDEyNGIxNzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA0OWUyMjI0YTkyOGRhNTQ1YmEyMjYxOGQ1MzVlYmVlZWNlZGYyMmJhMzJlNzhhMmY4ZGQyMDU2ZDQxN2EyMzUifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:65515a4e9bdd6ebad25be3bccb372ded2a1a7af650a3a47ab525c5daa87a2c49:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxNWNkMzA5ODJjNzNiNzE4ZGVlNzZmNTU0MmRlNTY4NjBmYzMzYjdkM2QzOTk5NTA3YWVmNjRiYTNiNTgyOTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQxOWUxMzBmOWU5ZWJmMzY5ODJhNzdjYzg1M2M3NGQzNWNlZjVmNDgyOWEyYTA5NmM0OWM3ODBmZmFjNjFlZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjODI0MDgxOWY3NzlmNjQ5ODAwYjY3ODkwMjEzOWQzNDM0MTFhZTI3YjA4OWU3YWJiOTFlYThlYTAzYTQ3NjgifQ" + } + ] + }, + { + "id": "claim_0e5b894c36874fc79d38240142ad81e8", + "statement": "An active mobile scene resumes Hermes memory by reopening admission, reloading committed state and scheduling history backfill before later memory work uses the new generation.", + "evidence": [ + { + "resource": "repo://FrameworkTests/HermesMemoryTests.swift#L116-L181", + "version": "repo-lines-v1:sha256:ce72d05810dc1671686e744e8d2fdaf825e3662b3dd2f726191909a92e01ab3a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3MjA1MzIyZDIyNTY5NmVkODY4OGRlYzk5N2NjNGMyMzZlN2M4ZDVhMDY4ZjBmOGEwNzg0NWJkYzk4NWJhYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkMmZlMGM5ZTQ2MGJjYjM4YTQ0YzY4YTY2OWNlZWY3NDA4YWExNjcxMmUwMjZlMmFmZGI0Yzk4NTc5YzlmMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0ZWMyOTlmNTE0NWQ5M2I1YWM4OWZmMzVlOTE2YjE5ZDE3ZTA3NTg0MWQ1ODIwODI4YzdlMjJjZDMzZjdiYzYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" + }, + { + "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", + "version": "repo-lines-v1:sha256:b0fa484bcb19ae7f887f203d3b438c9bccc7b5936706cd85d285e53c0f9b31d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0NTg0ZDFiMmZhZDA3YjA0ZTIwZTVjOGE2MTM3OGEzNDFjZGJlN2E4ZDgwMzAwZDQ2NzJlNjhhYjcxZTFjZjMifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/supporting-system-directories.json b/openwiki/.claims/architecture/supporting-system-directories.json index 40491e82..bf37e1c8 100644 --- a/openwiki/.claims/architecture/supporting-system-directories.json +++ b/openwiki/.claims/architecture/supporting-system-directories.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:bc62050f5799c6e7764ff9d08a0e721dda371eb74b7b8342346c59f747abb3b5", + "pageVersion": "sha256:6971c304cb02eb6f308dbbbc6833b74c5a84b608c4e4a8435a9716000c404ec9", "claims": [ { "id": "claim_8809151c43594803b83cf0451d849112", @@ -241,10 +241,50 @@ "version": "repo-file-v1:sha256:6215ae5d2e32c9f76d70d6401196b14937f7be4b1e06b67db2297f3b81d86aa4" } ] + }, + { + "id": "claim_8e381cddf0fb4803b848b245806f77f1", + "statement": "StoreKitTrialTests is a separately gated desktop suite that stays disabled unless TERMIND_STOREKIT_TESTS=1 because running it requires manual StoreKit authorization.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L56-L58", + "version": "repo-lines-v1:sha256:59b48c1c93af632d0d54f375f34be63bfe084da21dbf98d89f2c5174f4f566ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWYxODBmOWMxMjY5MDNiY2MzMzFhYTk1NDU2ZTQ5NDQ0YTdkNGQwMTFlOGEyYTgzZjVkYzg3MDIzOTE4MTJkMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjQ4MzZlZDdlMDA1Mzc3NWEwYTk2NmNlODYxMzRiMjM0N2Q4NTFhN2IwMmY0ODEyYmViMmM1OWQxYzY4ZDUyNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGMwNmIzZDc5NGYzMzczOTYxOGNhZmVjOTQwY2ZkY2YxZjllYjI1ZjI5YjUyYWJjZmNkZjA0MWY3NTg2YWZmYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTA1MTY5OTI5YTliMjNhOWIwMDJiYjE5MDNhYTA3ZWMxZGE0ODA1ZjY3ZTEyNTBmYzZjOTQ4MDlmMzJlMGM3ZSJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L1-L18", + "version": "repo-lines-v1:sha256:01f6bcd9c1be6ab834bd576093f823f2b13ebdc7043b843fc256fa8ab1b51bb3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyMzkxMGFkMTQyYWFlZTQ0YzE0ZGU2YTYxODVkZTBiY2NhNDhlYzZlZGQ3ODFlNWU1MjQzYjdiODk2N2VkMTkifQ" + } + ] + }, + { + "id": "claim_3900a257383845c3891e337c052955f7", + "statement": "Mobile UIKit key-window integration suites are opt-in and must run with parallel testing disabled because they share process-global presentation state.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L50-L54", + "version": "repo-lines-v1:sha256:5119b61ad141f6edb840c60139e371386a18a8cd4b9f5912a76c1c142abedb1f:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODQ1MThlZDQyNWZhZWNhNTgxNGExYzEwMTU0ZjEzMjgxOGViM2VjMGI2ZTgzOThkZjQ5NGI4YWRmN2MzYTI0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTAzYWFlMjllMDYzMTZkNmRhNmY4YzJhOTFmNmU0NGE2MTJmYWJkNzkzYWQxZjVhMWZjYTUyMDZiY2VkYTQ3YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWIxYmM1YmM0YTIzN2Y1NzhlNmFmNGE0MTVkMDY0N2Y5OTEwYjAzYjM2NGUxZTI3YzFmMWRiYWE3NDNkMDNiYSJ9" + }, + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:11:02.802Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/architecture/system-boundaries.json b/openwiki/.claims/architecture/system-boundaries.json index cb4c6b08..47e7a195 100644 --- a/openwiki/.claims/architecture/system-boundaries.json +++ b/openwiki/.claims/architecture/system-boundaries.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:2552fa8187812755085075681b5a5148ef62a16224616146ec131bd1fb880d64", + "pageVersion": "sha256:18ed1963cf31915d010fc6c2937f77cd377fff2d65fae74d1c7ae7c9793f3700", "claims": [ { "id": "claim_085ce722f39f48eea75366ec3b3f7554", @@ -40,7 +40,7 @@ "evidence": [ { "resource": "repo://AGENTS.md", - "version": "repo-file-v1:sha256:d9c994a1f4dde96fe39505281f40e992ce4760d2d6752de29f59060250bbdca9" + "version": "repo-file-v1:sha256:7c19a99ec73d4473ac94bb134b62a1306c62d1285e5b8af26430f1d466de35a7" }, { "resource": "repo://Frameworks/TermindPersistence/HostPersistence.swift", @@ -66,7 +66,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFI/App/TermindIOSApp.swift", @@ -176,7 +176,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -191,6 +191,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/concepts/agent-runtime-and-assistant.json b/openwiki/.claims/concepts/agent-runtime-and-assistant.json index c33af32a..49a367ec 100644 --- a/openwiki/.claims/concepts/agent-runtime-and-assistant.json +++ b/openwiki/.claims/concepts/agent-runtime-and-assistant.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:ee7c8c1d70af9d78c4effdc07f9f5b65fb08c92a14abee40c88be00a9df41c87", + "pageVersion": "sha256:7fbe6994d8040bdf4a14e40af86c0197d0e560cafa0f7f62a0803988dbf4f85e", "claims": [ { "id": "claim_981f16f030e9469588af705aa377161e", @@ -234,7 +234,7 @@ "evidence": [ { "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift", - "version": "repo-file-v1:sha256:3a875082b9ca385a928fcf0e8bda396e66be74ac882859697c6ccf5423058df7" + "version": "repo-file-v1:sha256:53303b99278ef87af5bc0365c6546c10de8ea4cc6462f0bc9e1d39ba048515a7" }, { "resource": "repo://Frameworks/TermindPersistence/GRDBMemoryStore.swift#L123-L224", @@ -252,19 +252,19 @@ }, { "id": "claim_a3ad24a30cc74874af7e95eba686afbc", - "statement": "Disabling Hermes memory invalidates contexts and cancels indexing/reviews while retaining storage, whereas destroy persists disabled state, joins work, and removes the owned database directory; stale accesses are rejected by generation checks.", + "statement": "Disabling Hermes memory invalidates contexts and cancels indexing/reviews while retaining storage, whereas destroy persists disabled state, joins work, and removes the owned database directory; stale accesses are rejected by generation and suspension checks.", "evidence": [ { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L232-L255", - "version": "repo-lines-v1:sha256:1f1936e4e3cbc44909d06cc25a5f18b6c26588b7ff8596107fe54c2e1c9e1a5c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhlNzEwNmQ4YmYxZmZiMTA5ZGJhZjNlNzU3ZDYyMzA0NGEyYTE4YTZhODg4ZmU1YzllZGFiNjFjMWQ1NjBkMjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhjYThiNmYwOTgwNzYzZjgwY2I3Mjk4ODliYzM3MTdlMTQ1MjBiOTkxZGVkODdhZDNhNWZmOGUxY2FmYTJjMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L255-L284", + "version": "repo-lines-v1:sha256:e1784330f269064b86773f626a5b55f7a2fd71523446b7fb6ae744741c90b8ab:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhlNzEwNmQ4YmYxZmZiMTA5ZGJhZjNlNzU3ZDYyMzA0NGEyYTE4YTZhODg4ZmU1YzllZGFiNjFjMWQ1NjBkMjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhjYThiNmYwOTgwNzYzZjgwY2I3Mjk4ODliYzM3MTdlMTQ1MjBiOTkxZGVkODdhZDNhNWZmOGUxY2FmYTJjMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L52-L76", - "version": "repo-lines-v1:sha256:63ceffb701cb214698f1c57e2105b4dc02d7d20d26ec8b71c4eb50058a6e8aed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwYzVkNTIyNmFhZThmY2Y0MzhjMzM0OTQ3MWY0M2JlYWU3ZjkzYjAwMTlmOWY3MTIxOWQyMzhhNTY3MWU0YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmMjE1Y2QxMWU4MjNlM2M0M2IxYWY0OGI2MDBmNDQ1ZTY5MDA4OTZmNmU1MmFlN2U1ZGI3M2FmYjFkNjMwMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5NzJkNjIzZGZjOTFjMGU2YzQ4MDU1YzY3NzhlYThmNWUxZGQxOTYyZGNlYmY3YjY1NDQ4M2NlZGUzZGY0OGEifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L53-L76", + "version": "repo-lines-v1:sha256:7c4365b3144f820a6bf6277f89ffedc136cf093a4d1dbfd34b12228c640a16ec:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwYzVkNTIyNmFhZThmY2Y0MzhjMzM0OTQ3MWY0M2JlYWU3ZjkzYjAwMTlmOWY3MTIxOWQyMzhhNTY3MWU0YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZmZWNjOTI3NDViZjE3YmEyOGU5N2Y3ZDM3ZDUwNjExYzQ4NmU2ZmYwMDFmZGM3NDBmZTVlN2YyMDYxMTYwNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmMjE1Y2QxMWU4MjNlM2M0M2IxYWY0OGI2MDBmNDQ1ZTY5MDA4OTZmNmU1MmFlN2U1ZGI3M2FmYjFkNjMwMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkNWI4OTE4YjQ5NWU5NDY3NjA4OWMyMjY0MTAxNGY4ODFiZTJmODg2N2QwOWRkOTdjNDNhZDk2YTBiMjAxZjcifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L98-L140", - "version": "repo-lines-v1:sha256:63a4e0832eb58af19f1014b010116530bd83aa6cfe5c62c9e3643d154d77383e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MmY5Njk1Yzc0NjdjNzQ3NzNjOWNlN2ZjNDI5YzVkMGQwZTQ4N2YzYzUxZDE1ZDY2NjhhM2QyNDA3MmJiOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyNWZmZTlhNjZiY2Q1Y2QwNWE0NDhhYjFlYmQ1NmVjMTJkNTg1ZjYyN2RhOGM5YmI2ZjYxYTVhNWUzOWU2YzcifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L98-L156", + "version": "repo-lines-v1:sha256:fefd73279e7bae150fd1456fb9c4fe71c6894b670f2e57f9465b7153df418934:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MmY5Njk1Yzc0NjdjNzQ3NzNjOWNlN2ZjNDI5YzVkMGQwZTQ4N2YzYzUxZDE1ZDY2NjhhM2QyNDA3MmJiOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5MTIwOTlkZjRlMDZjMzE2MGQxNmVmODEyNzE2ZDFjNmE2MDJjNTRiYzAyODUxNGNmZmRmYmQ0ZmU3OTRkYzAifQ" } ] }, @@ -485,12 +485,12 @@ "version": "repo-lines-v1:sha256:70eb20eff459aff69f606cacc64e2820ff4dfef497ac0aa9b4d16242c3ebe5e9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5Yjk2NjZhZTAyNTVlNjdiODYwZTUxNmU2NTNjNDg2NThhZmYxN2Y3NjdhNTk2MjIxMDI4ZjQ3ZTcwMjJiM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlNTU4YzYzYTI3MmJmMmFmZmE5YTc2NGViNDBjOWIzMWU3NzUyMjBlOWI3ZTg0YTk3ZWJhNzk1YjIwNGQ0ODIifQ" }, { - "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L215-L227", - "version": "repo-lines-v1:sha256:cd20a3869cf3d54e2dba2169f1e5d4a254f24780ec533eae7cebbc62715da7e2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5ZjlhOGZjNjI0YTNlM2JjNjU0ZjJiMGJjYTRhYjk4ZDgzNGJiZmY0M2Q2MTI2ZGNiYzg3OThhODE4Njg0MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmMzBhYTA1MGU1ZjU1NjUzODUyZDU3YzU5ZDBmZWFhMWUxZTJkNmJlYzc3Mjk1NTQ4ZjFlNjExZWZmNzc0OTEifQ" + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L217-L229", + "version": "repo-lines-v1:sha256:b16064d85b0614fc662b49558b83b061cfe3dda7168cb2a60b948cb891506760:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5ZjlhOGZjNjI0YTNlM2JjNjU0ZjJiMGJjYTRhYjk4ZDgzNGJiZmY0M2Q2MTI2ZGNiYzg3OThhODE4Njg0MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjMmU5YTYzYjZiYjJlNWQwZmMxZmYxZWFiZjhmNDJiODYzNGQwNTQxMmQyODRkYzA4NTNiNzcxMTI5ZTZhOGMifQ" }, { "resource": "repo://TFM/App/AppModel.swift#L638-L654", - "version": "repo-lines-v1:sha256:52f1ee86dd5715fa752288f82f000ad9f5df3df9a8b00a1cd9ca301d14921032:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjNDJhZWNiYjEyMjRmOWZlOGI3NjQwMDJhOTcyYzgwMzBiNDNiZjY5NGI2MDdjZmIzNTFlM2QwYzFmZmJhMzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFlZWMxYmQwNjQ0MjU2MWMzNjBkM2Q1MzgzZmQ3MTQwMjEwNzMwMWI2NWQ5NGYyZjk1YWI1MDIwYmRiYjdkOWMifQ" + "version": "repo-lines-v1:sha256:87b8f260970cb6208ef05500d29e9b3b0e132c0ed86e8e2a2e54b79401534f07:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImViNWVhODgxYzA2ODA5ZTRhMzRjNjJhM2NkNDVhMDEzMzZmYTk3MzE4NWM2ZmExMTJiZWJhYzdhOTY0MDA0NjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3ODY0MDg0NmE5MzJkZWQzYTNhNTkyYmJkMzc5MTA4YzVhYmZlY2M2NTE3MDFjNDVkNTg5YzRmYjUxMzBjZDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzYjYxOGJlODcwZGM3ZWE5ZTZlODVjNWViYjI1MDdiMDZlZWJiODYxYjUzMjYzMWNlNDIzNGMxYzEzMDY4Y2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMxMjI2NTBmNWU1Mjg1ZGI3YzJiZmMyMjA4NzRhMzIxNDBlY2I2MmMwY2VlYjlkMjcwZmIyZmQxNTkyYzUwNzEifQ" } ] }, @@ -622,27 +622,31 @@ }, { "id": "claim_1b7003b57ebd41dfbc14412d029a7f28", - "statement": "New submissions capture memory-session eligibility before preparation; later enabling does not add a session to the turn, while disabling revokes existing contexts and rejects late loads from the prior generation.", + "statement": "New submissions capture memory-session eligibility before preparation; later enabling does not add a session to the turn, while disabling or suspending revokes existing contexts and rejects late loads from the prior generation.", "evidence": [ { "resource": "repo://FrameworkTests/AssistantExecutionControllerTests.swift#L395-L428", - "version": "repo-lines-v1:sha256:a08af3f8158f08c746947fff72a037f53c6abcef3acc5a298d04c24e3a817d93:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZjA5ZWIwYzU3NDRmYjk3MzMxYjk4ZmYzZmJmMDVhNjAyYmEzZjdmNjdlNWRhNWJlOTA5NTAzM2Y2MjU4MjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFmODg2Mjk4OTI2YzJmYTAwODkxNDQ4MzdhZjIyYzFmOThlNWRiNTlhZDJiNDI3MDFlMWY4OWYwOTZlNjNiZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkZWFjMjgxZjBmYTk0OTMzODI3YzJlYzFlNGFmMThiZTk0Yjk0NjRmMzMyNDIyYzUxMzNiZDNmYThkODg0YmMifQ" + "version": "repo-lines-v1:sha256:e0c628005bbd0a7b21b41f4d06fe389d8994c372892bab2fa31f47b2a7ef6552:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjOWM3NDk2NjVhYzY2ZWIzZjIzOTc3NmY5MWFiNTdjZDZjNWZjMDhmNjQ3YmUyMTBjNDUyYjMwZDBkNWQxNTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZTk4NzI4MTZlNDQwYjg5OTBkODc1NGNmOTY1ODNlOGY0MTQ2MzhmNzA5MjI1M2M4YTA2MTViOTlmNmE1NjgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJiMGJkZjExMTljYjZkMTBmYmM2NDg1MDZhZTA1MDUyNzAyODBhNjhiM2Y1ZDY3NWMxM2FmMTc1NmVlNTZmOTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkNWI4OWU2MGQyY2NhOTBjZmQ1NWRlY2NmZGQ3OWZmYmFkNDExMGMxYzI3YTE1ODE4NmRkNTIzZWU0NmI2ODQifQ" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L611-L619", - "version": "repo-lines-v1:sha256:04ba197fac5b56c77ec0c0f30bbd76411d9455f58e9d396d84d2b36c9e6967c8:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzFmOWYwZmE1Mjg2ZTNjNWNjODQyMGM4OTlhNDllOWE4MDg5YzdlODFmNDM4MjhkM2ZmODE1YWZkNjhhYmU1NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZjJhYjU2YWMwYTg2OWJmODA5ZWVlMWJkZGNlMzg4YWYwNzdjOWZiMzY3MjUyMWRjN2ZjOTVlMjA5YTExNzdjYSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDY0NzlmM2RlOGUxNmNkMWJkMjcxMjk5YzlhYTY3MTBiOWFkMjU0Yzk5NTBmNGRiYjZkZjFmMDAyYzYwMjBiMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzY0ZTM0ZWNiZjkzMjgzZDEzYTVlN2UyNGY0MTAxZTg2NzQ1YzI2NjZjMmNiOGEwNGY3YmE0N2QwM2Y5MTkyMiJ9" + "version": "repo-lines-v1:sha256:797c520b75143282d1d5cf6143484c6e0f6a95f0bb2615d2a77457078b62bb6b:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZmM3NWU3MTE3ZmE4MDQ5MWM3MTZlNjdkOWZhNjJiYWI2YjM1ZmJmOWEwMzczNDE4NjM4YWZhN2EwYjkyMDdhMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDMyYzExYTI2ZDM4M2FkYjdiMmViY2RlNmE3ZWU1NmJlZTBhYWRhZTNjMjA2MmEzODhlYWUxYWY0ZjA3NzMxZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDY0NzlmM2RlOGUxNmNkMWJkMjcxMjk5YzlhYTY3MTBiOWFkMjU0Yzk5NTBmNGRiYjZkZjFmMDAyYzYwMjBiMCJ9" }, { "resource": "repo://Frameworks/TermindApplication/Agent/AssistantExecutionController.swift#L794-L809", - "version": "repo-lines-v1:sha256:f5bad0e8ec4b31fdac4505332e2c746548c179c72239909f782501aec3db7129:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjYTA3Y2I3YmYxMDg4OGZkZDFlMmMwMDMzYzI4MDIzMGVkMTRkNjkzNGY0NjFiNTZhNTg3NDllNmRjOTUyNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjViOGQzYzU0YjEwNzZiYmJmNTcwMmQyMTc3NDA0NmFlZDI3ZmNlYzVhM2JiNzYzNzQ0NjNlN2RlYTEwNGMwMmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwYmFhMzUxMmU3OGFiOGM2NzBiZWU2NWVlNDQ4MzQ4ZTNjODVkNWZkYTFmM2JmYmYzYWRlMzI3MGIyNDY3MDgifQ" + "version": "repo-lines-v1:sha256:a64446cfb2f421e6f070a56abb737a1236e081aa2786a60ee03a32f01e419be6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzNDAxNTgxMjg1YmEzYzg2NmNkZmVlMDFlZDE0ODhmZmQ1YmM4NDYxMmZhZTg2MTE4MDlkNDdmYTZhYzM0MGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiYzUzMDFiNzY2N2NjNmE5MGQzNzIxMjI5NWE0YmE5NTM3MTM3NDAwNjhjMzA2NmExYTYwOGUwNzg5ZTlkMDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFlZjNmNzdkMzZmYzRmMmNjZjBiN2FiODYxYjA1NjYyZjA5OWNkMTg5YTg4ZmM4NDE3YjE1MmE1NGEzMTFiNDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmNDVhNTRjOWY3NzBiOGQzZDAwNmU5ZjBkNzExNGMyZDgzZTkwYzU5ZDhhNzY0MjA3MDU5ZTgzZDAxN2UwOGMifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L52-L76", - "version": "repo-lines-v1:sha256:63ceffb701cb214698f1c57e2105b4dc02d7d20d26ec8b71c4eb50058a6e8aed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwYzVkNTIyNmFhZThmY2Y0MzhjMzM0OTQ3MWY0M2JlYWU3ZjkzYjAwMTlmOWY3MTIxOWQyMzhhNTY3MWU0YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmMjE1Y2QxMWU4MjNlM2M0M2IxYWY0OGI2MDBmNDQ1ZTY5MDA4OTZmNmU1MmFlN2U1ZGI3M2FmYjFkNjMwMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5NzJkNjIzZGZjOTFjMGU2YzQ4MDU1YzY3NzhlYThmNWUxZGQxOTYyZGNlYmY3YjY1NDQ4M2NlZGUzZGY0OGEifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L98-L116", - "version": "repo-lines-v1:sha256:ae20e9db15b84f07e0787f36390d963dfb0425b2ba7f5a5cec258e46ddc6598f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjZmQ4ZjZmYzk3NjQ5MmIzNjQ2MmQ4NmUyNjVhODNkNGRjYzlhN2YwMDViYmZjYmI1NjYwZThkOGM0NmQ1MzkifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L53-L76", + "version": "repo-lines-v1:sha256:7c4365b3144f820a6bf6277f89ffedc136cf093a4d1dbfd34b12228c640a16ec:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwYzVkNTIyNmFhZThmY2Y0MzhjMzM0OTQ3MWY0M2JlYWU3ZjkzYjAwMTlmOWY3MTIxOWQyMzhhNTY3MWU0YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZmZWNjOTI3NDViZjE3YmEyOGU5N2Y3ZDM3ZDUwNjExYzQ4NmU2ZmYwMDFmZGM3NDBmZTVlN2YyMDYxMTYwNDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJmMjE1Y2QxMWU4MjNlM2M0M2IxYWY0OGI2MDBmNDQ1ZTY5MDA4OTZmNmU1MmFlN2U1ZGI3M2FmYjFkNjMwMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkNWI4OTE4YjQ5NWU5NDY3NjA4OWMyMjY0MTAxNGY4ODFiZTJmODg2N2QwOWRkOTdjNDNhZDk2YTBiMjAxZjcifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L98-L150", + "version": "repo-lines-v1:sha256:4861df2a432fb0b946b8527e319163182d0e31dd2ea3e4d569edb846c98f4860:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5MmZkOWM4MzE1NGM3OTBiNGI3MDUyY2ViZTk3ZWI5ZjVjZjYyNjZhOGFkNGY2ZWMyOGY4NjY5MjU4NWNhMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MmY5Njk1Yzc0NjdjNzQ3NzNjOWNlN2ZjNDI5YzVkMGQwZTQ4N2YzYzUxZDE1ZDY2NjhhM2QyNDA3MmJiOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFiYzgyYTk3NzE1YzQ3N2I3MzM1MWJkN2M4NGVkYWRjNTdmMTlhM2QzOTIyYjE2NjRjZTlmOGZlNTdkNzU3ODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTlhN2I4OGVlMzgyYzM4Y2VjMjZiNWYxZDhiNDczNGYwNDRlNDRhYjRiNTM0ZDE2Yzg0MjBlMzZiOTZlOGYifQ" } ] }, @@ -1000,19 +1004,19 @@ }, { "id": "claim_750079dc18ec48c1be4abb0842993534", - "statement": "An independent three-second macOS reply fallback can end quit waiting before save/resource cleanup finishes; shared unstructured SSH authentication/pool tasks may not settle on runtime cancellation and background-command close only requests cancellation.", + "statement": "An independent three-second macOS reply fallback can end quit waiting before save/resource cleanup finishes; shared SSH configuration or pool work remains alive when other waiters still own it, while the last waiter or explicit Host retirement cancels the underlying task, and background-command close only requests cancellation.", "evidence": [ { "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift#L161-L168", "version": "repo-lines-v1:sha256:0cf7f999434cae91c1dad2c325405a08e8456edc6bb0625623dd3016bfbd4236:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwM2RjOTQyY2M5M2MyY2E2MWM4ZDc1YzFkM2NmNWM1OTUzNjg1YmY1YmZhNzRlZjIzMDY5YTJkYmU5Mzg0MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmExM2NkOGJlNzIzNDcxZmEzZGE3ZWI4NTdjMjYxYzJmNjRlZWQxZTUwMzMyNTQ3NTlmOGYyZGUxY2Q2NjNkNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzg2Nzg2ZDIwN2ZlMzE4MDZjYjIzYzA0ZGExZTQ2Y2ZkYTNlMjNmNDg3ZGJhOWFmMmIzZGYzMTIyOGY2ZjAwNCJ9" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1543-L1575", - "version": "repo-lines-v1:sha256:32cc2e0c42513700520084d4401216b1a3f77cfc0dba7999d7f25613103a928f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTUzZjdkNDRmYWI0YTU0Njc3N2Y5MTRiNmRkMGUwMjIyMTA0ZjM5OTY0OTdhMDYzNDJmYTFiZWUzYWU1NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3YTBlZjQ2NDQzM2JjZjY0MDBhM2RlZTVhNTZlMjQyMmFkYmFmYTVjNDBmMGJhNzY3MzJlOWU4NGY1Y2QyM2EiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YzJjMWEzYjJlYzM4ZjE5MGJkNzllM2M2NzNiOGNkNWE5MWMyZGEyYThkODM4MzdlZjgzNGVhNzU3ZjZiMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM4OTQ4NjdkZDBkYTNjOWE2MTdhYjIxODMzODc3NjkwZmJkZDhjNTEzZTg5N2MyZDBhZGY0ODM1NGU2ZmJlMTAifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1638-L1774", + "version": "repo-lines-v1:sha256:23aaca80cf4e2215a2f75373a0dcf3b4d6ee051aa3b90f8a3ad089893590f8a8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTM3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhZTE1M2Y3ZDQ0ZmFiNGE1NDY3NzdmOTE0YjZkZDBlMDIyMjEwNGYzOTk2NDk3YTA2MzQyZmExYmVlM2FlNTVmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmIwNDQ5MTQ2OTcyMmU4MzYzYTk0ZTQwNDQ1MzhlMDEzYTJhMjE3ZTM2YjFlNTY4YmE2MGFjYjdiNGM4ZjJlIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkNmMyYzFhM2IyZWMzOGYxOTBiZDc5ZTNjNjczYjhjZDVhOTFjMmRhMmE4ZDgzODM3ZWY4MzRlYTc1N2Y2YjA3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3NDcyMmRmYjk2MDYxMDVlNTFhZjZkZDllYzI0YjNjZGE4ZDQ1MDU0NzA1YzUxN2UwNGU4OTJlM2U1YzQ2MjZhIn0" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L599-L649", - "version": "repo-lines-v1:sha256:264009231dd112a56fb55f2187bd34367716c9df86e21870d47ee40f60cf2884:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjOWRhNzhjOTJlMzEzNzI1OGY0NzA5MGQ1MjM5OTY2ZWEyYWQ4MTczYzRiZGZkMTQyMzhjYjE2NzcxZjM1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmNGQxOGNmNDZiOWM5ZmQ0MDViYmVkZjlhZDc5ZmVmMGNhNGYzYjI4NTdmYWQ5MDA0NDI5MGJkNWM0NTVkMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA5ZmMwMWRiNDUzOWNjNTgxNGFhOTk0NDQ0NzhlMWY2MTI5ZjNhODllYzFjYTEyOTllYWM5ODBhZjZhZTRlMTEifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L700-L774", + "version": "repo-lines-v1:sha256:9bfcbf42450fdb97123b2ad5a7f98b81d110c3f3314600403d93ad7b04860dab:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwMzg4ZDNjZDU0YTVlOGYxZmVkZjlmOGY0ZjYxYTBiYWNkMDI1YWU0NGE1MDUzMDMxYjExNzU1YTQ5MmI1NzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZTI3M2YyMzE3MjgwZjUwYWJkMzkyMzQxNTg4YTNiOWVmOGRhZjA3YjI1YTkxNmMyMmFlZGQxYWVhMDdlNzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhNzc4Yjc5MjgwMzkyYWQyMjYzNjJmYzk5OGQwNGJhNzdhZjU4ZmYzYWQ2MWYyZjc2NzA0ZWZjMjNkNWU2YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgifQ" }, { "resource": "repo://TFM/App/TermindApp.swift#L215-L244", @@ -1026,29 +1030,69 @@ }, { "id": "claim_1dc38eea858b41789f6c9b273549ec5b", - "statement": "Captured-turn termination joins exclude existing manual compaction, title generation and memory indexing; MemoryModel.suspend cancels current tasks, but successful terminal repository saves invoke historyChanged and can start indexing again.", + "statement": "Captured-turn termination joins exclude existing manual compaction and title generation; MemoryModel.suspend now establishes a persistent admission fence that invalidates current access and suppresses history indexing until resume reloads state and backfills history.", "evidence": [ + { + "resource": "repo://FrameworkTests/HermesMemoryTests.swift#L116-L181", + "version": "repo-lines-v1:sha256:ce72d05810dc1671686e744e8d2fdaf825e3662b3dd2f726191909a92e01ab3a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3MjA1MzIyZDIyNTY5NmVkODY4OGRlYzk5N2NjNGMyMzZlN2M4ZDVhMDY4ZjBmOGEwNzg0NWJkYzk4NWJhYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkMmZlMGM5ZTQ2MGJjYjM4YTQ0YzY4YTY2OWNlZWY3NDA4YWExNjcxMmUwMjZlMmFmZGI0Yzk4NTc5YzlmMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0ZWMyOTlmNTE0NWQ5M2I1YWM4OWZmMzVlOTE2YjE5ZDE3ZTA3NTg0MWQ1ODIwODI4YzdlMjJjZDMzZjdiYzYifQ" + }, { "resource": "repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift#L13-L16", "version": "repo-lines-v1:sha256:dd81b44cc0557db5439accd290c19cb9b58b2c232fd0f3255a0504d4c2cc9d18:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdmNTQ1ZWVjNjkzZmZiNDU2YWM2NGRmZjc4YTA2MTcxYjYxZDI3MjVjZGUyNTdhYjg2ODgzNTc0NmQ5YzM3NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWMwZTU3ZGY4OGJhMmExOGMyZDQwOTJmNzM4N2E3MmFjMTBlYjc1NWNjNjFiZDlkYTFlYzU2NzdkMWNjZjE1YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzRhYjk4MjQ4YzViYmM1M2IzZmFjZjQxYmI2NWQ3ODk5YzhjYmRiYWJmYzJhYTQ1ZWVjYWMxNjhmNmQ0MzU1OCJ9" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L153-L180", - "version": "repo-lines-v1:sha256:70176bb0b07b5d039e790f72be2fd529f699f1cd9b61d2019f01e7243c5542a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L177-L204", + "version": "repo-lines-v1:sha256:463d4609d3eaa2a49b28fa86063677b92b26cb06a7c30dc499260f0aa1a5278c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjhmZjRmY2M1OGUzOTQxNjdmNmMwYTkwOGZiMzIzZDJmZmM3YWRmYWEzMTNjZjk3MDhhOTNmMGM2MmFjYjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTc5NmZiOTc5MjY1ODlhZjExYTQ5ZGNhMjViNTgwMDlkYjVhZjhhMWQ3Y2UwMjhjZmYxNDBjYzJhNWEwNjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZhYTc3MTExMzFhNDc3MTA0NWFhMWU0NDlhZmI3YjkyOTg3Njk1ZDRjNTkzZDYyZGI5Mzc0MTQxNGFmNTMzZTQifQ" }, { - "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L251-L255", - "version": "repo-lines-v1:sha256:46b0558ae01ecd5d23e50f403021b1076616087e98308d4065bd3d8626dc7a58:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjU1OWVmYWQzMjUzOTliYWNiNTA5OGJkNmQyMDFjMDEyODQ1MjhiMzFkMjBhMDNkMzI0Y2NmOWQ2N2IxYTlmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDgzNTRmZTc1YTVjYmMzYzViYmVlYTdkNThkMmUyMGY4MDJmYTgyYmNlOWVkOTRhMzJjODljOTkzMzM1MWZiYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODYyODEyOGQwOTU4NjAyZTI1NGQ2YWU2ZWE5MGNmMzQ3MDQ5ZjM2N2EwMTcwMTAyMmY1MjhkZGE0OTMwNTk5OCJ9" + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" }, { "resource": "repo://TFM/App/AppModel.swift#L724-L777", "version": "repo-lines-v1:sha256:1fc048283823ebca7238e66af31e150e119b83645a4e5f760aa9bf444ce1337b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNmE1OWU3Nzg2NmU1MGRiZDYxZjA5ZmQ1YWM2MjQwN2Q0ZGVlNjdkNGIyNTJhNDExYzRhZTEwZWNmMWQwNmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNkYmExMmRjNjgzMWEzMWU5NzdjMTMzMDU0NWZiMzZhMTFjYjE2MDQ5YjQ5ZGYwN2MzYjAyNjM4YjIwNzA2YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzODNlN2M2MmI5ZmI0ZGU3YjNjNTc3ZmFkNjNiOTYxMGRlZTcyNmI4NzljYzZjN2M5ZjYzNTljOTU5YWEwODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYjkxZWUwZThhZmIwMWVmNzYyMmM2ZTg0NzA2MDM2YmVmMzAwNTc5YWU1ZjcyYmQzOTViMmY3MjllNmRjOWUifQ" } ] + }, + { + "id": "claim_3ef78f9811f847169b4d6b6346c628a6", + "statement": "Hermes suspension retains the enabled preference and storage but revokes the current generation, refuses new memory work, reloads committed state on resume and reconciles writes that committed after their old-generation caller was invalidated.", + "evidence": [ + { + "resource": "repo://FrameworkTests/HermesMemoryTests.swift#L116-L181", + "version": "repo-lines-v1:sha256:ce72d05810dc1671686e744e8d2fdaf825e3662b3dd2f726191909a92e01ab3a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3MjA1MzIyZDIyNTY5NmVkODY4OGRlYzk5N2NjNGMyMzZlN2M4ZDVhMDY4ZjBmOGEwNzg0NWJkYzk4NWJhYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkMmZlMGM5ZTQ2MGJjYjM4YTQ0YzY4YTY2OWNlZWY3NDA4YWExNjcxMmUwMjZlMmFmZGI0Yzk4NTc5YzlmMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0ZWMyOTlmNTE0NWQ5M2I1YWM4OWZmMzVlOTE2YjE5ZDE3ZTA3NTg0MWQ1ODIwODI4YzdlMjJjZDMzZjdiYzYifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L274-L284", + "version": "repo-lines-v1:sha256:c6bd184fd629fc2765d33f8ae15143e6791863bd814b8356a2ceefdd5dcfe82e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1NTllZmFkMzI1Mzk5YmFjYjUwOThiZDZkMjAxYzAxMjg0NTI4YjMxZDIwYTAzZDMyNGNjZjlkNjdiMWE5ZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4MzU0ZmU3NWE1Y2JjM2M1YmJlZWE3ZDU4ZDJlMjBmODAyZmE4MmJjZTllZDk0YTMyYzg5Yzk5MzMzNTFmYmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2MjgxMjhkMDk1ODYwMmUyNTRkNmFlNmVhOTBjZjM0NzA0OWYzNjdhMDE3MDEwMjJmNTI4ZGRhNDkzMDU5OTgifQ" + }, + { + "resource": "repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift#L78-L164", + "version": "repo-lines-v1:sha256:67d507f673f714fbd8442cbbac6e38841b3be5a78042e349430e664b1c646143:eyJzZWxlY3RlZExpbmVDb3VudCI6ODcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxYmJlOTJmOGFhMDlkNDE4MGVjMWU4ZmY1NTAwOGY0NDJmNzhlZDAxYmFlZDNmNDEwNTczYjA1OWFmMGI1MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MmY5Njk1Yzc0NjdjNzQ3NzNjOWNlN2ZjNDI5YzVkMGQwZTQ4N2YzYzUxZDE1ZDY2NjhhM2QyNDA3MmJiOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE4OGNlZThkZTU2Y2FmYjQzMGRhYmU4ODYzZTk1ZjA5ZTU2OGQ3NjU5Nzk0YTg4MmNiNmNkZTI1NTFmNzlhMmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwNGY4YmMyNjM5OTg1OGYwM2VhMGQxMDIwNDBhNzVhNzkwMDliNGMzNjUzZDAxZTU0Y2NiODI4YjAyODQyZjYifQ" + } + ] + }, + { + "id": "claim_ae78c3e5358a4cd7b7bd9377f8a515f2", + "statement": "Mobile Assistant background suspension keeps send, edit, plan implementation and manual compaction admissions closed until the retained conversation and connection cleanup task completes.", + "evidence": [ + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L115-L155", + "version": "repo-lines-v1:sha256:8c82421c493d71a46a31cea25dee42618562d9e3080c889c363ebef1d1f47b25:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ3N2Q1YWM0NmZkNzdmMWZlYWMyMTM1ZTlmZDAxMGYyMzk4MWQxYzBhMzMzZWJkMTNlZjE2MmUyYzZiNTg0MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzZDE2ODk4MTU0MTExNzUyNDc4NGZlZWJjNTY5NzIzNmYyZDk2ZDI2YWJmYjA2ZTIxYmM2OWQ4MzIwODY0MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzNjFmYmU2YTJkNzg3MDAzYjc5NDliMDY4NTFjOGJlNDczNTI3MDNiZjAwYmZkYjM5YTVlZmEwZDAxYTdkNmQifQ" + }, + { + "resource": "repo://TFI/Application/Agent/MobileAssistantModel.swift#L217-L245", + "version": "repo-lines-v1:sha256:b75dd4e03723d8a10cdffed754b346623c2cb81c76b67c816179f18641779a94:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5ZjlhOGZjNjI0YTNlM2JjNjU0ZjJiMGJjYTRhYjk4ZDgzNGJiZmY0M2Q2MTI2ZGNiYzg3OThhODE4Njg0MzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3ODY0MDg0NmE5MzJkZWQzYTNhNTkyYmJkMzc5MTA4YzVhYmZlY2M2NTE3MDFjNDVkNTg5YzRmYjUxMzBjZDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg4YThkZDliYTFlODExZjZkNDY3NWU3ZDI0MWE3OTU2OTRhY2ViYWNhODhiYTlmYjU0Nzc5Y2M1YmQ5YTA4N2UifQ" + }, + { + "resource": "repo://TFITests/MobileAssistantTests.swift#L493-L551", + "version": "repo-lines-v1:sha256:65515a4e9bdd6ebad25be3bccb372ded2a1a7af650a3a47ab525c5daa87a2c49:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxNWNkMzA5ODJjNzNiNzE4ZGVlNzZmNTU0MmRlNTY4NjBmYzMzYjdkM2QzOTk5NTA3YWVmNjRiYTNiNTgyOTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQxOWUxMzBmOWU5ZWJmMzY5ODJhNzdjYzg1M2M3NGQzNWNlZjVmNDgyOWEyYTA5NmM0OWM3ODBmZmFjNjFlZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjODI0MDgxOWY3NzlmNjQ5ODAwYjY3ODkwMjEzOWQzNDM0MTFhZTI3YjA4OWU3YWJiOTFlYThlYTAzYTQ3NjgifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/concepts/commerce-localization-and-product-surfaces.json b/openwiki/.claims/concepts/commerce-localization-and-product-surfaces.json index 98c1669a..cf0cf79c 100644 --- a/openwiki/.claims/concepts/commerce-localization-and-product-surfaces.json +++ b/openwiki/.claims/concepts/commerce-localization-and-product-surfaces.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:a1c9a480314b124cb096731918e3a208c7dab2e590e8c42bcfa94456f9440b9c", + "pageVersion": "sha256:0387b384f0e9df7daaf614e6d1e66ed31e2d0bd1f198bce95d1d8e6191d2e00b", "claims": [ { "id": "claim_70c5c3da992944188e719c65adc9b06c", @@ -233,10 +233,24 @@ "version": "repo-file-v1:sha256:97866af84c8b01df60e0e125b61a85836eaa9156052aeac3c3ddb0c234cad81f" } ] + }, + { + "id": "claim_57342484912448139dbc6265eedd1769", + "statement": "StoreKitTrialTests is skipped during routine unattended runs and requires an explicit TERMIND_STOREKIT_TESTS=1 opt-in because the local SKTestSession test host needs manual StoreKit authorization.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L56-L58", + "version": "repo-lines-v1:sha256:59b48c1c93af632d0d54f375f34be63bfe084da21dbf98d89f2c5174f4f566ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWYxODBmOWMxMjY5MDNiY2MzMzFhYTk1NDU2ZTQ5NDQ0YTdkNGQwMTFlOGEyYTgzZjVkYzg3MDIzOTE4MTJkMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjQ4MzZlZDdlMDA1Mzc3NWEwYTk2NmNlODYxMzRiMjM0N2Q4NTFhN2IwMmY0ODEyYmViMmM1OWQxYzY4ZDUyNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGMwNmIzZDc5NGYzMzczOTYxOGNhZmVjOTQwY2ZkY2YxZjllYjI1ZjI5YjUyYWJjZmNkZjA0MWY3NTg2YWZmYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTA1MTY5OTI5YTliMjNhOWIwMDJiYjE5MDNhYTA3ZWMxZGE0ODA1ZjY3ZTEyNTBmYzZjOTQ4MDlmMzJlMGM3ZSJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L1-L18", + "version": "repo-lines-v1:sha256:01f6bcd9c1be6ab834bd576093f823f2b13ebdc7043b843fc256fa8ab1b51bb3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyMzkxMGFkMTQyYWFlZTQ0YzE0ZGU2YTYxODVkZTBiY2NhNDhlYzZlZGQ3ODFlNWU1MjQzYjdiODk2N2VkMTkifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T03:23:14.035Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/concepts/persistence-and-secrets.json b/openwiki/.claims/concepts/persistence-and-secrets.json index 3831d6de..6e509456 100644 --- a/openwiki/.claims/concepts/persistence-and-secrets.json +++ b/openwiki/.claims/concepts/persistence-and-secrets.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:2c7b5e94e1ecc8ef99eb9c4ea83b9ffb4420e8a9229b17a20f93376b48e8633f", + "pageVersion": "sha256:b564d4439f025bc55d8b4c0a8f3054174469499c9351087e0dd6383a4c70fe33", "claims": [ { "id": "claim_e50b505da2214c6781fa8eb1a146dacf", @@ -12,7 +12,7 @@ }, { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -26,7 +26,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/App/TermindApp.swift", @@ -457,6 +457,6 @@ ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/concepts/ssh-multiplexing-and-scheduling.json b/openwiki/.claims/concepts/ssh-multiplexing-and-scheduling.json index 314a9aee..8185c62d 100644 --- a/openwiki/.claims/concepts/ssh-multiplexing-and-scheduling.json +++ b/openwiki/.claims/concepts/ssh-multiplexing-and-scheduling.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:eedd08fcec41ac31b854d4f0375e28efcca4b20b1dfac2d25880b18421e8d199", + "pageVersion": "sha256:ec75319c1b21afb2ec24dfee47fe74a0d32ad67f1f7e04314e7c3a6a07e65937", "claims": [ { "id": "claim_c56b98e3167443d9a8aa8d3405b35eca", @@ -130,19 +130,23 @@ }, { "id": "claim_1fd2c2f8970047b8bf324a93ca5f2650", - "statement": "AgentConnectionPool reuses Host/fingerprint keyed connections and primary SFTP across runs, joins in-flight opens and protects active exec/SFTP leases from its default 180-second idle retirement; replacement exec retry is limited to connectionUnavailable.", + "statement": "AgentConnectionPool reuses Host/fingerprint keyed connections and primary SFTP across runs, joins in-flight opens with independently cancellable waiters, protects active exec/SFTP leases from its default 180-second idle retirement, and limits replacement exec retry to connectionUnavailable.", "evidence": [ { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1051-L1093", - "version": "repo-lines-v1:sha256:2f42e9d3dec1ae9ef7bebbca339793f38e26964a5da29fdac17952be06061ded:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0ZTQ2MDk3Yzg0MjkwZmY4YjdmOWNmOWUwMmYxMTRlZWU5N2VhYmM2MDZiZTdjZmJhMjI3ZjEzNjFhYzNiZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE0MGVhYTUxNDQzMTlkMjQ4ZmM1MGEyMGY3M2VmYmM4ZGIxZmQwYjIzZmRhNjVkYTM1MDU2YjhmM2I3YTNkNWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUxMDE0Mzc2ZTEyOWYzZjE2ZjM3ZWQ3ZDUxMTQ0YzY1YjAyYzlkMGViN2Y1MzNkNjg0NDZkOGI4N2Q4OTJhODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE2OWMzZTIxNmM4N2M1YmU4MjdlNzM0M2IxZGEwNDM0MGY3NjMzMzJmYTc0ZTNjNzZkNWY4MDgwN2ZkNDRiODEifQ" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1238-L1392", + "version": "repo-lines-v1:sha256:3216969f4007ddb5dd8ee451ff76370ce052ada7caf174a7779d2d40ec5aa2bf:eyJzZWxlY3RlZExpbmVDb3VudCI6MTU1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyOWVlODg1MWQ1MDM1OTdlMTgzMjIxMDg1NDNiZGQwMThhZjFlYjVmNGViZWNhNjk4MzQxZDgzNWU3MGYwZTc4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkOWE0MGRjMDdlNjNiMjQxZTRkMWM2YTM4NGZhM2QwYzJiZTg3ZWE1ODM0MGQ4YzU2ZGIyNDU1ZjNlY2NiNWQwIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyM2MzNWUwZTc5NTJiMGJiNjg4YjI3ZTM2MmY5NGIxZDRiMjk4M2Y0YTgwM2IxYzBmYjM0Mzc1N2EyMmU4MGNmIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIzN2U4YzZmNTA3YzVhNTAxNThjYzBjY2RjYWM0MDY3MzZkYTNlNmU0MzYwOGU5ZTEwYzY2MmU4OWE1ZGQ3N2M4In0" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1318-L1432", - "version": "repo-lines-v1:sha256:707e4db604c194ca27b91f7ad568cca6fc34450bb25dcc1a35ea700157cc5e6f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJiMjdlNTE3ZjU0YWJjYmJhZjMxMzRlZWE1YmZlYjQzNjBkYTQ3ZWIyN2Q2NWZjMWEzMGM5N2RlMWY1YmI1MjQ1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlMzIwZmNkZDBjNmFkNDQ0ZDIzM2Y3M2Q3ODIyMTY2MDcyZTEwYWJlOGZkZmRmNzgyYWNjMWVlODNmZjU2Y2Y1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI2YTBmYmI0NWVjZWU3ZmMwMWY3NDZiZGU2ODJlMTE0YjkyYzU1OTlkZDllMDUxNmQyMmQzYzMyNWVkMDM3NDZmIn0" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1564-L1595", + "version": "repo-lines-v1:sha256:3a9acd4c8114408922da435777791b50ceee74096f97e7bd23054a9d5189cf9e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM5MDZjYTI5YmVjMjdhMGQzNTFiNGVjZGVjMTg0YWQyNmY3ODM5ODM0MWIyYjM4OWNhMzUxMzcwYTAzZDI3NGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3NjU3YjNhNDRiZjMxYzhkNDI1MWIzZWI5NWYwYWRhOTUxYTViNTA1Yjk5Mjk5ODhiMmI2MTFlM2FiNjI3OGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1MDU5MDUwMWE4YjVhYzQ5YmJkOTc5NzZjZTJkYTk1MDRhNDQzNzE2ZmNjNjgzYTllNGQ3YzQyZGY5ZGE4MTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg3YWMyMGYzMTBiNjNkMjM4MzNmYWZmMDk2ODM3MWM1MzhjY2MyZmYwNzc1MTVkNzI5MGNmM2JmZGQ2OWY1MjYifQ" }, { - "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1469-L1584", - "version": "repo-lines-v1:sha256:aba7ec5c1e1cccd658c5215426672c337e11ebdcdc7d2878000d06641ed5a32f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzOTA2Y2EyOWJlYzI3YTBkMzUxYjRlY2RlYzE4NGFkMjZmNzgzOTgzNDFiMmIzODljYTM1MTM3MGEwM2QyNzRkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxNTA1OTA1MDFhOGI1YWM0OWJiZDk3OTc2Y2UyZGE5NTA0YTQ0MzcxNmZjYzY4M2E5ZTRkN2M0MmRmOWRhODE5IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwOTIwZjM4ZTMwOGI1NDBkZDU2YzQxZDUyN2NkNjQyYTZkNmQyOTNhZTNmMzkyNGY4NGVlNjNkZTUxZWQyYzBkIn0" + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1638-L1774", + "version": "repo-lines-v1:sha256:23aaca80cf4e2215a2f75373a0dcf3b4d6ee051aa3b90f8a3ad089893590f8a8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTM3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhZTE1M2Y3ZDQ0ZmFiNGE1NDY3NzdmOTE0YjZkZDBlMDIyMjEwNGYzOTk2NDk3YTA2MzQyZmExYmVlM2FlNTVmIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmIwNDQ5MTQ2OTcyMmU4MzYzYTk0ZTQwNDQ1MzhlMDEzYTJhMjE3ZTM2YjFlNTY4YmE2MGFjYjdiNGM4ZjJlIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkNmMyYzFhM2IyZWMzOGYxOTBiZDc5ZTNjNjczYjhjZDVhOTFjMmRhMmE4ZDgzODM3ZWY4MzRlYTc1N2Y2YjA3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3NDcyMmRmYjk2MDYxMDVlNTFhZjZkZDllYzI0YjNjZGE4ZDQ1MDU0NzA1YzUxN2UwNGU4OTJlM2U1YzQ2MjZhIn0" + }, + { + "resource": "repo://TFMTests/AgentConnectionPoolTests.swift", + "version": "repo-file-v1:sha256:a10e41bdc9f97276bec620b1cedc8390702681153cdbca815308c91e29eb4799" } ] }, @@ -155,10 +159,24 @@ "version": "repo-lines-v1:sha256:ef2acbffed6b7a7fd6955d7fa3a6f0c7c93e6cca2ed82d431dbc1a64ff794d6d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0OWUxM2UyOGRlY2Q3Y2U5NDdmMjMzNzM5NGQ2MWQ5Y2I1MGIzZGI1OGJlYjUzM2M1Zjg1NjVmNGUwNzdiYzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExOGJmY2MwNWRhNWVkOWI1NWRjNTcyNWE0ZmNkYWVjNWMyNTc1ZmRmNGNlMjJhMTk5MDliMWIzZjRkZTgxZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" } ] + }, + { + "id": "claim_09d7ae1b456b450cb059570dcfb03de0", + "statement": "Cancelling one joined Agent connection waiter does not cancel an opening still owned by other waiters; the last waiter cancels it, explicit Host retirement cancels all waiters, and a late success for a retired opening is disconnected rather than pooled.", + "evidence": [ + { + "resource": "repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift#L1675-L1774", + "version": "repo-lines-v1:sha256:4847b31059874bdbaee91f89d037bedc3d8dd27b943965835fd217cd28a0a7e3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxYjhiOGI5NmRlMjNiYjM5ODI5OGU1MjIxODE1ZGI2MjViZGYyYzgxYjYwYTQ2ZGJiM2VjMmQzNDJmMzU0NTUwIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJlNmIwNDQ5MTQ2OTcyMmU4MzYzYTk0ZTQwNDQ1MzhlMDEzYTJhMjE3ZTM2YjFlNTY4YmE2MGFjYjdiNGM4ZjJlIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5NjQ1MzUwNzkyMTM0YjdhMWIwYjM0YzM5MWU4Nzc3YTI3Mjk4NmEyNjQyYWI0M2E3M2M3YThiYjI5MTcwZTRlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3NDcyMmRmYjk2MDYxMDVlNTFhZjZkZDllYzI0YjNjZGE4ZDQ1MDU0NzA1YzUxN2UwNGU4OTJlM2U1YzQ2MjZhIn0" + }, + { + "resource": "repo://TFMTests/AgentConnectionPoolTests.swift", + "version": "repo-file-v1:sha256:a10e41bdc9f97276bec620b1cedc8390702681153cdbca815308c91e29eb4799" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T03:23:14.035Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/concepts/ui-design-principles.json b/openwiki/.claims/concepts/ui-design-principles.json index 047b53f2..7c4cb6a6 100644 --- a/openwiki/.claims/concepts/ui-design-principles.json +++ b/openwiki/.claims/concepts/ui-design-principles.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3204534fc02c6315b0c2fe23d6cb486a18f8b86be9b8a42dd31523c3bbfd560c", + "pageVersion": "sha256:c449e2380e48e619eda9f0ac0151d8491634d439a2740a92763fb4df6b8b7fcc", "claims": [ { "id": "claim_3a403468456840acbfe0a524b90d5717", @@ -160,7 +160,7 @@ }, { "resource": "repo://TFITests/MobileAdaptiveLayoutTests.swift#L75-L180", - "version": "repo-lines-v1:sha256:7029c2d16c93e885407ee0ac5dcf872a2cf816cf64cf5a6e1c2f1468bc6a7609:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMGE4ODZkMjJlMjhmYThjZmFkMzg2ZTUzMzlmN2JhNGFmNDEzM2U4NGRmMjU5YTYwNTNjNjJiMzI4ZjQ2Y2U3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5M2IxZTE2YzVlMWYyNGI4Njc3YzQzYjI2ZmYyMGI3ZDM3MGE2OGIyMWIwY2ZlZTA3NmQ4OWNlNmNjYmEwMmFmIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJjMTNhOTQ2OThjNjNhZjM5MmJkMWI0YTZkZWIwZWI5ZjdmYzFlNWVjZTQ0MGM1OTU3MTdkYmEyYjM2ZGJjYTU4In0" + "version": "repo-lines-v1:sha256:183c09e20a2fe787af9ee1eb4e3bcc40d84b9789653fde93b7209b166ef6cc10:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwMWJhNDcxOWM4MGI2ZmU5MTFiMDkxYTdjMDUxMjRiNjRlZWVjZTk2NGUwOWMwNThlZjhmOTgwNWRhY2E1NDZiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJhMGE4ODZkMjJlMjhmYThjZmFkMzg2ZTUzMzlmN2JhNGFmNDEzM2U4NGRmMjU5YTYwNTNjNjJiMzI4ZjQ2Y2U3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5M2IxZTE2YzVlMWYyNGI4Njc3YzQzYjI2ZmYyMGI3ZDM3MGE2OGIyMWIwY2ZlZTA3NmQ4OWNlNmNjYmEwMmFmIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJjMTNhOTQ2OThjNjNhZjM5MmJkMWI0YTZkZWIwZWI5ZjdmYzFlNWVjZTQ0MGM1OTU3MTdkYmEyYjM2ZGJjYTU4In0" }, { "resource": "repo://TFM/App/RootView.swift#L342-L351", @@ -182,7 +182,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift#L560-L578", - "version": "repo-lines-v1:sha256:e1458e03b4dc3d86d48461e276ad947b989000475cf59e42b68402a40297a355:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" + "version": "repo-lines-v1:sha256:0569dc2b10c701c3b0c48b14a14a2521ba0f40ec2645f7220d7e5a61e98501d2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExYjYwOWM3ODJiMmM2NDVhM2M2ZDFmOWMyMDA3YzdjMGVlMWI5MTI1YjJjOGIxOTU0YTFlMTVjY2UxM2VkYjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMGVhYzUyYWE2NjM0MDczNjQ0MjU1MTg2YTc5NGIxMjdmYzQ5MTA2NTRmYTNhMjBiOTgzMjMxOTIxZWU2ZWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZTJhMWYwNzIwY2I4YWNhODQ4ZjIzNzM0N2E4YjUzNGY2YTEzZDVhZTBmMTQ3MDE4ODQ5ZmZjYjkxOTRlZWMifQ" }, { "resource": "repo://TFI/App/MobileAppModel.swift#L635-L663", @@ -347,10 +347,32 @@ "version": "repo-lines-v1:sha256:9601001fe4af32f37a89a620b06175d054106430cb8d592abab3542ee6f56bc8:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2IzZWIyODgwNTgxMGU0OTgwMjE3ZGJmMmJkMjNhOWM5M2Y4ZTFiZjAyMDdkZGVlOThmZDlhNTQ1ZTYxMGRjOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMTkxMmRjZmY4ZDViM2RiNmEyNjY0NGE1ZTVlMTA0MzBmNDhkNTBmYmM0Y2U2MDYyZjEwNDY2MjQyMDEyOWMyMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZWUwY2ViM2FjNjViZDYzZTdiNTI4NjE1YzM5YzMxMTNlMTUyNWY2MzY0YjliMjg2ZGNiYjI3YjI4MWZjMzdkMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzE1MWE5ZGJhY2E5NmQxYjg4NjU2ZTM0YTgzMjVkNDRhNjAzZjhkYThlNjE3ZjNiZjdmNDMxMTY2YjZhMzIwNSJ9" } ] + }, + { + "id": "claim_730c2f34dd7b4af986db922cd376b22c", + "statement": "Mobile editor-toolbar, forwarding, host-integration and saved-name rendering suites install process-global UIKit key windows and therefore require TERMIND_MOBILE_UI_TESTS=1 with cross-suite parallel testing disabled.", + "evidence": [ + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/integrations/mcp-server-and-bridge.json b/openwiki/.claims/integrations/mcp-server-and-bridge.json index 3e083082..2cccfb2c 100644 --- a/openwiki/.claims/integrations/mcp-server-and-bridge.json +++ b/openwiki/.claims/integrations/mcp-server-and-bridge.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:e8702d030f8cc69c70ce0b630321aa0bf139b70e4dfb9e29b469cb5cfd77a53c", + "pageVersion": "sha256:ecf21d72d9c031ff95071424e149d92da037b8ec6e0151dd5b9c4a80fea18ceb", "claims": [ { "id": "claim_b47979774aa94e7dae517a6894c508d4", @@ -8,7 +8,7 @@ "evidence": [ { "resource": "repo://TFI/App/MobileAppModel.swift", - "version": "repo-file-v1:sha256:c2cb4b92ca17a7787641c64d2e87058979c4f731f2d098c63aa164ebc695c02b" + "version": "repo-file-v1:sha256:fc361c911553c831e066539b9f2c0df902eb588f71f3bac8606c0a48048a196e" }, { "resource": "repo://TFM/App/MCPServerController.swift", @@ -315,10 +315,36 @@ "version": "repo-lines-v1:sha256:bbe609256f29f5d3f57df07fd079fd2175c599b92d24faf60060b97a17b592d7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0MWJjMzU3ZWE2YzA2MmNiNjE1MGRiZmIwY2Q3YTBhZTM4ZTA3MTllYzU1Y2QwOWY0OGY3Y2U4MGVjOTcyZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjYzgwYmFjZGQ0ZWUwOTdkYmRlMzFjZTEzNGIzM2YyODlhMzVhMWM1ZTBmZjNhYmFhZWI3YTVhNmY5ZmVmNDEifQ" } ] + }, + { + "id": "claim_43b43ec4cc7648a6b1aa639440f83140", + "statement": "MCP upload disconnect synchronously cancels the active transfer token and records disconnect provenance separately, allowing 410 cancellation plus resumable pause while preserving 502 mapping for unrelated SFTP cleanup failures; attempt IDs isolate old callbacks and committed success survives late disconnect.", + "evidence": [ + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L177-L197", + "version": "repo-lines-v1:sha256:09225b7b105df303acc8396e48c42c5251ddf2feff12b03d2c52d91c79fb352e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJkOTVmZTFiOTA3OGRkZDkzZWExMWM5MjlhODc4MzAyMzQwOWVmYWIyMDdmMDcwNmRkZDllZGM4NDJlZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI2NWU4NzRiZTc5MTIwY2ExMDZjMzcyZmM5ODlkNjZiZWQxOWMzZTQ0ZjRjMmIzNjJiZjUxMmU5ZWZhYWI4MjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyZTVkNGJlYjE4YmNmZTY3NjVlZTBlYzQ4MjZhODg2ZWY0ZTFlMGNiYjc3NTE0ZmQ2M2ZhOTIwNDRmNzEzODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MTFhMDA0NWFiYzgzOWNjYzM0YjVkZjRkYWNiY2ZiYTFjMmUyMjBlOTQ0NDM1MTM0ZTY1NjA1YmJmNGU0MmYifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L251-L276", + "version": "repo-lines-v1:sha256:3792a7f25d2ec146b7b352c37c6e4a5fd9883de3c0451915c4f7da84dbc9887e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMWQ3MjAzODMyYmQ5ZTZlNDY5ZjUzYTdkMWQ2MWE5YjM3NTQxOTIyNzliZTY0OTYyYTg5MDVlNzAyNWMzNjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5ZDk1ZWE2MmVhNjlkZmZkZTJmYmI1ODE4MDZhNzgyMTViODcxYzU3ZTliZWZmNjMxN2YxN2YwYjVmMmM1YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiODM5NTZmZDAzNzU5M2UyNzUyMGYwN2I1ZDQwYzhiZWRjOTkyMTVlNDMxMTA0ODA0NWZiYjJmY2IwMGZlMzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUxODUyZWMwMmU4N2ZiYTQ5MDQ1OGJlNDc0YWY1ZDliYmJhZjk0NWJmYjQ4MzhjNmIxY2Y5NGVmYjRmMWZmYmYifQ" + }, + { + "resource": "repo://TFMTests/MCPUploadTests.swift#L21-L53", + "version": "repo-lines-v1:sha256:2e8d63e4f97cfb831287755652f9f735659bc2871d71a3b7827defbae6ff055c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3ZjY0NGJkNTI1YmI3YjdjMjFhYzJhODY3ZDgyM2IwM2M3NzQ3NjFkYzQxMzg2NDIxMzYxMDNlYjNjNjcyYjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg5NWEyYTgzNmJjZWYyMjY5YTI4MWRkMzQ3MjdlN2Q0ZTI1YTlhMGU4MDc4ZTI5YjM5MTg1OTQ2MDZlNzNmN2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgyYzFjZjUxZDU4ZWIzOWQyNDBjM2EzNjc2NTM0NzhhMWYwOWFkNGM3NjAwOWY3YWU1M2U2Y2E2OThiOWM1MjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlmMmMyMDczMWRiOTVmNTA1MGU4NTgwNDY4MDYwNGQ5MzZiY2MxNWIwMjI4MzNhMDBlM2I1ZWQ4ZWZkYjViYTAifQ" + }, + { + "resource": "repo://TFMTests/MCPUploadTests.swift#L349-L385", + "version": "repo-lines-v1:sha256:abaaf8c46f62023236e935d4b7d44779766e48bbd0cc68f16f6592abfb97c637:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRlMDQzYTczOTBlMWNhN2FhMDU2ZTk1YWFmMjE2NTM5YTIyNTkwNjU2ZGI4ZmIwNmI2ZWU5YzMyMWMxMjNlYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlZDdlYjk1NWQzOTAwMmZhMTlmM2MzNTJmYzhmNDNiMTg1MDNiYjRjYjIyNzhiNTFlMjFlMjdkMWFiYjU3ZTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWZjNmY2NzhjN2U5Y2EwY2YxOTY1YmVjZWUzMjA5OWEzNzRlZTc0N2Q2NTE2ZWViZWFiMzQ2NDU0NGVhMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3N2U0MDI5NjM5ZmIzOGQxZGRmYzZjMjllOWJiZTRkNGMxMWVmMjBlOTRiZmM4ZWE5YTdjYzI3NmVlYjUxOTMifQ" + }, + { + "resource": "repo://TFMTests/MCPUploadTests.swift#L409-L423", + "version": "repo-lines-v1:sha256:1b322d04c0951a6a53f975af182a52d23521961f78ab35983ea597819276dbd4:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlmNzNlODA1NDZmYTc2MThhODBmNTNiNjFhMTA3NGYzMGI1MjRlOWE5NzZmNTAxOGJmOWQzY2RlNjNkMDQ1NzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU4ZWFmM2UxZGIyODRmNmE2ZmQ0ZTA2NmFlMmZiNjU0YWU2OGZkZmNmM2U3ZTRkY2E0YzJkZDIxNGE0OWI5ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU5MGRjYjA1YTgxZTQ0NTJkZTczZGU2YjIyNmEyNTg5ZTY2MzY0NDk4OGYxNmJhOTBhOTQ4NzkwMTJlOTZlMTAifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T07:36:39.730Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/operations/build-test-and-release.json b/openwiki/.claims/operations/build-test-and-release.json index 4b5ea775..2e055c07 100644 --- a/openwiki/.claims/operations/build-test-and-release.json +++ b/openwiki/.claims/operations/build-test-and-release.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:0049d9723b546cae3375f18e49fc5c7fc515150d30a5a18e696c9fbb9120813f", + "pageVersion": "sha256:67a0bedc5f971d2d6d6f41e11480cdd93f3b1450d460d8ac56bc1ed65a40264b", "claims": [ { "id": "claim_d34d7b16495a44a4a946d3dc9e0456f4", @@ -197,10 +197,50 @@ "version": "repo-lines-v1:sha256:c11f26108030abfcf1f2cdd98655ff0116e1d44180c4d5eec36e9b6bb15895e9:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjRjMjMzMjczNTc4NTMyYWE3NWIxM2ZiMzRjYzkwYjdkMDIwYWVkNDhmNjY1YTE4MjYxNjdiOTAwMjljN2MzYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTAzYWFlMjllMDYzMTZkNmRhNmY4YzJhOTFmNmU0NGE2MTJmYWJkNzkzYWQxZjVhMWZjYTUyMDZiY2VkYTQ3YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjE3MWVmNzc1NWEyMTNkNzkwZTA1OTU2ZGNhOTRiZjE5Nzk4YmYwNGZhOTNhOGE3MjE0OTgyNTczMDdiM2UwYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWIxYmM1YmM0YTIzN2Y1NzhlNmFmNGE0MTVkMDY0N2Y5OTEwYjAzYjM2NGUxZTI3YzFmMWRiYWE3NDNkMDNiYSJ9" } ] + }, + { + "id": "claim_ad8c264a95cc4d3289e82ff5a7bb19fb", + "statement": "StoreKitTrialTests is a manually authorized, default-off suite enabled with TERMIND_STOREKIT_TESTS=1 and is best run through a narrow TFM only-testing selection.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L56-L58", + "version": "repo-lines-v1:sha256:59b48c1c93af632d0d54f375f34be63bfe084da21dbf98d89f2c5174f4f566ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWYxODBmOWMxMjY5MDNiY2MzMzFhYTk1NDU2ZTQ5NDQ0YTdkNGQwMTFlOGEyYTgzZjVkYzg3MDIzOTE4MTJkMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjQ4MzZlZDdlMDA1Mzc3NWEwYTk2NmNlODYxMzRiMjM0N2Q4NTFhN2IwMmY0ODEyYmViMmM1OWQxYzY4ZDUyNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGMwNmIzZDc5NGYzMzczOTYxOGNhZmVjOTQwY2ZkY2YxZjllYjI1ZjI5YjUyYWJjZmNkZjA0MWY3NTg2YWZmYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTA1MTY5OTI5YTliMjNhOWIwMDJiYjE5MDNhYTA3ZWMxZGE0ODA1ZjY3ZTEyNTBmYzZjOTQ4MDlmMzJlMGM3ZSJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L1-L18", + "version": "repo-lines-v1:sha256:01f6bcd9c1be6ab834bd576093f823f2b13ebdc7043b843fc256fa8ab1b51bb3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyMzkxMGFkMTQyYWFlZTQ0YzE0ZGU2YTYxODVkZTBiY2NhNDhlYzZlZGQ3ODFlNWU1MjQzYjdiODk2N2VkMTkifQ" + } + ] + }, + { + "id": "claim_8dd9feb7f27b4681a46083d7f10dd0d9", + "statement": "The four mobile UIKit key-window integration suites require TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1 and an Xcode invocation with parallel testing disabled; they are not covered by default-suite success.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L50-L54", + "version": "repo-lines-v1:sha256:5119b61ad141f6edb840c60139e371386a18a8cd4b9f5912a76c1c142abedb1f:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODQ1MThlZDQyNWZhZWNhNTgxNGExYzEwMTU0ZjEzMjgxOGViM2VjMGI2ZTgzOThkZjQ5NGI4YWRmN2MzYTI0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTAzYWFlMjllMDYzMTZkNmRhNmY4YzJhOTFmNmU0NGE2MTJmYWJkNzkzYWQxZjVhMWZjYTUyMDZiY2VkYTQ3YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWIxYmM1YmM0YTIzN2Y1NzhlNmFmNGE0MTVkMDY0N2Y5OTEwYjAzYjM2NGUxZTI3YzFmMWRiYWE3NDNkMDNiYSJ9" + }, + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T03:23:14.035Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/operations/storefront-commerce-release.json b/openwiki/.claims/operations/storefront-commerce-release.json index 16ec63b0..88524c74 100644 --- a/openwiki/.claims/operations/storefront-commerce-release.json +++ b/openwiki/.claims/operations/storefront-commerce-release.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:1f74472fe60ef3963fee0dcf3fb7a78120097f664925842fc6db119dbd10d57f", + "pageVersion": "sha256:d7f1ff3b8eb7120aba2d023df8fc286ed253a0f183a332c4d4c6df27aba330a7", "claims": [ { "id": "claim_3d431c8c7b804187a1ef5a7d9178c430", @@ -71,10 +71,24 @@ "version": "repo-lines-v1:sha256:6a76d54ef19ad5c6b564234115af5f6d34dd4d600f01435ccb6ed36cebdbdf5f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNkMDE3YjUwYWY0OTFiMzExZTAwMzA0N2UzMGMyYWUxMDFkZDRkZjllMzRkNmE5YmI5OGEyZTIzZjIzNDI4NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEzMGQ3MDNjMjIxZGFkNmRhMzg4Yzk5MjdjOTQyN2I5YTY5NTBiYmNlZTliMzM0NGY4OGY4YjAxOTZmNGU0OWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk3YWNjMWEyNTVjNDVmNzk5ZTAyYjM4Zjg1MGVmOGIzMDMwOTNiZTE5OTZiMDIwNTE5MjU0YzRkNjYyYTViMzAifQ" } ] + }, + { + "id": "claim_d3350be7dc9c4d2faf80fbc61d192968", + "statement": "StoreKitTrialTests requires manual StoreKit authorization and an explicit TERMIND_STOREKIT_TESTS=1 opt-in; default test-suite success does not count as storefront lifecycle evidence.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L56-L58", + "version": "repo-lines-v1:sha256:59b48c1c93af632d0d54f375f34be63bfe084da21dbf98d89f2c5174f4f566ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWYxODBmOWMxMjY5MDNiY2MzMzFhYTk1NDU2ZTQ5NDQ0YTdkNGQwMTFlOGEyYTgzZjVkYzg3MDIzOTE4MTJkMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjQ4MzZlZDdlMDA1Mzc3NWEwYTk2NmNlODYxMzRiMjM0N2Q4NTFhN2IwMmY0ODEyYmViMmM1OWQxYzY4ZDUyNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGMwNmIzZDc5NGYzMzczOTYxOGNhZmVjOTQwY2ZkY2YxZjllYjI1ZjI5YjUyYWJjZmNkZjA0MWY3NTg2YWZmYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTA1MTY5OTI5YTliMjNhOWIwMDJiYjE5MDNhYTA3ZWMxZGE0ODA1ZjY3ZTEyNTBmYzZjOTQ4MDlmMzJlMGM3ZSJ9" + }, + { + "resource": "repo://TFMTests/StoreKitTrialTests.swift#L1-L18", + "version": "repo-lines-v1:sha256:01f6bcd9c1be6ab834bd576093f823f2b13ebdc7043b843fc256fa8ab1b51bb3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyMzkxMGFkMTQyYWFlZTQ0YzE0ZGU2YTYxODVkZTBiY2NhNDhlYzZlZGQ3ODFlNWU1MjQzYjdiODk2N2VkMTkifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-22T03:23:14.035Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/quickstart.json b/openwiki/.claims/quickstart.json index 1f59b1a5..e1805c5a 100644 --- a/openwiki/.claims/quickstart.json +++ b/openwiki/.claims/quickstart.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:255121c2ef0130528b47855870bb8f42015ef543c8d3f140d6f7673f0051a3bb", + "pageVersion": "sha256:582572b4a7b9b58da44bb0abd4c5be3d8b7d6890b0653e69a98f75ceaabb4dc7", "claims": [ { "id": "claim_83870f4f1ad6403ea0d8bd8775729a76", @@ -104,15 +104,15 @@ }, { "id": "claim_49b7e46dc58948ad8a9cfb47be7bd41d", - "statement": "Routine Xcode validation must use `-onlyUsePackageVersionsFromResolvedFile`; the repository defines TFM/TFI build entrypoints, strict Swift formatting, and a localization validation script.", + "statement": "Routine Xcode validation must use -onlyUsePackageVersionsFromResolvedFile; the repository defines TFM/TFI build entrypoints, strict Swift formatting, localization validation, and explicit gates for visible-window, StoreKit, and mobile key-window tests.", "evidence": [ { - "resource": "repo://AGENTS.md#L26-L34", - "version": "repo-lines-v1:sha256:a91f88bee69fc799fe765bb122f6b5106642c565be05fb945780ea3d3248ebf3:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTczYmEyNjc2YzMzZGE1MjMyNTFkNWNjZjUzNjViODFhMGEwMjMyZWU3MGI3OTFlNmJjMTNmYTIyNTFhMGQ4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjNkZGYwYjM4NzRlMWFmZmJlMzgwYTg1YWUxM2E2MDNhMzNjZmNmMDQ4YzBmNDM0NTNmYThkOTY4OTcyOTk2OCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzE4ODJlOTJlMDU3MTI0MDRlN2M4MWQ2Mjc2NTIzMWQ5Y2VkZjg5MGNlNmUwZTVlMDBhZDk2NzIzOTA5YzA3OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTMwM2VlYzhlNjU1MjZiMzI1MWUyNDRmNjljMGIwOTMwY2ZmNTFiZDhiODRkMzZhMzk1NzJlZDg3YjIyOWZkOSJ9" + "resource": "repo://AGENTS.md#L24-L32", + "version": "repo-lines-v1:sha256:a91f88bee69fc799fe765bb122f6b5106642c565be05fb945780ea3d3248ebf3:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTczYmEyNjc2YzMzZGE1MjMyNTFkNWNjZjUzNjViODFhMGEwMjMyZWU3MGI3OTFlNmJjMTNmYTIyNTFhMGQ4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjNkZGYwYjM4NzRlMWFmZmJlMzgwYTg1YWUxM2E2MDNhMzNjZmNmMDQ4YzBmNDM0NTNmYThkOTY4OTcyOTk2OCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGI1ODA1YWE0ZDNjNDk5NWQ4OWQxZTA4ZjY5NjkyNDg2MjMyNzJkYTZiNjk2ZGEwZWNlMjUxMGY2NjE3Y2RjMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTMwM2VlYzhlNjU1MjZiMzI1MWUyNDRmNjljMGIwOTMwY2ZmNTFiZDhiODRkMzZhMzk1NzJlZDg3YjIyOWZkOSJ9" }, { - "resource": "repo://AGENTS.md#L40-L48", - "version": "repo-lines-v1:sha256:b27ae6846aa3bf61e897870b4f13db11ff5622aa74b65e7c5b153d79066742e2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3ZmM5ODZlZjAxZWU1ZjU4YzZmMWZhMjY1YjhhNjQxMzExNjhmOGFhNTk5M2Q3ODg2ZDk2YzZlMTg5ODk0MzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0NTE4ZWQ0MjVmYWVjYTU4MTRhMWMxMDE1NGYxMzI4MThlYjNlYzBiNmU4Mzk4ZGY0OTRiOGFkZjdjM2EyNDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxZGE4Nzc5OGNjOTU3NTdiNzc0NDU4ZWM0OWI4NDFkY2FjMzgwNTFkOTI5Y2RiOTgxOTBiYjE3YjI3ZGZkODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZGM0MzMyOThhZDU4MTI5ZWUwYTg4NTJjMTNmNTI5MjBkYWFmOTRhOGM2NGE0ODcxZWVlZjQ2ZTEzMTVlZDIifQ" + "resource": "repo://AGENTS.md#L38-L50", + "version": "repo-lines-v1:sha256:1504391daf2c6128093191cf4154eebb7c2a830ea322086e3cbf2a30066708cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3ZmM5ODZlZjAxZWU1ZjU4YzZmMWZhMjY1YjhhNjQxMzExNjhmOGFhNTk5M2Q3ODg2ZDk2YzZlMTg5ODk0MzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0NTE4ZWQ0MjVmYWVjYTU4MTRhMWMxMDE1NGYxMzI4MThlYjNlYzBiNmU4Mzk4ZGY0OTRiOGFkZjdjM2EyNDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQxZGE4Nzc5OGNjOTU3NTdiNzc0NDU4ZWM0OWI4NDFkY2FjMzgwNTFkOTI5Y2RiOTgxOTBiYjE3YjI3ZGZkODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZGM0MzMyOThhZDU4MTI5ZWUwYTg4NTJjMTNmNTI5MjBkYWFmOTRhOGM2NGE0ODcxZWVlZjQ2ZTEzMTVlZDIifQ" } ] }, @@ -139,10 +139,36 @@ "version": "repo-lines-v1:sha256:c502b82a80a53329d256f638301cdaa11432ebba24ee9005eec7d39ed1f5dd65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTU3NzFmOGY3ZmZmZjlhMjNkOWZiZTMyYTlmYzAwYzc0ZjRjYTU2NWY2NjFhNmQ2YmYxNGRiNmY2ZTlkNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNjVlZGIzMWM5NDAzM2M2ZWVmOTIzOTQ3OWMzODczYzY0NzhhYmMyMWZhZDZlODJkYjJhNWU5ZDMyYzRhNmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwNjZjMGUyZDJhNzZhZGViYTcyYzdmMjY3OWJlMTNkM2UzZWM5MzgzNzFhODliNTUzM2I0OTk0OTQwZTBhOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzY2EyMjE1MzJlMmQwOGM1YTgwYjA2MDJmNTBkMzIyMjBmMjZhMTBlNGFmYmYyMzA1NWU2ZGU2MmY3Yjk2ZDkifQ" } ] + }, + { + "id": "claim_a6f36ae9e4474af5b27f98e2f5d44acd", + "statement": "The four mobile UIKit key-window suites are omitted from routine parallel validation and require TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1, narrow only-testing filters and parallel testing disabled.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L46-L46", + "version": "repo-lines-v1:sha256:20f70f81c29f72ceb3b8c0d0ed710e433bbc08043f50f8fa17d7544dbd06708d:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjBmNzBmODFjMjlmNzJjZWIzYjhjMGQwZWQ3MTBlNDMzYmJjMDgwNDNmNTBmOGZhMTdkNzU0NGRiZDA2NzA4ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjBmNzBmODFjMjlmNzJjZWIzYjhjMGQwZWQ3MTBlNDMzYmJjMDgwNDNmNTBmOGZhMTdkNzU0NGRiZDA2NzA4ZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjc0N2NkNGE1NTI3OTAxN2Q1ODYxMzE3ZjlhZDcwZWY0ODIwZTFiOTAxYzI4MDFhYjE1MTdjZGI5NjhiZjliYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmVjYzgxYWMyZjE3ZWVjMDQwYzhjNWM2NzYwODkzYjJkNmJmMmJkMjVhN2M4MTlkMTBhMGMzMWZmMjE5M2NhZiJ9" + }, + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T08:21:59.182Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json index d7fcc69e..4803b3af 100644 --- a/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json +++ b/openwiki/.claims/testing/behavioral-boundaries-and-fixtures.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "pageVersion": "sha256:3c5890c5831313021a01c7840ee3ab3fa0b82708cc9e2b60a336255809efbf3f", + "pageVersion": "sha256:af03fbc03d89a8fd3c34ff1e83c1df89dc2a3c9f26c59993e76ae74434fe1ffa", "claims": [ { "id": "claim_b7fbd64158554b8b8ca80a0a613b3bc3", @@ -369,10 +369,64 @@ "version": "repo-lines-v1:sha256:c502b82a80a53329d256f638301cdaa11432ebba24ee9005eec7d39ed1f5dd65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYTU3NzFmOGY3ZmZmZjlhMjNkOWZiZTMyYTlmYzAwYzc0ZjRjYTU2NWY2NjFhNmQ2YmYxNGRiNmY2ZTlkNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiNjVlZGIzMWM5NDAzM2M2ZWVmOTIzOTQ3OWMzODczYzY0NzhhYmMyMWZhZDZlODJkYjJhNWU5ZDMyYzRhNmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwNjZjMGUyZDJhNzZhZGViYTcyYzdmMjY3OWJlMTNkM2UzZWM5MzgzNzFhODliNTUzM2I0OTk0OTQwZTBhOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEzY2EyMjE1MzJlMmQwOGM1YTgwYjA2MDJmNTBkMzIyMjBmMjZhMTBlNGFmYmYyMzA1NWU2ZGU2MmY3Yjk2ZDkifQ" } ] + }, + { + "id": "claim_07d623d7949c46aaa9ae28d047d5c2d9", + "statement": "HermesMemoryTests uses actor continuations to await history-index replacement counts and controlled barriers for stale-generation writes/reviews, avoiding load-sensitive wall-clock polling for the key lifecycle event.", + "evidence": [ + { + "resource": "repo://FrameworkTests/HermesMemoryTests.swift#L116-L181", + "version": "repo-lines-v1:sha256:ce72d05810dc1671686e744e8d2fdaf825e3662b3dd2f726191909a92e01ab3a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3MjA1MzIyZDIyNTY5NmVkODY4OGRlYzk5N2NjNGMyMzZlN2M4ZDVhMDY4ZjBmOGEwNzg0NWJkYzk4NWJhYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkMmZlMGM5ZTQ2MGJjYjM4YTQ0YzY4YTY2OWNlZWY3NDA4YWExNjcxMmUwMjZlMmFmZGI0Yzk4NTc5YzlmMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0ZWMyOTlmNTE0NWQ5M2I1YWM4OWZmMzVlOTE2YjE5ZDE3ZTA3NTg0MWQ1ODIwODI4YzdlMjJjZDMzZjdiYzYifQ" + }, + { + "resource": "repo://FrameworkTests/HermesMemoryTests.swift#L247-L337", + "version": "repo-lines-v1:sha256:f5eb0859e6d2cb250d5574f7a492c1bd5042b86eb01c1632cfcb4a76b6933dff:eyJzZWxlY3RlZExpbmVDb3VudCI6OTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxNTlkY2U2MjQxZGRhYzhmODgzNGVkYjM2ODgwZmJiMDdhZGMyOTI0MjQ3M2VlMTViZWRjYjgyNGM3MjZmYjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiOGVkOWM5YmY2NmU1ZGI0ODc0ZjE5NThiNDA1OThjMmE1MjFjODUzMTk1ZDk3NjdmNWVmYWJiNjAwOThhNjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE2NzRiNjdiYWYyYmMwZDEwOGJmMWJkZjIwMDVkZWVjNjM3NThlNDVjNGJiMTE2ODk3ZjliZGM2YjhkMGQ0OTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE4MWRiYzk0YmUzNzhmYjY1OGU4MTAyZGU2ZGRlNDBkMzgwMmRmNzJmY2MwYjc5ZGVhOWU0ZDM3Y2FiNmFkZTYifQ" + } + ] + }, + { + "id": "claim_479a7f8dcd7d4fe9918a9b225ad08471", + "statement": "MCPUploadTests waits for the fake destination to start before exercising cancellation owners and requires HTTP disconnect to return 410, avoid commit, release activity, record an error audit and leave the ticket paused.", + "evidence": [ + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L177-L197", + "version": "repo-lines-v1:sha256:09225b7b105df303acc8396e48c42c5251ddf2feff12b03d2c52d91c79fb352e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2YjJkOTVmZTFiOTA3OGRkZDkzZWExMWM5MjlhODc4MzAyMzQwOWVmYWIyMDdmMDcwNmRkZDllZGM4NDJlZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI2NWU4NzRiZTc5MTIwY2ExMDZjMzcyZmM5ODlkNjZiZWQxOWMzZTQ0ZjRjMmIzNjJiZjUxMmU5ZWZhYWI4MjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyZTVkNGJlYjE4YmNmZTY3NjVlZTBlYzQ4MjZhODg2ZWY0ZTFlMGNiYjc3NTE0ZmQ2M2ZhOTIwNDRmNzEzODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MTFhMDA0NWFiYzgzOWNjYzM0YjVkZjRkYWNiY2ZiYTFjMmUyMjBlOTQ0NDM1MTM0ZTY1NjA1YmJmNGU0MmYifQ" + }, + { + "resource": "repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift#L251-L276", + "version": "repo-lines-v1:sha256:3792a7f25d2ec146b7b352c37c6e4a5fd9883de3c0451915c4f7da84dbc9887e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlMWQ3MjAzODMyYmQ5ZTZlNDY5ZjUzYTdkMWQ2MWE5YjM3NTQxOTIyNzliZTY0OTYyYTg5MDVlNzAyNWMzNjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5ZDk1ZWE2MmVhNjlkZmZkZTJmYmI1ODE4MDZhNzgyMTViODcxYzU3ZTliZWZmNjMxN2YxN2YwYjVmMmM1YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiODM5NTZmZDAzNzU5M2UyNzUyMGYwN2I1ZDQwYzhiZWRjOTkyMTVlNDMxMTA0ODA0NWZiYjJmY2IwMGZlMzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUxODUyZWMwMmU4N2ZiYTQ5MDQ1OGJlNDc0YWY1ZDliYmJhZjk0NWJmYjQ4MzhjNmIxY2Y5NGVmYjRmMWZmYmYifQ" + }, + { + "resource": "repo://TFMTests/MCPUploadTests.swift#L349-L390", + "version": "repo-lines-v1:sha256:53c278ee0d7b5f24c8d269dcbe6263662af712762339d7d30f00ec928b8692ab:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRlMDQzYTczOTBlMWNhN2FhMDU2ZTk1YWFmMjE2NTM5YTIyNTkwNjU2ZGI4ZmIwNmI2ZWU5YzMyMWMxMjNlYzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3MWZmNDc1YjI4ZjA3ZTAxZmYyZDlkMDNlNGI1Njg3MWU5ZmE2ZDZiN2RkZmU2NTBmOGQyYjEyYmE3ZWZlZWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlOWZjNmY2NzhjN2U5Y2EwY2YxOTY1YmVjZWUzMjA5OWEzNzRlZTc0N2Q2NTE2ZWViZWFiMzQ2NDU0NGVhMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVmNTFmNjliM2QwYWU0YWZiNDk3MDc0MmQzNTcxMzZhMzc2MGFhM2QxMjM5ZWZhY2RjMmQ1ZTEwYzhjNDA4MDMifQ" + } + ] + }, + { + "id": "claim_b9219599d1c743eea0f97eb6a7798624", + "statement": "The four mobile process-global key-window suites are disabled by default and must run with TERMIND_MOBILE_UI_TESTS=1 plus Xcode parallel testing disabled; serialization within each suite is insufficient to coordinate separate suites.", + "evidence": [ + { + "resource": "repo://TFITests/MobileEditorToolbarTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:f3c48bd72cbd1f8c19ae509d0b42757cc76641b1ba1ab1eba51d555a48751658:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxNTMzZDM3NTZmOGMwZjgyNDc4YjE4NWUzODYwNWNlOTA0MDBhM2Q1ZjlmMDA0MGIxMDgzZjllMmRjYjVkMWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkNGQ0Njk5YWY0MjE4ZTg0M2FhOGQwYzZjNTY3YjEzMzMxYzA5MWQxYTgxMzI0ODE4MWM1ZTRjMjQ5MDRlN2IifQ" + }, + { + "resource": "repo://TFITests/MobileForwardingRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:1c43b4b67ff75200d330c0e6dcb8a7c652af56899d58c6fccc9aca89262ec5fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1OTEzNmM3MGIwNGU3YjM0Y2U1OWIzODMxZGFiNjY0NWIwY2MxOWIzMjg3NmQ1ZmFiZjNmMmVlMjY5NTg4MmMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4M2IxNTM3YjRlN2Q0NWFhNWJmZGM5YjZlY2RkZGUwMmM5ZGFmYTEyNTQ5ZjUyMjRkMDc3Mzk5MGZjZjFhZTQifQ" + }, + { + "resource": "repo://TFITests/MobileHostIntegrationRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:07c662e83a529833bf9aea2b962509e8653d5bf9062d208b941f887f04216160:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYTliNzVmNjRiMTA2ZWI1NDRkZTIwMWY1OTkxMjJkNzVhYmM0ODNjNWIxNTZiYmE2YzA1YzBmOTU2ZGJjM2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMxOGMxYWY1Y2Q1MjFiYTJlYmNkZGZjM2U1YzEzMWRhNDhmMjA1YTAyNWMwOTU2Nzk2Yjc2NTBkMDg3NDQzOGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRjN2VkN2ZlM2FkNDdlMGNlOWFkZjhjODdkZDEyZmZlYzczMmI5Y2FkNjZhODYyMzI4ZTgwNTM2ZjIzMzBlOGUifQ" + }, + { + "resource": "repo://TFITests/MobileSavedNameRenderingTests.swift#L1-L12", + "version": "repo-lines-v1:sha256:0eddd809cc4a05cb51814cbb15b23dc1a881757e4f63cd610d210108342a4038:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVlOTE5NjUyYjQ3ZmJjZTM2Y2UxYzA4NTE5MjFlZWY4YjBmZjE1NGNiODMyYjlkNTY2YTY0MDYyMGQ2MjFmNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMxODAyNTA0ZjI1YmEzY2QwYjhmZjEwMGI5NGJlYjUyNmQ1ZWUyNTQxYzJkODkwZDgxZjNhNDk2NjNiMTIxYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2E3MWVjMzZhNzBmOGVmYTc5MjkzNWJjZDAzYWM5YjdhZjhiZjAzNmExMjI5NmM1NTFlYjA3OGQyMjI5YmEifQ" + } + ] } ], "verification": { "by": "openwiki/0.5.2", - "at": "2026-09-23T06:54:45.592Z" + "at": "2026-09-23T10:06:09.363Z" } } diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index d63103f4..420d7b67 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,7 +1,7 @@ { - "updatedAt": "2026-09-23T08:26:51.160Z", + "updatedAt": "2026-09-23T10:25:40.794Z", "command": "update", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", "model": "host-agent/codex", "status": "complete", "language": "en" diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json index c3638c44..0a7d7c79 100644 --- a/openwiki/.page-manifest.json +++ b/openwiki/.page-manifest.json @@ -2,46 +2,46 @@ "schemaVersion": 1, "pages": { "/openwiki/architecture/desktop-ui.md": { + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c", "pageVersion": "sha256:fb2e8faf240307b667e408bf1a3ffe425899dcf6d34f9dae8cccab4e30a70c88", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e" }, "/openwiki/architecture/engineering-invariants.md": { - "pageVersion": "sha256:ced917378133d346e25f9793e04d360a19ff15bd567435b4183562c3dc9fab09", + "pageVersion": "sha256:35da0200127e36e0161d9ebfabc103070580eff4d348f77b92091549c2d15e27", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/mobile-ui.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:3e4fffdec19e331d34ec12f5b4ead52c83e7d4d17e8f0937e8ba884b0d26ed34", + "pageVersion": "sha256:6f758688dba0fd6ecc5da1905671f8372325879f816a81755431c0cc4469c04e", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/module-map.md": { - "pageVersion": "sha256:eebf0a77f06515f063a173fdf53ac96cca81aeae0519a99c3c2697d94c59429d", + "pageVersion": "sha256:4f2820877ec3b78a77319f48a9cfee8f397b3bf2216d9de35fce3ebdf601f2c9", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/native-app-directories.md": { - "pageVersion": "sha256:7b943e2d155437d61aafb7681db058ef94efb0afbdab534a552eeaee29dc8413", + "pageVersion": "sha256:71a3c6e74feb945c25eb8f6d8025534596d9d7cdb41b67b79887004ca006d40d", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/platform-runtime.md": { - "pageVersion": "sha256:ed3f410b516491c6b31b18037bf8c9a0274dc08a50a13080e5d7906c041da1af", + "pageVersion": "sha256:63336e85b6d432a53dd31486e99e84d3cb86d592f42771810bab0fc8e9e52392", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/shared-module-directories.md": { "gitHead": "ae38d77013b7502c185781920ebfd4236afb1e6b", @@ -51,39 +51,39 @@ "completedRunId": "3212507d-fdea-4335-9994-e4980af3ccef" }, "/openwiki/architecture/supporting-system-directories.md": { - "gitHead": "12fee3a5c735ed6f5b9ef8ecd854e326527f186c", - "sourceFingerprint": "sha256:2d19bb85bb852de045b9a3c58808dc15f2c945041b63d4a53ead6dd37ceef43d", - "pageVersion": "sha256:bc62050f5799c6e7764ff9d08a0e721dda371eb74b7b8342346c59f747abb3b5", + "pageVersion": "sha256:6971c304cb02eb6f308dbbbc6833b74c5a84b608c4e4a8435a9716000c404ec9", "completedBy": "codex", - "completedRunId": "fad30cd1-ec54-497a-96f1-7175412c6b64" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/architecture/system-boundaries.md": { - "pageVersion": "sha256:2552fa8187812755085075681b5a5148ef62a16224616146ec131bd1fb880d64", + "pageVersion": "sha256:18ed1963cf31915d010fc6c2937f77cd377fff2d65fae74d1c7ae7c9793f3700", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/concepts/agent-runtime-and-assistant.md": { - "pageVersion": "sha256:ee7c8c1d70af9d78c4effdc07f9f5b65fb08c92a14abee40c88be00a9df41c87", + "pageVersion": "sha256:7fbe6994d8040bdf4a14e40af86c0197d0e560cafa0f7f62a0803988dbf4f85e", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/concepts/commerce-localization-and-product-surfaces.md": { - "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", - "sourceFingerprint": "sha256:8456f71ffdd978736ff6bf9aed042724573fd633b1d40f6d2b74fec0acb445f6", - "pageVersion": "sha256:a1c9a480314b124cb096731918e3a208c7dab2e590e8c42bcfa94456f9440b9c", - "completedBy": "omp", - "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" + "pageVersion": "sha256:0387b384f0e9df7daaf614e6d1e66ed31e2d0bd1f198bce95d1d8e6191d2e00b", + "completedBy": "codex", + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/concepts/persistence-and-secrets.md": { - "pageVersion": "sha256:2c7b5e94e1ecc8ef99eb9c4ea83b9ffb4420e8a9229b17a20f93376b48e8633f", + "pageVersion": "sha256:b564d4439f025bc55d8b4c0a8f3054174469499c9351087e0dd6383a4c70fe33", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/concepts/remote-services-and-forwarding.md": { "gitHead": "bdeb201d19b1d86d1bf1145d6cd6bbfbd95bd4a1", @@ -100,32 +100,32 @@ "completedRunId": "1928445d-30c0-4dcb-b85b-38ecfc74a433" }, "/openwiki/concepts/ssh-multiplexing-and-scheduling.md": { - "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", - "sourceFingerprint": "sha256:8456f71ffdd978736ff6bf9aed042724573fd633b1d40f6d2b74fec0acb445f6", - "pageVersion": "sha256:eedd08fcec41ac31b854d4f0375e28efcca4b20b1dfac2d25880b18421e8d199", - "completedBy": "omp", - "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" + "pageVersion": "sha256:ec75319c1b21afb2ec24dfee47fe74a0d32ad67f1f7e04314e7c3a6a07e65937", + "completedBy": "codex", + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/concepts/ui-design-principles.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:3204534fc02c6315b0c2fe23d6cb486a18f8b86be9b8a42dd31523c3bbfd560c", + "pageVersion": "sha256:c449e2380e48e619eda9f0ac0151d8491634d439a2740a92763fb4df6b8b7fcc", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/integrations/mcp-server-and-bridge.md": { - "gitHead": "42e7766127e4b31d41296740f46c08c9b0bb063f", - "sourceFingerprint": "sha256:abf4e2d831f004bdc87fc30b20d0c3c96e898a5cec5654d292675be4a3378a82", - "pageVersion": "sha256:e8702d030f8cc69c70ce0b630321aa0bf139b70e4dfb9e29b469cb5cfd77a53c", + "pageVersion": "sha256:ecf21d72d9c031ff95071424e149d92da037b8ec6e0151dd5b9c4a80fea18ceb", "completedBy": "codex", - "completedRunId": "2fd5fd33-d302-431f-8b58-eecdb3c5515e" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/operations/build-test-and-release.md": { - "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", - "sourceFingerprint": "sha256:8456f71ffdd978736ff6bf9aed042724573fd633b1d40f6d2b74fec0acb445f6", - "pageVersion": "sha256:0049d9723b546cae3375f18e49fc5c7fc515150d30a5a18e696c9fbb9120813f", - "completedBy": "omp", - "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" + "pageVersion": "sha256:67a0bedc5f971d2d6d6f41e11480cdd93f3b1450d460d8ac56bc1ed65a40264b", + "completedBy": "codex", + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/operations/cloudkit-release-readiness.md": { "gitHead": "2ded2a56a35c168034bdb82043a88bc0466e2088", @@ -149,25 +149,25 @@ "completedRunId": "8bdb277a-cf85-4174-a91f-b6a4f89a046d" }, "/openwiki/operations/storefront-commerce-release.md": { - "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", - "sourceFingerprint": "sha256:8456f71ffdd978736ff6bf9aed042724573fd633b1d40f6d2b74fec0acb445f6", - "pageVersion": "sha256:1f74472fe60ef3963fee0dcf3fb7a78120097f664925842fc6db119dbd10d57f", - "completedBy": "omp", - "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" + "pageVersion": "sha256:d7f1ff3b8eb7120aba2d023df8fc286ed253a0f183a332c4d4c6df27aba330a7", + "completedBy": "codex", + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/quickstart.md": { - "pageVersion": "sha256:255121c2ef0130528b47855870bb8f42015ef543c8d3f140d6f7673f0051a3bb", + "pageVersion": "sha256:582572b4a7b9b58da44bb0abd4c5be3d8b7d6890b0653e69a98f75ceaabb4dc7", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/testing/behavioral-boundaries-and-fixtures.md": { - "gitHead": "f9d3cd9e6831951262ed934f7d6fb5f782401496", - "sourceFingerprint": "sha256:470a7b67217aacb7d87b7fddf13971bf2d3fbdef0393232dd0af8c1a20c38167", - "pageVersion": "sha256:3c5890c5831313021a01c7840ee3ab3fa0b82708cc9e2b60a336255809efbf3f", + "pageVersion": "sha256:af03fbc03d89a8fd3c34ff1e83c1df89dc2a3c9f26c59993e76ae74434fe1ffa", "completedBy": "codex", - "completedRunId": "21cd41b9-0003-416e-86e9-126eaf940714" + "completedRunId": "b656c41e-8a8b-416f-97a3-1d80a5d73048", + "gitHead": "93717005b8635e95454721bb769dbc134b09a023", + "sourceFingerprint": "sha256:94019c07d8390b8b80f999dedb97c249f32c1bc428f3be0de99d102add10a829" }, "/openwiki/workflows/port-forwarding-pool-and-dispatch.md": { "gitHead": "58a0cfd1bf9d08057801c740e79bbcb3c1fcc37e", @@ -191,11 +191,11 @@ "completedRunId": "691ddd1f-2966-4a5b-b437-127adf28ff38" }, "/openwiki/workflows/ssh-session-lifecycle.md": { + "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", + "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c", "pageVersion": "sha256:c2af67d394cef803d994353ab5c3de83d224bf76ea40f58ab3cc274a8ec6329b", "completedBy": "codex", - "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e", - "gitHead": "9dc90787df17eecdf99fe3772b6d3940b11dca6b", - "sourceFingerprint": "sha256:0ee98d82fdc0728e69cf29d5feb2cee02a6f4f906947caa959ad13197eb9210c" + "completedRunId": "0f19cd13-5045-4067-a13c-959543e6781e" } } } diff --git a/openwiki/architecture/engineering-invariants.md b/openwiki/architecture/engineering-invariants.md index afed5594..3bdcbdaf 100644 --- a/openwiki/architecture/engineering-invariants.md +++ b/openwiki/architecture/engineering-invariants.md @@ -14,8 +14,6 @@ sources: resource: repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift - id: openwiki-source-1c48f72c0c7a3164d7d5c715 resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift - - id: openwiki-source-2d98a2885dc21f508f49b749 - resource: repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift - id: openwiki-source-5c91b796f7e6d651aedf3023 resource: repo://Frameworks/TermindApplication/FeatureModels/CommandHistoryModel.swift - id: openwiki-source-dc7b363138921b0923d2dc2f @@ -118,6 +116,8 @@ sources: resource: repo://TFM/App/AppModel.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift + - id: openwiki-source-396f97a332870d48c065d7e9 + resource: repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift - id: openwiki-source-0854be84b166268626a3cb08 resource: repo://TFM/Features/Sidebar/WindowSidebarView.swift - id: openwiki-source-22b9fca540c3029c5ace451e @@ -128,12 +128,14 @@ sources: resource: repo://TFMTests/AppTerminationTests.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift + - id: openwiki-source-47dc468698b2904f0004ee04 + resource: repo://TFMTests/MCPUploadTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Cross-Cutting Engineering Invariants @@ -216,9 +218,11 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r MCP/SFTP cleanup. Only after those joins return does it close Agent background commands, then the connection pool. Repeated shutdown returns the same cleanup task; an independent three-second native fallback replies once even if a save or drain remains blocked. Joining an attempt does not prove save success or completion before process exit. - Shared unstructured SSH authentication/pool setup can outlive runtime cancellation. Existing manual compaction, titles and - memory indexing are outside these captured-turn joins; memory suspension cancels current tasks but successful terminal - saves can schedule indexing again. Background-command close requests cancellation without proving remote settlement. + Shared SSH configuration and connection openings use independently cancellable waiters; their underlying task is cancelled + only after the last waiter leaves, while explicit Host retirement cancels the whole opening. Existing manual compaction and + title generation remain outside the captured-turn joins. Memory suspension is a persistent admission fence: it invalidates + current sessions and tasks, ignores history notifications while suspended, then reloads state and backfills history on resume. + Background-command close requests cancellation without proving remote settlement. See [Platform Runtime](/openwiki/architecture/platform-runtime.md) and `AppTerminationTests` for scope and verification. - Connection/authentication/forwarding state is memory-only, excluded from domain records, sync and UserDefaults. Serial is local/temporary, never persisted or an Agent host. Vault-synced Workspace templates contain named @@ -482,7 +486,9 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r HTTP 200/Content-Length alone proves neither success nor persistence. - Shared metadata/expiry policy, separate directional state/claims/errors/ordering. Four active slots, each direction at most 64 unfinished and 256 terminal records for thirty minutes. Live work blocks idle expiry; completion remains queryable. Disconnect/recoverable SSH - failure pauses; explicit revocation cancels. Resume needs original active session/unexpired capability; exit loses both. Generation IDs + failure pauses; upload disconnect synchronously cancels that attempt's thread-safe transfer token before MainActor bookkeeping, while a + separate per-attempt disconnect signal preserves 410 mapping and resumability without misclassifying ordinary SFTP cleanup failures. + Explicit revocation cancels. Resume needs original active session/unexpired capability; exit loses both. Generation IDs reject old callbacks. Host/session retirement, stop/token rotation/direction disable revoke; release capacity after cleanup. Audits never include bytes/tokens. Upload expiry may clean partials; download allows valid Range after success. diff --git a/openwiki/architecture/mobile-ui.md b/openwiki/architecture/mobile-ui.md index 5e974b23..10561434 100644 --- a/openwiki/architecture/mobile-ui.md +++ b/openwiki/architecture/mobile-ui.md @@ -4,6 +4,8 @@ title: TFI Mobile UI Architecture description: "How the iPhone/iPad app composes registered feature stacks, scene-local navigation, shared runtimes, adaptive content regions, native terminal/transcript surfaces and mobile interaction policies." tags: [ios, ipados, tfi, ui, navigation, adaptive-layout, swiftui, uikit] sources: + - id: openwiki-source-7397a04f40a393c81b0ca52d + resource: repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift - id: openwiki-source-906678adb91c2fce2d3fcaed resource: repo://TFI/App/MobileAppModel.swift - id: openwiki-source-60d390fe6fbfea55a2a8230a @@ -42,14 +44,24 @@ sources: resource: repo://TFITests/MobileAdaptiveLayoutTests.swift - id: openwiki-source-b822b041afbf829d09a8555a resource: repo://TFITests/MobileAppTests.swift + - id: openwiki-source-a154b5d36ec59496392d45e4 + resource: repo://TFITests/MobileAssistantTests.swift - id: openwiki-source-16ec96bd78162e7e62effd0e resource: repo://TFITests/MobileConnectionPickerTests.swift + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-11c91698354a4f95be3c4654 resource: repo://TFITests/MobileWorkspaceMenuTests.swift -generated: { by: "codex", at: "2026-09-23T00:59:31.979Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T10:06:09.363Z --- # TFI Mobile UI Architecture @@ -221,7 +233,7 @@ The application runtime owns execution, not `onDisappear` on the conversation vi `MobileRootView` reports its scene phase; `MobileAppModel` aggregates all scenes. Entering background handling occurs when **all tracked scenes** are backgrounded, not whenever one feature disappears or one scene resigns activity. -At that boundary the app pauses edit monitoring, memory/sync/Metrics/outbox work and recording, then starts its background allowance. Expiration suspends workspaces, stops forwarding, pauses transfers and expires Assistant background execution. An active scene ends the allowance and asks retained workspaces to resume, rechecks edited files, and restarts the appropriate services. This is best-effort mobile continuity, not unlimited background network execution. +At that boundary the app pauses edit monitoring, memory/sync/Metrics/outbox work and recording, then starts its background allowance. Memory suspension is an admission fence rather than a one-shot cancellation: existing memory sessions are invalidated, new indexing/review/edit access is refused, and resume reloads committed state before backfilling history. Expiration suspends workspaces, stops forwarding, pauses transfers and expires Assistant background execution. While the Assistant suspension drain is active, send, edit, plan implementation and manual compaction remain blocked even if a scene becomes active. An active scene ends the platform allowance and asks retained workspaces to resume, rechecks edited files, and restarts the appropriate services, but it does not bypass that Assistant drain. This is best-effort mobile continuity, not unlimited background network execution. Closing a workspace, navigating back from its detail, switching tabs, and exhausting background allowance are therefore four different actions with different owners. A UI refactor must preserve that distinction. @@ -238,4 +250,6 @@ Implemented principles: `MobileAdaptiveLayoutTests` exercises native tab identity across compact/regular widths, all five destinations, Dynamic Type and English/Chinese variants. It also checks that region changes retain the Metrics list and scroll position, and preserve Assistant conversation identity, native transcript, composer focus and marked Chinese text. These tests validate injected geometry and navigation behavior; platform availability and real-device presentation still need appropriate device verification. +Four suites that create or replace a process-global UIKit key window are excluded from the default parallel run. Run `MobileEditorToolbarTests`, `MobileForwardingRenderingTests`, `MobileHostIntegrationRenderingTests` and `MobileSavedNameRenderingTests` explicitly with `TERMIND_MOBILE_UI_TESTS=1` and parallel testing disabled; their shared presentation surface makes concurrent execution nondeterministic even though each suite is serialized internally. + For a change, inspect scene routing tests for navigation, region/layout tests for geometry, workspace/terminal tests for session continuity, and Assistant tests for run-versus-selection ownership. See [Platform Runtime Composition](/openwiki/architecture/platform-runtime.md) and [Build, Test, and Release Operations](/openwiki/operations/build-test-and-release.md) before changing the lifetime boundary. diff --git a/openwiki/architecture/module-map.md b/openwiki/architecture/module-map.md index 8e1afee5..a20f397a 100644 --- a/openwiki/architecture/module-map.md +++ b/openwiki/architecture/module-map.md @@ -49,7 +49,7 @@ sources: generated: { by: "codex", at: "2026-09-22T09:17:11.491Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Module and Directory Architecture Map diff --git a/openwiki/architecture/native-app-directories.md b/openwiki/architecture/native-app-directories.md index 8b26dde1..e1bb5b8a 100644 --- a/openwiki/architecture/native-app-directories.md +++ b/openwiki/architecture/native-app-directories.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Native App Directory Responsibilities diff --git a/openwiki/architecture/platform-runtime.md b/openwiki/architecture/platform-runtime.md index 8eada4e8..92bc9fa2 100644 --- a/openwiki/architecture/platform-runtime.md +++ b/openwiki/architecture/platform-runtime.md @@ -10,14 +10,14 @@ sources: resource: repo://Frameworks/TermindApplication/Agent/AssistantLibraryModel.swift - id: openwiki-source-1c48f72c0c7a3164d7d5c715 resource: repo://Frameworks/TermindApplication/Agent/ConversationModel.swift - - id: openwiki-source-2d98a2885dc21f508f49b749 - resource: repo://Frameworks/TermindApplication/Agent/MemoryIndexedAgentRepository.swift - id: openwiki-source-7397a04f40a393c81b0ca52d resource: repo://Frameworks/TermindApplication/FeatureModels/MemoryModel.swift - id: openwiki-source-ee4e94c8b189e0881ec4fd15 resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentBackgroundCommands.swift - id: openwiki-source-0c82bf45bc197f879a54fd3a resource: repo://Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift + - id: openwiki-source-256e2b134a8446ea63b15585 + resource: repo://FrameworkTests/HermesMemoryTests.swift - id: openwiki-source-cd0be716a2446029f345525a resource: repo://Termind.xcodeproj/project.pbxproj - id: openwiki-source-dc9295278cc250ed20567540 @@ -66,10 +66,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-531f6567ecf96fd4b0c5e97a resource: repo://TFMTests/ExternalConnectionURLTests.swift -generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Platform Runtime, App Composition, and Lifetimes @@ -151,7 +151,7 @@ The returned cleanup task separately joins the controller's captured preparation The app publishes that one cleanup task before suspending memory/edit monitoring and stopping metrics, speech, window-owned workspaces, SFTP work, external authentication, forwarding and the outbox. Repeated calls return the same task. `AppTerminationReplyCoordinator` independently races cleanup completion against a three-second fallback and replies to AppKit once. The fallback can permit exit while persistence or resource cleanup is still pending; returning from a save attempt does not establish that it succeeded. -This is a captured-turn shutdown boundary, not a join of every Assistant task. Already-running manual compaction, title generation and memory indexing are outside it. `memory.suspend()` cancels current work, but a successful terminal save through `MemoryIndexedAgentRepository` can schedule indexing again. Shared unstructured SSH authentication and pool-opening tasks may not inherit runtime cancellation, leaving a consumer drain blocked and later resource closes unreached before fallback. Background-command close itself requests cancellation rather than proving remote settlement. These limitations make the native fallback necessary; the iOS lifecycle remains separate. +This is a captured-turn shutdown boundary, not a join of every Assistant task. Already-running manual compaction and title generation are outside it. `memory.suspend()` now establishes a persistent admission fence: it invalidates current access, ignores later history notifications, and reloads state plus backfills history only after resume. Shared SSH configuration and pool-opening work may still outlive one cancelled caller when other waiters remain; each waiter cancels independently, the final waiter cancels the underlying task, and explicit Host retirement cancels the whole opening. Background-command close itself requests cancellation rather than proving remote settlement. These limitations make the native fallback necessary; the iOS lifecycle remains separate. ## Mobile composition @@ -199,9 +199,9 @@ Scene phases are aggregated by scene ID. The app enters global background behavi `MobileBackgroundController` keeps one identity-bearing lease around the UIKit identifier. The expiration handler runs synchronously on the MainActor: it detaches the matching lease, invokes the app's cancellation callback, and ends that lease's valid identifier before returning. Finishing or replacing a lease makes its old callbacks inert. A denied acquisition immediately invokes expiration without ending an invalid identifier. If expiration reenters `begin`, the old lease cannot end or overwrite its replacement; if the platform calls expiration before returning an identifier, the outer acquisition retires the returned old identifier afterward. Each valid identifier is ended at most once. -The callback requests suspension; it does not await network teardown or persistence. Workspaces detach their sessions and start asynchronous disconnects. Assistant synchronously requests cancellation for every conversation before joining retained consumers, then closes background commands and connection pools. Those drains remain best effort under iOS suspension or termination, so an ended platform lease is not evidence that a save or remote cleanup completed. The Assistant composer-send guard remains set until its cleanup task returns, even if a scene becomes active meanwhile. +The callback requests suspension; it does not await network teardown or persistence. Workspaces detach their sessions and start asynchronous disconnects. Assistant synchronously requests cancellation for every conversation before joining retained consumers, then closes background commands and connection pools. Those drains remain best effort under iOS suspension or termination, so an ended platform lease is not evidence that a save or remote cleanup completed. The Assistant admission fence remains set until its cleanup task returns—even if a scene becomes active meanwhile—and blocks send, edit, plan implementation and manual compaction rather than merely disabling the composer. -When any scene becomes active, mobile refreshes its current device-directory record, reenables edit monitoring, finishes the background task, asks existing workspaces to resume, reconciles sync according to foreground entitlement, restarts the outbox and metrics, refreshes entitlement asynchronously, and updates activities. Returning before expiration therefore preserves healthy connections; after expiration, workspace recovery follows the workspace's reconnection journey. Expired forwarding and pending interactive authentication are stopped rather than silently restarted on foreground. +When any scene becomes active, mobile refreshes its current device-directory record, reenables edit monitoring, resumes memory (which reloads committed state and schedules history backfill), finishes the background task, asks existing workspaces to resume, reconciles sync according to foreground entitlement, restarts the outbox and metrics, refreshes entitlement asynchronously, and updates activities. Returning before expiration therefore preserves healthy connections; after expiration, workspace recovery follows the workspace's reconnection journey. Expired forwarding and pending interactive authentication are stopped rather than silently restarted on foreground, and an unfinished Assistant suspension drain keeps its own admission fence until cleanup completes. ## Ownership invariants and safe extension points diff --git a/openwiki/architecture/supporting-system-directories.md b/openwiki/architecture/supporting-system-directories.md index 9ba12ccf..6fe08092 100644 --- a/openwiki/architecture/supporting-system-directories.md +++ b/openwiki/architecture/supporting-system-directories.md @@ -54,6 +54,14 @@ sources: resource: repo://TermindMCPBridge/main.swift - id: openwiki-source-637644793f7e37cc057eb973 resource: repo://TFI/Platform/MobileLiveActivityController.swift + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-d1ffd558b3360478aa85b5af resource: repo://TFIWidgets/WorkspaceLiveActivity.swift - id: openwiki-source-76b1cdca4a02038756187f92 @@ -64,12 +72,14 @@ sources: resource: repo://TFM/Infrastructure/Networking/LoopbackHTTPServer.swift - id: openwiki-source-d3d065330c730839f5cc6112 resource: repo://TFMTests/ArchitectureBoundaryTests.swift + - id: openwiki-source-0dd012e3740bcc348fc36ccb + resource: repo://TFMTests/StoreKitTrialTests.swift - id: openwiki-source-0f8d0f9d08d6674698dd054c resource: repo://WorkspaceActivitySupport/WorkspaceActivityAttributes.swift -generated: { by: "omp", at: "2026-09-22T03:45:03.442Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:11:02.802Z + at: 2026-09-23T10:06:09.363Z --- # Supporting Products, Packages, and Tooling @@ -159,8 +169,8 @@ Here solid arrows are updates/navigation and dashed arrows are shared type contr | --- | --- | | `FrameworkTests` | Flat shared-layer test suites for values/use cases, persistence/migration, sync, shared presentation, and other framework contracts. | | `FrameworkTests/Fixtures` | Synthetic original-module V1/V14 SwiftData stores. They preserve cross-module migration evidence and must not be regenerated from the extracted models. | -| `TFMTests` | Flat desktop and integration suites: architecture boundaries, windows/navigation, terminal/remote flows, transfers, MCP, and rendering. Visible-window and screenshot cases have explicit opt-in gates. | -| `TFITests` | Flat mobile suites: app/scene ownership, workspace/session behavior, backgrounding, editor/Assistant behavior, adaptive layout, and screenshots. | +| `TFMTests` | Flat desktop and integration suites: architecture boundaries, windows/navigation, terminal/remote flows, transfers, MCP, and rendering. Visible-window and screenshot cases have explicit opt-in gates; manually authorized StoreKit tests use their own default-off gate. | +| `TFITests` | Flat mobile suites: app/scene ownership, workspace/session behavior, backgrounding, editor/Assistant behavior, adaptive layout, and screenshots. Suites that replace the process-global UIKit key window are opt-in and run with parallel testing disabled. | | `Benchmarks/PortForwarding` | Standalone forwarding benchmark executable over shared-framework SPI; not hosted by an app or test bundle. | | `Scripts` | Flat executable utilities grouped by function below. | | `Scripts/Tests` | Python regression tests for version bumping and forwarding-benchmark tooling. | diff --git a/openwiki/architecture/system-boundaries.md b/openwiki/architecture/system-boundaries.md index c8baf8e3..fa79516a 100644 --- a/openwiki/architecture/system-boundaries.md +++ b/openwiki/architecture/system-boundaries.md @@ -43,7 +43,7 @@ sources: generated: { by: "codex", at: "2026-09-23T00:14:42.075Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # System Boundaries and Dependency Ownership diff --git a/openwiki/concepts/agent-runtime-and-assistant.md b/openwiki/concepts/agent-runtime-and-assistant.md index 57e6481a..96bb95de 100644 --- a/openwiki/concepts/agent-runtime-and-assistant.md +++ b/openwiki/concepts/agent-runtime-and-assistant.md @@ -48,6 +48,8 @@ sources: resource: repo://FrameworkTests/AssistantConversationOperationsTests.swift - id: openwiki-source-6ec3fb4cd6a582cdf278bab9 resource: repo://FrameworkTests/AssistantExecutionControllerTests.swift + - id: openwiki-source-256e2b134a8446ea63b15585 + resource: repo://FrameworkTests/HermesMemoryTests.swift - id: openwiki-source-105d05d558f14996921a83f6 resource: repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved - id: openwiki-source-a62bfbbc7de13a894444718b @@ -90,10 +92,10 @@ sources: resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-8d265a8732f0af93c5d4e274 resource: repo://TFMTests/MCPServerToolsTests.swift -generated: { by: "codex", at: "2026-09-23T08:21:59.182Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Assistant, Agent Runtime, Skills, and Memory @@ -220,11 +222,11 @@ The app snapshots its `AssistantConversationStore`, separately requests controll MCP shutdown and SFTP cancellation proceed alongside those Assistant joins. Agent background commands are closed only after all these waits return, followed by the Agent connection pool. Repeated shutdown returns the same cleanup task. The native reply coordinator separately replies once when cleanup returns or its three-second fallback fires; termination can therefore happen before a blocked save or later resource closure completes. A joined save attempt remains distinct from save success. -Cancellation is cooperative across the remote stack. `AgentSSHAuthenticationContext.configurationAttempt()` and shared pool opening await unstructured tasks; cancelling the runtime does not necessarily cancel those tasks or settle their interaction continuations. Such a drain may remain blocked until exit, before background-command or pool closure is reached. `AgentBackgroundCommands.closeAll()` itself requests command-task cancellation without awaiting remote settlement. These joins also exclude already-running manual compaction, title generation and memory indexing. `memory.suspend()` cancels current work, but successful terminal saves call `historyChanged()` and can schedule indexing again. The shutdown contract therefore covers captured turns and retained consumers, with the native fallback bounding the quit reply. +Cancellation is cooperative across the remote stack. Shared SSH configuration and pool openings register one continuation per caller. Cancelling one waiter resumes only that caller with cancellation and keeps the underlying task alive for remaining waiters; the last waiter cancels the task, while explicit Host retirement cancels the whole opening. A shared drain can therefore remain blocked while another legitimate waiter still owns it. `AgentBackgroundCommands.closeAll()` itself requests command-task cancellation without awaiting remote settlement. These joins also exclude already-running manual compaction and title generation. Memory suspension now remains an admission fence: it invalidates current sessions/tasks and ignores later history notifications until resume reloads committed state and schedules backfill. The shutdown contract therefore covers captured turns and retained consumers, with the native fallback bounding the quit reply. ### Mobile background expiration -Mobile background expiration first sets `isSuspending`, cancels pending preparations and recording, snapshots the stored conversations, and calls `stop()` on every conversation synchronously. Only after all cancellation requests are issued does its cleanup task call `stopAndWait()` for each snapshot member, then close background commands and Agent connections. A first conversation's blocked save therefore cannot postpone cancellation of a later queued or active run. Repeated expiration during that cleanup is inert, and composer sends remain blocked until cleanup returns, including when a scene has already returned to the foreground. +Mobile background expiration first sets `isSuspending`, cancels pending preparations and recording, snapshots the stored conversations, and calls `stop()` on every conversation synchronously. Only after all cancellation requests are issued does its cleanup task call `stopAndWait()` for each snapshot member, then close background commands and Agent connections. A first conversation's blocked save therefore cannot postpone cancellation of a later queued or active run. Repeated expiration during that cleanup is inert. Send, prepared edit, plan implementation and manual compaction admissions all remain blocked until cleanup returns, including when a scene has already returned to the foreground. The finite UIKit lease has a separate boundary: its expiration callback requests this cleanup and ends the matching valid lease synchronously, without awaiting those drains. Cleanup may continue when execution is available, but iOS suspension or termination can prevent completion. Neither the lease nor Live Activities guarantee persistence or continued network work. See [Platform Runtime](/openwiki/architecture/platform-runtime.md) for scene aggregation, lease identity and workspace recovery. @@ -332,6 +334,8 @@ A memory session receives a frozen snapshot for its conversation. Memory reviews New submissions capture whether they have a memory-session identity before preparation awaits. Enabling memory afterward does not add a session to that captured turn. Disabling it remains a revocation boundary: `MemoryModel` invalidates existing contexts and rejects a late snapshot load from the old generation, so capture does not override a later disable. +Suspension is distinct from disabling. It keeps the user's enabled preference and database, but advances the generation, revokes active access, cancels indexing/reviews, refuses refresh/edit/session/search/index/review admission, and ignores terminal history notifications while suspended. Resume opens admission, reloads the store for the new generation, and schedules a full history replacement. If an old-generation write committed in storage while suspension invalidated its caller, the reload reconciles that committed state instead of publishing the stale return value. + ## Focused verification The focused tests establish behavior that is important when changing this area: diff --git a/openwiki/concepts/commerce-localization-and-product-surfaces.md b/openwiki/concepts/commerce-localization-and-product-surfaces.md index 205f245a..ec181024 100644 --- a/openwiki/concepts/commerce-localization-and-product-surfaces.md +++ b/openwiki/concepts/commerce-localization-and-product-surfaces.md @@ -5,7 +5,7 @@ description: Explains StoreKit-derived Pro access, native paywall and review own tags: [commerce, storekit, localization, product-surfaces, marketing, widgets] verified: - by: openwiki/0.5.2 - at: 2026-09-22T03:23:14.035Z + at: 2026-09-23T10:06:09.363Z sources: - id: openwiki-source-8037e2358a2c4f9b2c722a11 resource: repo://AGENTS.md @@ -55,7 +55,7 @@ sources: resource: repo://TFMTests/StoreKitTrialTests.swift - id: openwiki-source-0f8d0f9d08d6674698dd054c resource: repo://WorkspaceActivitySupport/WorkspaceActivityAttributes.swift -generated: { by: "openwiki/0.5.2", at: "2026-09-22T00:31:10.149Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } --- ## Scope and ownership @@ -124,7 +124,7 @@ The public site is a separate Astro app in `LandingPage/`, with strict TypeScrip ## Change and verification guide -- For an access change, update `ProFeature`/callers and `ProAccessController` together; test entitlement phases, verification failures, pending Ask to Buy, restore, and metadata failure separately. `StoreKitTrialTests` exercises the local `SKTestSession` configuration, while `CommerceConfigurationTests` catches drift between product identifiers and the fixture. +- For an access change, update `ProFeature`/callers and `ProAccessController` together; test entitlement phases, verification failures, pending Ask to Buy, restore, and metadata failure separately. `StoreKitTrialTests` exercises the local `SKTestSession` configuration, while `CommerceConfigurationTests` catches drift between product identifiers and the fixture. Because `SKTestSession` needs manual StoreKit authorization on the test host, that suite is skipped by default and runs only when `TERMIND_STOREKIT_TESTS=1` is explicitly supplied; routine unattended suite success does not mean it ran. - Treat local StoreKit testing as insufficient evidence for production: configure and verify App Store Connect metadata and use Sandbox/review environments for storefront prices, subscription lifecycle, restore, Family Sharing, and cross-device behavior. - For paywall or review work, test both platform presenters and the shared controller’s cancellation, persistence, cooldown, and multi-surface rules. Do not infer a rating or prompt display from an attempt record. - For text or product-copy changes, complete all three languages, run `Scripts/check-zh-localization.sh`, and run `AppLanguageTests` where applicable. diff --git a/openwiki/concepts/persistence-and-secrets.md b/openwiki/concepts/persistence-and-secrets.md index 9d52e45f..d23895e1 100644 --- a/openwiki/concepts/persistence-and-secrets.md +++ b/openwiki/concepts/persistence-and-secrets.md @@ -87,7 +87,7 @@ sources: generated: { by: "codex", at: "2026-09-23T06:59:53.554Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Persistent Data, Migrations, and Secret Boundaries diff --git a/openwiki/concepts/ssh-multiplexing-and-scheduling.md b/openwiki/concepts/ssh-multiplexing-and-scheduling.md index d9dd5498..99c5f16a 100644 --- a/openwiki/concepts/ssh-multiplexing-and-scheduling.md +++ b/openwiki/concepts/ssh-multiplexing-and-scheduling.md @@ -3,9 +3,6 @@ type: implementation guide title: SSH Multiplexing, Channel Admission, and I/O Scheduling description: "The concrete ownership and scheduling layers behind Termind SSH: per-connection session leases, serial libssh2 workers, exec state machines, multiplexed SFTP retries, readiness, backpressure, and feature-specific connection reuse." tags: [ssh, multiplexing, scheduling, libssh2, backpressure, sftp, concurrency] -verified: - - by: openwiki/0.5.2 - at: 2026-09-22T03:23:14.035Z sources: - id: openwiki-source-8e06414c0a0a26e45de192bd resource: repo://Frameworks/TermindDomain/Connections/SSHPreferences.swift @@ -29,11 +26,16 @@ sources: resource: repo://Frameworks/TermindRemoteInfrastructure/SSH/SSHPTYChannel.swift - id: openwiki-source-e63615bc111f4678ec12525d resource: repo://Frameworks/TermindRemoteInfrastructure/SSH/SSHStreamPump.swift + - id: openwiki-source-0260a25fb41739d94e23d2ea + resource: repo://TFMTests/AgentConnectionPoolTests.swift - id: openwiki-source-7364a5d16e49e89eb03bbee2 resource: repo://TFMTests/SSHForwardingPumpTests.swift - id: openwiki-source-ec3ec372dd69a7df3113c6b1 resource: repo://TFMTests/SSHMaxSessionsPolicyTests.swift -generated: { by: "omp", at: "2026-09-22T02:43:23.140Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } +verified: + - by: openwiki/0.5.2 + at: 2026-09-23T10:06:09.363Z --- # SSH Multiplexing, Channel Admission, and I/O Scheduling @@ -76,7 +78,7 @@ This is a conceptual layering diagram, not a claim that every owner supports eve Metrics deliberately opts out of concurrent operations: `SSHExclusiveConnectionAdapter.withConnection` uses a busy flag and FIFO continuations to serialize complete operations on its owned connection. That adapter has no topology/fallback policy. Similar-looking SSH usage does not authorize merging Metrics, terminal, Agent, and forwarding lifetimes. -The Agent has a separate higher-level reuse policy: `AgentConnectionPool` keys connections by Host ID plus route fingerprint, joins same-fingerprint in-flight opens, and opens primary SFTP on its pooled SSH connection. Reuse survives a follow-up Agent run; default idle expiry is 180 seconds, and active exec/SFTP leases prevent whole-host idle retirement. Missing/changed routes invalidate reuse. This is an Agent-owned pool, not the terminal or forwarding pool. +The Agent has a separate higher-level reuse policy: `AgentConnectionPool` keys connections by Host ID plus route fingerprint, joins same-fingerprint in-flight opens, and opens primary SFTP on its pooled SSH connection. Every joined caller has its own waiter identity. Cancelling one waiter resumes only that caller; the underlying open continues for remaining waiters, but is cancelled and removed when the last waiter leaves. Explicit Host disconnect cancels the whole opening and all waiters, and a late successful connection whose opening identity was retired is disconnected instead of entering the pool. Reuse survives a follow-up Agent run; default idle expiry is 180 seconds, and active exec/SFTP leases prevent whole-host idle retirement. Missing/changed routes invalidate reuse. This is an Agent-owned pool, not the terminal or forwarding pool. ## Session-channel budget: local FIFO, not server discovery @@ -159,6 +161,6 @@ The Agent's automatic replacement retry is deliberately narrow: `AgentConnection | Can a busy/slow flow starve or corrupt a peer? | `SSHForwardingPumpTests`: ready-queue rotation, watermarks, window updates, half-close; `SSHForwardingWireTests`: concurrent streams and slow consumer. | | Why did SFTP open another socket rather than a sibling? | File permit's selected mode and `SFTPTransferContext.capability`, not the exec ready queue. | | Why does closing the terminal not close SSH yet? | PTY `closeTerminalSession` and last-sibling callback; an SFTP subsystem can still own the transport. | -| Why was an Agent connection retained or rebuilt? | `AgentConnectionPool` route fingerprint, in-flight join, active leases and idle sweep; `AgentConnectionPoolTests`. | +| Why was an Agent connection retained or rebuilt? | `AgentConnectionPool` route fingerprint, per-caller in-flight waiters, active leases and idle sweep; `AgentConnectionPoolTests`. | Tests cited here are implementation evidence; they are not a performance guarantee for arbitrary servers/routes. Keep the pinned dependency graph when validating changes, and do not turn the [historical forwarding benchmarks](/openwiki/operations/port-forwarding-benchmarks.md) into general WAN latency claims. diff --git a/openwiki/concepts/ui-design-principles.md b/openwiki/concepts/ui-design-principles.md index 5c8b976f..45f26f9c 100644 --- a/openwiki/concepts/ui-design-principles.md +++ b/openwiki/concepts/ui-design-principles.md @@ -50,6 +50,14 @@ sources: resource: repo://TFI/UI/Managed/MobileCards.swift - id: openwiki-source-fffc181d864b6caea779a6f5 resource: repo://TFITests/MobileAdaptiveLayoutTests.swift + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-8194e1eb3652c7ad61d75cfa resource: repo://TFITests/MobileTerminalFontTests.swift - id: openwiki-source-873894299539cce9a737a860 @@ -88,10 +96,10 @@ sources: resource: repo://TFMTests/TerminalFontRenderingTests.swift - id: openwiki-source-9442022b3dba1be62a55f0f0 resource: repo://TFMTests/TerminalThemeTests.swift -generated: { by: "codex", at: "2026-09-23T05:17:08.701Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T10:06:09.363Z --- # UI Design Principles and Shared Presentation Boundaries @@ -242,6 +250,8 @@ The implementation already treats accessibility as behavior, not a final label p Localization likewise affects measurement and identity-sensitive presentation. The existing adaptive/rendering cases exercise English, Simplified Chinese and Traditional Chinese, including narrow desktop editors and the mobile tab set. A layout that works only with the shortest English label is not proven by a source-level logic test. +Mobile tests that must install a real UIKit key window are a different seam from injected geometry tests. The editor-toolbar, forwarding, host-integration and saved-name rendering suites share process-global presentation state, so they are disabled in the default parallel run and must be invoked with `TERMIND_MOBILE_UI_TESTS=1` plus parallel testing disabled. Serialization inside each suite does not make separate key-window suites safe to overlap. + Do not claim all animation sites are already Reduce Motion-aware: the helpers make that policy available, but each changed call site still needs inspection. Likewise, an accessibility label does not prove that focus order, clipping, menu reachability or large-text layout is correct. ## 8. Continuity has a cost budget diff --git a/openwiki/integrations/mcp-server-and-bridge.md b/openwiki/integrations/mcp-server-and-bridge.md index 8712f92b..01fac7e9 100644 --- a/openwiki/integrations/mcp-server-and-bridge.md +++ b/openwiki/integrations/mcp-server-and-bridge.md @@ -16,6 +16,8 @@ sources: resource: repo://TFM/App/MCPServerController.swift - id: openwiki-source-76b1cdca4a02038756187f92 resource: repo://TFM/Application/MCPServer/MCPServerTools.swift + - id: openwiki-source-396f97a332870d48c065d7e9 + resource: repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift - id: openwiki-source-d485ebb339c77875a3b2701e resource: repo://TFM/Features/ExternalRequests/ExternalAuthCoordinator.swift - id: openwiki-source-ef4c6426a19087a6c49e4841 @@ -42,10 +44,10 @@ sources: resource: repo://TFMTests/MCPTransferTicketTests.swift - id: openwiki-source-47dc468698b2904f0004ee04 resource: repo://TFMTests/MCPUploadTests.swift -generated: { by: "codex", at: "2026-09-23T01:34:03.251Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T07:36:39.730Z + at: 2026-09-23T10:06:09.363Z --- # MCP Server, Transfer APIs, and Stdio Bridge @@ -145,6 +147,8 @@ The upload ticket binds the expected byte count, SHA-256 digest, target path, an Interrupted uploads can resume only from the coordinator’s acknowledged remote offset. The client must provide matching `Upload-Offset` and suffix `Content-Length`; the remote prefix is verified before continuation. Digest mismatch, truncation, cancellation, or cleanup failure does not publish a successful remote file. The status may report a possible temporary remote path if cleanup fails. Uploads never overwrite directories; `overwrite` is only meaningful for an existing regular file. +HTTP disconnect is signaled outside the MainActor, so its callback synchronously cancels the attempt's thread-safe transfer token before scheduling coordinator bookkeeping. A second per-attempt token records that the cancellation came from this HTTP connection: failure mapping can return `410 upload_cancelled` and preserve a resumable paused ticket without interpreting every internal SFTP failure—which also cancels the transfer token—as a disconnect. Attempt IDs keep a late callback from an old connection from cancelling a resumed upload, and a disconnect after remote commit cannot erase success. + ### Download: `sftp_download` then `GET /downloads/` Creating a download ticket performs no remote I/O. On GET, the coordinator accepts regular files only, opens a dedicated SFTP stream, fixes the length, and returns binary bytes without local staging. It does not support `HEAD`, body/query credentials, or arbitrary ranges. A client may use one `Range: bytes=N-` continuation from its saved length; invalid range returns `416` without consuming a waiting ticket, and a resume verifies the existing prefix and source metadata before headers. diff --git a/openwiki/operations/build-test-and-release.md b/openwiki/operations/build-test-and-release.md index d646a2e0..c9d9a51a 100644 --- a/openwiki/operations/build-test-and-release.md +++ b/openwiki/operations/build-test-and-release.md @@ -5,7 +5,7 @@ description: Safe, targeted validation and release procedures for Termind’s Xc tags: [operations, build, testing, localization, release, agentkit, screenshots] verified: - by: openwiki/0.5.2 - at: 2026-09-22T03:23:14.035Z + at: 2026-09-23T10:06:09.363Z sources: - id: openwiki-source-ea70eb6c045047448e446296 resource: repo://.gitignore @@ -27,11 +27,21 @@ sources: resource: repo://Termind.xcodeproj/project.pbxproj - id: openwiki-source-105d05d558f14996921a83f6 resource: repo://Termind.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-793d7861000adc19f0c3df01 resource: repo://TFMTests/AppLanguageTests.swift + - id: openwiki-source-0dd012e3740bcc348fc36ccb + resource: repo://TFMTests/StoreKitTrialTests.swift - id: openwiki-source-a08489338be00125d646c77e resource: repo://TFMTests/VisibleWindowTestTrait.swift -generated: { by: "codex", at: "2026-09-22T01:13:21.371Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } --- # Build, Validation, Localization, and Dependency Release Operations @@ -103,6 +113,24 @@ TEST_RUNNER_TERMIND_WINDOW_TESTS=1 xcodebuild -project Termind.xcodeproj -scheme In Xcode, set `TERMIND_WINDOW_TESTS=1` in the Test action environment. Existing screenshot tests may retain their own dedicated opt-in variables; offscreen layout and logic tests remain part of normal validation. This separation prevents a routine test run from activating the app, stealing focus, or becoming display-dependent. +StoreKit integration tests have a separate default-off gate because the test host requires manual StoreKit authorization. Run only the focused suite after authorizing the host: + +```sh +TERMIND_STOREKIT_TESTS=1 xcodebuild -project Termind.xcodeproj -scheme TFM -destination 'platform=macOS' -onlyUsePackageVersionsFromResolvedFile test -only-testing:TFMTests/StoreKitTrialTests +``` + +On mobile, four UIKit integration suites replace the process-global key window. They remain skipped in the normal parallel suite and must run together with their opt-in enabled and Xcode parallel testing disabled: + +```sh +TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1 xcodebuild -project Termind.xcodeproj -scheme TFI -destination '' -onlyUsePackageVersionsFromResolvedFile -parallel-testing-enabled NO test \ + -only-testing:TFITests/MobileEditorToolbarTests \ + -only-testing:TFITests/MobileForwardingRenderingTests \ + -only-testing:TFITests/MobileHostIntegrationRenderingTests \ + -only-testing:TFITests/MobileSavedNameRenderingTests +``` + +Default-suite success does not imply either manually gated group ran. + For an isolated interactive desktop demo, use a supported scene and one of the three supported languages: ```sh diff --git a/openwiki/operations/storefront-commerce-release.md b/openwiki/operations/storefront-commerce-release.md index 91e24790..6502e030 100644 --- a/openwiki/operations/storefront-commerce-release.md +++ b/openwiki/operations/storefront-commerce-release.md @@ -5,8 +5,10 @@ description: Product metadata, entitlement expectations, App Store Connect owner tags: [app-store, storekit, commerce, subscriptions, release] verified: - by: openwiki/0.5.2 - at: 2026-09-22T03:23:14.035Z + at: 2026-09-23T10:06:09.363Z sources: + - id: openwiki-source-8037e2358a2c4f9b2c722a11 + resource: repo://AGENTS.md - id: openwiki-source-b37c974aaea12bd08606d67d resource: repo://Frameworks/TermindDomain/Commerce/ProAccessTypes.swift - id: openwiki-source-3aa42bdd0f8eaf866f06599f @@ -17,7 +19,7 @@ sources: resource: repo://TFMTests/ProAccessTests.swift - id: openwiki-source-0dd012e3740bcc348fc36ccb resource: repo://TFMTests/StoreKitTrialTests.swift -generated: { by: "codex", at: "2026-09-22T01:13:21.371Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } --- # App Store Commerce Configuration and Release Verification @@ -52,7 +54,7 @@ Keep runtime access behavior in the shared commerce controller and domain types. ## Release verification -1. Run `StoreKitTrialTests` and the relevant `SKTestSession`/Pro access suites. Verify purchase, introductory trial, renewal, cancellation, restore, expiration, billing retry, paid-to-paid grace, refund, revocation, Ask to Buy, deferred completion, and transaction updates. +1. After manually authorizing StoreKit for the test host, run `StoreKitTrialTests` explicitly with `TERMIND_STOREKIT_TESTS=1`, preferably using `-only-testing:TFMTests/StoreKitTrialTests`, plus the relevant Pro access suites. The StoreKit suite is skipped by default, so routine suite success is not evidence that it ran. Verify purchase, introductory trial, renewal, cancellation, restore, expiration, billing retry, paid-to-paid grace, refund, revocation, Ask to Buy, deferred completion, and transaction updates. 2. With a new Sandbox account, verify the localized annual and lifetime product names, descriptions, prices, annual period, introductory eligibility/duration, and Family Sharing. Existing accounts may already have consumed introductory eligibility. 3. Verify the same Apple-account entitlement on Mac, iPhone, and iPad, then verify Family Sharing separately. Confirm lifetime precedence and cancellation-through-expiry behavior. 4. Apply all English, Simplified Chinese, and Traditional Chinese metadata changes in App Store Connect. Coordinate storefront text with website and free-Workspace messaging while preserving the product IDs, prices, subscription group, offer, and grace policy. diff --git a/openwiki/quickstart.md b/openwiki/quickstart.md index 00701ef8..4c9c4428 100644 --- a/openwiki/quickstart.md +++ b/openwiki/quickstart.md @@ -16,6 +16,14 @@ sources: resource: repo://TFI/App/MobileRootView.swift - id: openwiki-source-6c9f797fc6a1e7ce639b72b8 resource: repo://TFI/App/TermindIOSApp.swift + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-6570374eaac2e0278fa84640 resource: repo://TFM/App/TermindApp.swift - id: openwiki-source-22b9fca540c3029c5ace451e @@ -24,10 +32,10 @@ sources: resource: repo://TFMTests/ArchitectureBoundaryTests.swift - id: openwiki-source-0dd012e3740bcc348fc36ccb resource: repo://TFMTests/StoreKitTrialTests.swift -generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T08:21:59.182Z + at: 2026-09-23T10:06:09.363Z --- # Termind Engineering Quickstart @@ -103,6 +111,8 @@ Use an installed iPhone or iPad simulator destination for `TFI` tests. Keep visi StoreKit tests also remain skipped by default because they require manual authorization. When explicitly running them, set `TERMIND_STOREKIT_TESTS=1` and prefer `-only-testing:TFMTests/StoreKitTrialTests`. Default-suite success does not imply that opt-in suite ran. +Four mobile UIKit suites that replace the process-global key window are also default-off: `MobileEditorToolbarTests`, `MobileForwardingRenderingTests`, `MobileHostIntegrationRenderingTests`, and `MobileSavedNameRenderingTests`. Run them with `TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1`, narrow `-only-testing` selections, and `-parallel-testing-enabled NO`; suite-level serialization does not coordinate key-window ownership across separate suites. + ## Change checklist - Identify the domain and owner in the routing map; read the detailed page before implementing. diff --git a/openwiki/testing/behavioral-boundaries-and-fixtures.md b/openwiki/testing/behavioral-boundaries-and-fixtures.md index a558389b..168088fd 100644 --- a/openwiki/testing/behavioral-boundaries-and-fixtures.md +++ b/openwiki/testing/behavioral-boundaries-and-fixtures.md @@ -24,6 +24,8 @@ sources: resource: repo://FrameworkTests/CloudSyncSafetyPersistenceTests.swift - id: openwiki-source-eb9518e5c7eb3782fa0cccc0 resource: repo://FrameworkTests/CloudSyncSafetyTests.swift + - id: openwiki-source-256e2b134a8446ea63b15585 + resource: repo://FrameworkTests/HermesMemoryTests.swift - id: openwiki-source-8428b9f5de49b8a561d93fa5 resource: repo://FrameworkTests/PersistenceCompatibilityTests.swift - id: openwiki-source-db40aa993661b07f1a9aff6d @@ -38,8 +40,18 @@ sources: resource: repo://TFITests/MobileCardRenderingTests.swift - id: openwiki-source-16ec96bd78162e7e62effd0e resource: repo://TFITests/MobileConnectionPickerTests.swift + - id: openwiki-source-7341d7b266d976d7aac5d161 + resource: repo://TFITests/MobileEditorToolbarTests.swift + - id: openwiki-source-f4b87eae375fa9241ff8cb35 + resource: repo://TFITests/MobileForwardingRenderingTests.swift + - id: openwiki-source-96b0d4f1528e0e0e4e7b6a12 + resource: repo://TFITests/MobileHostIntegrationRenderingTests.swift + - id: openwiki-source-1068a19b8ad42f734f4b4bb6 + resource: repo://TFITests/MobileSavedNameRenderingTests.swift - id: openwiki-source-9b78a7e39bfa08bea5192722 resource: repo://TFITests/MobileWorkspaceStoreTests.swift + - id: openwiki-source-396f97a332870d48c065d7e9 + resource: repo://TFM/Application/MCPServer/MCPUploadCoordinator.swift - id: openwiki-source-7e364c6182a8339831ecab24 resource: repo://TFM/Features/Workspaces/WorkspaceNavigatorView.swift - id: openwiki-source-cfbc34def649c94f760c5695 @@ -52,6 +64,8 @@ sources: resource: repo://TFMTests/ChatComposerStateTests.swift - id: openwiki-source-c15cd5bef4646aaf64df407e resource: repo://TFMTests/ConnectionHistoryTests.swift + - id: openwiki-source-47dc468698b2904f0004ee04 + resource: repo://TFMTests/MCPUploadTests.swift - id: openwiki-source-53e2b2fd2e661123f63e69e1 resource: repo://TFMTests/SFTPTransferContextTests.swift - id: openwiki-source-0dd012e3740bcc348fc36ccb @@ -62,10 +76,10 @@ sources: resource: repo://TFMTests/VisibleWindowTestTrait.swift - id: openwiki-source-05f0aeba12df8388b97b3c76 resource: repo://TFMTests/WorkspaceNavigatorRenameTests.swift -generated: { by: "codex", at: "2026-09-23T06:54:45.592Z" } +generated: { by: "codex", at: "2026-09-23T10:06:09.363Z" } verified: - by: openwiki/0.5.2 - at: 2026-09-23T06:54:45.592Z + at: 2026-09-23T10:06:09.363Z --- # Testing Strategy and Behavioral Boundaries @@ -152,6 +166,10 @@ SFTP transfer tests use a controlled `TransferChannelHarness`, a `TransferChanne Those tests are intentionally actor-driven and use polling only to wait for observable harness state. Prefer this style for races: hold the transfer at a known point, make the ownership transition, then release it and assert the terminal state and disconnect count. +MCP upload cancellation uses the same principle. `MCPUploadTests` waits until the fake destination has actually entered the transfer, fires each cancellation owner—including HTTP disconnect—and then requires a `410`, no remote commit, released activity and failed audit. The disconnect case additionally requires a paused ticket. This catches the ordering gap where a socket callback could enqueue MainActor bookkeeping after the transfer had already mapped an ordinary failure to `502`. + +Hermes lifecycle tests avoid wall-clock polling for the index replacement itself. The actor-backed store resumes continuations when the first or second history replacement arrives, then the test yields only for the MainActor indexing flag to settle. Separate barriers hold an old-generation memory write or review at the commit/model boundary, suspend and resume the feature, and verify stale publication is rejected while committed storage is reloaded. Under a highly parallel full suite, event ownership is more deterministic than increasing a timeout. + ### Mobile: app state, scenes, and device-local restoration `TFITests` owns `MobileAppModel` composition and mobile scene behavior. Mobile app tests build `MobileAppModel(inMemory: true)` or supply dependency seams, which permits assertions over the actual shared schema and repositories without using the user's database or Keychain. Representative tests verify that scenes share durable conversations while navigation, selected conversation, and composer drafts stay scene-local; shared SwiftData history is observable from fresh repositories over the same container. @@ -184,6 +202,8 @@ In Xcode, set `TERMIND_WINDOW_TESTS=1` in the Test action environment. Existing StoreKit tests use a separate opt-in because they require manual authorization. `StoreKitTrialTests` is serialized and skipped unless `TERMIND_STOREKIT_TESTS=1`; when explicitly authorized, prefer the narrow `-only-testing:TFMTests/StoreKitTrialTests` selection. Ordinary default-suite success therefore does not imply this StoreKit suite ran. +Mobile UIKit tests that create a replacement key window also use a separate gate. `MobileEditorToolbarTests`, `MobileForwardingRenderingTests`, `MobileHostIntegrationRenderingTests` and `MobileSavedNameRenderingTests` are serialized and skipped unless `TERMIND_MOBILE_UI_TESTS=1`. Run the four suites with Xcode parallel testing disabled because suite-level serialization does not coordinate process-global key-window state across separate suites. + ## Change-to-regression matrix | Change type | Primary regression seam | Required behavior to protect | @@ -196,7 +216,10 @@ StoreKit tests use a separate opt-in because they require manual authorization. | SSH credentials, prompts, host trust, or agent authentication | Authentication and interaction-policy fixtures | Ephemeral secrets stay ephemeral, prompt scope/order/coalescing is correct, forbidden work stays noninteractive, and secrets are invalidated after use. | | SSH/SFTP transfer scheduling or pane/session teardown | `SFTPTransferContextTests` with a controlled channel harness | Concurrency bounds hold, background transfer ownership survives UI teardown, cancellation cleans up, and fallback does not invite interaction. | | Mobile workspace, scene, restoration, or background lifecycle | `MobileAppTests` and `MobileWorkspaceStoreTests` | State is shared or local at the intended scope, external sessions remain ephemeral, restoration is safe, and callbacks cannot revive retired state. | +| Hermes suspension or resume reconciliation | `HermesMemoryTests` with actor continuations and controlled write/review barriers | Suspended work cannot publish, history waits until resume, and committed stale-generation writes are reloaded. | +| MCP upload disconnect/cancellation | `MCPUploadTests` with a blocked source and started destination | Every owner unblocks both sides without commit; disconnect maps to 410 and leaves a resumable paused ticket without changing ordinary failures. | | macOS rendering or focus behavior | Focused offscreen test plus `.requiresVisibleWindow` suite when needed | Default tests stay headless; opted-in tests prove the user-visible interaction. | +| Mobile process-global key-window rendering | Four TERMIND_MOBILE_UI_TESTS suites with Xcode parallel testing disabled | Native navigation, toolbars and localized rendering run without cross-suite key-window interference. | ## Execution strategy