sneakoscope 8.2.0 → 8.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex-app/menubar/constants.js +1 -0
- package/dist/core/release/canonical-test-proof.js +0 -1
- package/dist/core/release/gate-manifest.js +4 -0
- package/dist/core/release/release-gate-affected-selector.js +2 -0
- package/dist/core/release/release-gate-contract.js +2 -0
- package/dist/core/telegram/doctor.js +7 -2
- package/dist/core/version-manager.js +4 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/RemoteCodingSettingsControls.swift +160 -0
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +76 -77
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +1 -0
- package/dist/scripts/install-surface-ssot-check.js +2 -2
- package/dist/scripts/release-metadata-check.js +3 -0
- package/dist/scripts/release-version-truth-check.js +149 -0
- package/package.json +2 -1
- package/release-gates.v2.json +39 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Proof-first orchestration for Codex CLI, ChatGPT Desktop, AI coding agents, mult
|
|
|
22
22
|
Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
|
|
23
23
|
<!-- END SKS SEARCH VISIBILITY MARKETING -->
|
|
24
24
|
|
|
25
|
-
This README documents package **SKS 8.2.
|
|
25
|
+
This README documents package **SKS 8.2.1** — its own identity, read from `package.json` and subject to release-gate verification, not advice about what to install.
|
|
26
26
|
|
|
27
27
|
Use the official latest stable SKS and Codex CLI releases. The Codex compatibility SSOT is always the **current latest stable** host; capability probes measure what that host can actually do. Product docs do not crown a fixed `0.x.y` string as SSOT (release pins and schema directories are measured artifacts for the current package, not a permanent product version claim). Menu Bar / Center induce updates to the latest stable build. Run `sks update-check` for what is installed and read the capability report for what is supported. Install SSOT is npm `sneakoscope@latest`; PATH `sks` and Menu Bar stamped generation must match that version or gates fail. It resolves managed SKS skills from the authoritative global install, preserves a runnable Naruto child slot when `max_threads=2`, and keeps Menu Bar repair transactional so stamped generations remain verifiable. Naruto uses stable opt-in multi-agent V2 when the host exposes it (Codex official multi-agent wrap-only; SKS does not reimplement a parallel runtime). Local code search is mode-separated (`sks search files|text|structure|symbol|context`); `context` is answered by the compiled TriWiki Context Graph (`context-graph.json` is exhaustive authority; `context-pack.json` and managed `AGENTS.md` are bounded projections) — see [docs/architecture/context-graph.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/architecture/context-graph.md) and [docs/PRODUCT-CONTRACT.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/docs/PRODUCT-CONTRACT.md). See [CHANGELOG.md](https://github.com/mandarange/Sneakoscope-Codex/blob/main/CHANGELOG.md).
|
|
28
28
|
|
|
@@ -25,6 +25,7 @@ export const NATIVE_SOURCE_FILES = [
|
|
|
25
25
|
'ProvidersOpenRouter.swift',
|
|
26
26
|
'ProvidersBridgeCatalog.swift',
|
|
27
27
|
'RemoteCodingViewController.swift',
|
|
28
|
+
'RemoteCodingSettingsControls.swift',
|
|
28
29
|
'DiagnosticsViewController.swift',
|
|
29
30
|
'SettingsViewController.swift',
|
|
30
31
|
'OperationModels.swift',
|
|
@@ -9,7 +9,6 @@ const CURRENT_COMPILED_TESTS = new Set([
|
|
|
9
9
|
'dist/core/__tests__/codex-release-manifest-current.test.js',
|
|
10
10
|
'dist/core/__tests__/release-gate-hermetic-env.test.js',
|
|
11
11
|
'dist/core/__tests__/update-menubar-package-local.test.js',
|
|
12
|
-
'dist/core/__tests__/update-simulated-upgrade.test.js',
|
|
13
12
|
'dist/core/__tests__/version-manager-current-docs.test.js',
|
|
14
13
|
'dist/core/install/__tests__/installed-package-smoke.test.js',
|
|
15
14
|
'dist/core/perf/__tests__/release-latency-slo.test.js'
|
|
@@ -11,6 +11,8 @@ export const FORBIDDEN_RECURSIVE_GATES = new Set([
|
|
|
11
11
|
]);
|
|
12
12
|
export const ALWAYS_ON_GATES = new Set([
|
|
13
13
|
'release:metadata-current',
|
|
14
|
+
'release:version-truth',
|
|
15
|
+
'install-surface:ssot',
|
|
14
16
|
'architecture:guard',
|
|
15
17
|
'safety:mutation-callsite-coverage',
|
|
16
18
|
'side-effect:runtime-report',
|
|
@@ -25,6 +27,8 @@ export const ALWAYS_ON_GATES = new Set([
|
|
|
25
27
|
]);
|
|
26
28
|
export const REQUIRED_FOR_PUBLISH = new Set([
|
|
27
29
|
'release:metadata-current',
|
|
30
|
+
'release:version-truth',
|
|
31
|
+
'install-surface:ssot',
|
|
28
32
|
'architecture:guard',
|
|
29
33
|
'safety:mutation-callsite-coverage',
|
|
30
34
|
'side-effect:runtime-report',
|
|
@@ -11,6 +11,7 @@ export const RELEASE_GATE_CONTRACT_IDS = Object.freeze([
|
|
|
11
11
|
'commands:current-surface-only',
|
|
12
12
|
'config:managed-merge',
|
|
13
13
|
'docs:truthfulness',
|
|
14
|
+
'install-surface:ssot',
|
|
14
15
|
'latest-version:guidance',
|
|
15
16
|
'migration:current-surface-e2e',
|
|
16
17
|
'migration:upgrade-safety',
|
|
@@ -24,6 +25,7 @@ export const RELEASE_GATE_CONTRACT_IDS = Object.freeze([
|
|
|
24
25
|
'release:metadata-current',
|
|
25
26
|
'release:proof-truth',
|
|
26
27
|
'release:provenance',
|
|
28
|
+
'release:version-truth',
|
|
27
29
|
'runtime:installed-smoke',
|
|
28
30
|
'safety:mutation-callsite-coverage',
|
|
29
31
|
'schema:check',
|
|
@@ -23,6 +23,7 @@ export async function probeTelegram(input) {
|
|
|
23
23
|
blockers.push(`telegram_token_load_failed:${redactTelegramError(error)}`);
|
|
24
24
|
}
|
|
25
25
|
let identityValid = false;
|
|
26
|
+
let botId = null;
|
|
26
27
|
let getMeCheckedAt = null;
|
|
27
28
|
let getMeLatencyMs = null;
|
|
28
29
|
if (!tokenConfigured)
|
|
@@ -30,7 +31,9 @@ export async function probeTelegram(input) {
|
|
|
30
31
|
else {
|
|
31
32
|
const started = Date.now();
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
+
const identity = await input.client.getMe();
|
|
35
|
+
identityValid = identity.is_bot === true && Number.isSafeInteger(identity.id) && identity.id > 0;
|
|
36
|
+
botId = identityValid ? identity.id : null;
|
|
34
37
|
if (!identityValid)
|
|
35
38
|
blockers.push('telegram_getme_not_bot');
|
|
36
39
|
}
|
|
@@ -72,6 +75,7 @@ export async function probeTelegram(input) {
|
|
|
72
75
|
status: !tokenConfigured ? 'not_configured' : blockers.length ? 'degraded' : 'ready',
|
|
73
76
|
token_configured: tokenConfigured,
|
|
74
77
|
token_source: tokenConfigured ? (receiptProbe?.token_source ?? 'unknown') : 'none',
|
|
78
|
+
bot_id: botId,
|
|
75
79
|
bot_identity_valid: identityValid,
|
|
76
80
|
getme_checked_at: getMeCheckedAt,
|
|
77
81
|
getme_latency_ms: getMeLatencyMs,
|
|
@@ -89,7 +93,7 @@ async function probeTelegramReceipt(receiptPath, now) {
|
|
|
89
93
|
if (!liveness.ok) {
|
|
90
94
|
return {
|
|
91
95
|
schema: 'sks.telegram-doctor-probe.v1', ok: false, status: 'not_configured',
|
|
92
|
-
token_configured: false, bot_identity_valid: false, paired_chat_count: 0,
|
|
96
|
+
token_configured: false, bot_id: null, bot_identity_valid: false, paired_chat_count: 0,
|
|
93
97
|
token_source: 'none',
|
|
94
98
|
audit_healthy: false, audit_last_error: null,
|
|
95
99
|
getme_checked_at: null, getme_latency_ms: null,
|
|
@@ -122,6 +126,7 @@ async function probeTelegramReceipt(receiptPath, now) {
|
|
|
122
126
|
status: !receipt.token_configured ? 'not_configured' : blockers.length ? 'degraded' : 'ready',
|
|
123
127
|
token_configured: receipt.token_configured,
|
|
124
128
|
token_source: receipt.token_source ?? (receipt.token_configured ? 'unknown' : 'none'),
|
|
129
|
+
bot_id: receipt.bot_id ?? null,
|
|
125
130
|
bot_identity_valid: receipt.bot_identity_valid,
|
|
126
131
|
getme_checked_at: receipt.getme_checked_at ?? null,
|
|
127
132
|
getme_latency_ms: receipt.getme_latency_ms ?? null,
|
|
@@ -323,6 +323,10 @@ async function syncSourcePackageVersion(root, version) {
|
|
|
323
323
|
rel: 'src/core/version.ts',
|
|
324
324
|
replace: (text) => text.replace(/export const PACKAGE_VERSION = ['"][^'"]+['"];/, `export const PACKAGE_VERSION = '${version}';`)
|
|
325
325
|
},
|
|
326
|
+
{
|
|
327
|
+
rel: 'plugins/sks/.codex-plugin/plugin.json',
|
|
328
|
+
replace: (text) => text.replace(/("version"\s*:\s*")[^"]+("(?=\s*[,}]))/, `$1${version}$2`)
|
|
329
|
+
},
|
|
326
330
|
{
|
|
327
331
|
rel: 'crates/sks-core/Cargo.toml',
|
|
328
332
|
replace: (text) => text.replace(/^version\s*=\s*"[^"]+"/m, `version = "${version}"`)
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '8.2.
|
|
1
|
+
export const PACKAGE_VERSION = '8.2.1';
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import Cocoa
|
|
2
|
+
|
|
3
|
+
final class RemoteCodingSettingsControls {
|
|
4
|
+
private static let tokenPattern = #"^\d{5,20}:[A-Za-z0-9_-]{20,128}$"#
|
|
5
|
+
|
|
6
|
+
let selectedBotDetail = NativeView.detail("No bot is configured.")
|
|
7
|
+
let pairingCommandField = NativeView.detail("Generate a code to create the /start pairing command.")
|
|
8
|
+
let botTokenField: NSSecureTextField
|
|
9
|
+
let setupButton: NSButton
|
|
10
|
+
let copyPairingButton: NSButton
|
|
11
|
+
|
|
12
|
+
private var pairingCommand: String?
|
|
13
|
+
private var selectedBotID: Int64?
|
|
14
|
+
private var selectedBotUsername: String?
|
|
15
|
+
|
|
16
|
+
init(target: AnyObject, submitTokenAction: Selector, copyPairingAction: Selector) {
|
|
17
|
+
let tokenField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24))
|
|
18
|
+
tokenField.placeholderString = "Paste the complete BotFather HTTP API token"
|
|
19
|
+
tokenField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
20
|
+
tokenField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
21
|
+
tokenField.setAccessibilityLabel("BotFather HTTP API token")
|
|
22
|
+
tokenField.setAccessibilityHelp("Required. Paste the complete token for any Telegram bot you own. SKS clears this field before verification and never displays the token again.")
|
|
23
|
+
tokenField.setAccessibilityIdentifier("sks-center-telegram-bot-token")
|
|
24
|
+
tokenField.target = target
|
|
25
|
+
tokenField.action = submitTokenAction
|
|
26
|
+
botTokenField = tokenField
|
|
27
|
+
|
|
28
|
+
setupButton = NativeView.button("Verify, Save & Apply", target: target, action: submitTokenAction)
|
|
29
|
+
setupButton.setAccessibilityIdentifier("sks-center-telegram-verify-save-apply")
|
|
30
|
+
setupButton.setAccessibilityHelp("Verify the entered bot token with Telegram, save it to the private SKS user secret file, and apply it to the resident poller.")
|
|
31
|
+
|
|
32
|
+
copyPairingButton = NativeView.button("Copy Pairing Command", target: target, action: copyPairingAction)
|
|
33
|
+
copyPairingButton.setAccessibilityHelp("Copy only the generated /start pairing command. The bot token is never copied or displayed.")
|
|
34
|
+
|
|
35
|
+
pairingCommandField.isSelectable = true
|
|
36
|
+
pairingCommandField.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
|
|
37
|
+
pairingCommandField.setAccessibilityLabel("Telegram pairing command")
|
|
38
|
+
pairingCommandField.setAccessibilityIdentifier("sks-center-telegram-pairing-command")
|
|
39
|
+
selectedBotDetail.isSelectable = true
|
|
40
|
+
selectedBotDetail.setAccessibilityLabel("Selected Telegram bot")
|
|
41
|
+
selectedBotDetail.setAccessibilityIdentifier("sks-center-telegram-selected-bot")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func configure(delegate: NSTextFieldDelegate) {
|
|
45
|
+
botTokenField.delegate = delegate
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func consumeTokenInput() -> String {
|
|
49
|
+
let value = botTokenField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
50
|
+
botTokenField.stringValue = ""
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func updateActionControls(operationInFlight: Bool, tokenConfigured: Bool, environmentOverride: Bool) {
|
|
55
|
+
let hasTokenInput = !botTokenField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
56
|
+
botTokenField.isEnabled = !operationInFlight && !environmentOverride
|
|
57
|
+
setupButton.title = tokenConfigured ? "Verify, Replace & Apply" : "Verify, Save & Apply"
|
|
58
|
+
setupButton.setAccessibilityLabel(setupButton.title)
|
|
59
|
+
setupButton.isEnabled = !operationInFlight && !environmentOverride && hasTokenInput
|
|
60
|
+
copyPairingButton.isEnabled = !operationInFlight && pairingCommand != nil
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func renderSelectedBot(
|
|
64
|
+
botID: Int64?,
|
|
65
|
+
username: String? = nil,
|
|
66
|
+
configured: Bool,
|
|
67
|
+
identityValid: Bool
|
|
68
|
+
) {
|
|
69
|
+
guard configured else {
|
|
70
|
+
selectedBotID = nil
|
|
71
|
+
selectedBotUsername = nil
|
|
72
|
+
selectedBotDetail.stringValue = "No bot is configured."
|
|
73
|
+
selectedBotDetail.setAccessibilityValue(selectedBotDetail.stringValue)
|
|
74
|
+
resetPairingCommand("Generate a code after connecting a bot.")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
let retainedUsername = username ?? (botID == selectedBotID ? selectedBotUsername : nil)
|
|
78
|
+
selectedBotID = botID
|
|
79
|
+
selectedBotUsername = retainedUsername
|
|
80
|
+
if let botID, let retainedUsername, identityValid {
|
|
81
|
+
selectedBotDetail.stringValue = "@\(retainedUsername) · Bot ID \(botID)"
|
|
82
|
+
} else if let botID, identityValid {
|
|
83
|
+
selectedBotDetail.stringValue = "Bot ID \(botID) · verified by Telegram"
|
|
84
|
+
} else if let botID {
|
|
85
|
+
selectedBotDetail.stringValue = "Bot ID \(botID) · identity needs verification"
|
|
86
|
+
} else {
|
|
87
|
+
selectedBotDetail.stringValue = "Configured bot · identity details unavailable"
|
|
88
|
+
}
|
|
89
|
+
selectedBotDetail.setAccessibilityValue(selectedBotDetail.stringValue)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func setPairingCode(_ code: String) {
|
|
93
|
+
pairingCommand = "/start \(code)"
|
|
94
|
+
pairingCommandField.stringValue = pairingCommand ?? ""
|
|
95
|
+
pairingCommandField.setAccessibilityValue(pairingCommandField.stringValue)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func resetPairingCommand(_ detail: String) {
|
|
99
|
+
pairingCommand = nil
|
|
100
|
+
pairingCommandField.stringValue = detail
|
|
101
|
+
pairingCommandField.setAccessibilityValue(detail)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@discardableResult
|
|
105
|
+
func copyPairingCommand() -> Bool {
|
|
106
|
+
guard let pairingCommand else { return false }
|
|
107
|
+
let pasteboard = NSPasteboard.general
|
|
108
|
+
pasteboard.clearContents()
|
|
109
|
+
return pasteboard.setString(pairingCommand, forType: .string)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static func validTokenShape(_ value: String) -> Bool {
|
|
113
|
+
value.utf8.count <= 1024
|
|
114
|
+
&& value.range(of: tokenPattern, options: .regularExpression) != nil
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static func publicFailure(_ output: String, fallback: String) -> String {
|
|
118
|
+
guard let data = output.data(using: .utf8),
|
|
119
|
+
let row = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
120
|
+
let error = row["error"] as? String else { return fallback }
|
|
121
|
+
switch error {
|
|
122
|
+
case "telegram_token_invalid", "telegram_token_stdin_empty":
|
|
123
|
+
return "That value is not a complete BotFather HTTP API token."
|
|
124
|
+
case "telegram_token_rejected":
|
|
125
|
+
return "Telegram rejected this token. In @BotFather, select the bot you want SKS to use and copy that bot's current complete token."
|
|
126
|
+
case "telegram_identity_verification_timeout":
|
|
127
|
+
return "Telegram identity verification timed out. Check the network connection and try the selected bot token again."
|
|
128
|
+
case "telegram_identity_verification_network_failed":
|
|
129
|
+
return "Telegram identity verification could not reach the Bot API. Check the network connection and try again."
|
|
130
|
+
case "telegram_identity_verification_failed":
|
|
131
|
+
return "Telegram returned an invalid bot identity response. Try the selected bot's current token again."
|
|
132
|
+
case let value where value.contains("401") || value.contains("Unauthorized"):
|
|
133
|
+
return "Telegram rejected this token. Copy the current token from @BotFather and try again."
|
|
134
|
+
case let value where value.contains("timeout") || value.contains("network"):
|
|
135
|
+
return "Telegram identity verification could not complete. Check the network connection and try again."
|
|
136
|
+
default:
|
|
137
|
+
return fallback
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static func partialSetupRecovery(_ response: TelegramCenterSetupResponse?) -> String {
|
|
142
|
+
var parts: [String] = []
|
|
143
|
+
if response?.webhook_removed == true { parts.append("The existing webhook was removed without dropping pending updates.") }
|
|
144
|
+
if response?.bot_state_reset == true { parts.append("Bot-scoped pairing and poll state were reset safely.") }
|
|
145
|
+
if response?.token_stored == false { parts.append("The token was not stored; rerun secure setup to finish.") }
|
|
146
|
+
if let note = response?.recovery?.note, !note.isEmpty { parts.append(note) }
|
|
147
|
+
return parts.isEmpty
|
|
148
|
+
? "Review the secret-free recovery status, then rerun secure setup."
|
|
149
|
+
: parts.joined(separator: " ")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static func tokenSourceDescription(_ source: String) -> String {
|
|
153
|
+
switch source {
|
|
154
|
+
case "env": return "operator environment token"
|
|
155
|
+
case "user_secret_file": return "private token file"
|
|
156
|
+
case "none": return "no token source"
|
|
157
|
+
default: return "token source unknown"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Cocoa
|
|
2
2
|
|
|
3
|
-
final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
3
|
+
final class RemoteCodingViewController: NSViewController, ControlCenterPage, NSTextFieldDelegate {
|
|
4
4
|
private static let botFatherURL = URL(string: "https://t.me/BotFather")!
|
|
5
|
-
private static let tokenPattern = #"^\d{5,20}:[A-Za-z0-9_-]{20,128}$"#
|
|
6
5
|
|
|
7
6
|
private let processClient: ProcessClient
|
|
8
7
|
private let telegramService: TelegramMenuBarService
|
|
@@ -10,7 +9,11 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
10
9
|
private let serviceDetail = NativeView.detail("Reading secret-free Telegram liveness status…")
|
|
11
10
|
private let pairingDetail = NativeView.detail("No pairing code has been issued in this session.")
|
|
12
11
|
private let serviceSpinner = NativeView.spinner(label: "Telegram operation in progress")
|
|
13
|
-
private lazy var
|
|
12
|
+
private lazy var settingsControls = RemoteCodingSettingsControls(
|
|
13
|
+
target: self,
|
|
14
|
+
submitTokenAction: #selector(enterToken),
|
|
15
|
+
copyPairingAction: #selector(copyPairingCommand)
|
|
16
|
+
)
|
|
14
17
|
private lazy var pairButton = NativeView.button("Generate Pairing Code", target: self, action: #selector(generatePairingCode))
|
|
15
18
|
private lazy var startButton = NativeView.button("Start", target: self, action: #selector(startService))
|
|
16
19
|
private lazy var stopButton = NativeView.button("Stop", target: self, action: #selector(stopService))
|
|
@@ -31,7 +34,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
31
34
|
override func loadView() {
|
|
32
35
|
let botFatherButton = NativeView.button("Open @BotFather", target: self, action: #selector(openBotFather))
|
|
33
36
|
botFatherButton.setAccessibilityHelp("Open the official Telegram BotFather chat in your default handler.")
|
|
34
|
-
|
|
37
|
+
settingsControls.configure(delegate: self)
|
|
35
38
|
pairButton.setAccessibilityHelp("With Telegram polling running, issue a short-lived, single-use code for pairing one intended private chat.")
|
|
36
39
|
startButton.setAccessibilityHelp("Start Telegram polling for the current SKS companion session after a bot token is configured.")
|
|
37
40
|
stopButton.setAccessibilityHelp("Stop polling for the current companion session without deleting the token or paired chats. A companion relaunch starts it again.")
|
|
@@ -47,12 +50,14 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
47
50
|
views: [
|
|
48
51
|
NativeView.detail(
|
|
49
52
|
"1. Create or select any bot you own in @BotFather. 2. Copy that bot's complete HTTP API token. " +
|
|
50
|
-
"3.
|
|
53
|
+
"3. Paste it below and choose Verify, Save & Apply. The token is the only value you enter: SKS derives the bot ID from Telegram getMe, and learns chat and user IDs through private-chat pairing."
|
|
51
54
|
),
|
|
52
55
|
NativeView.detail(
|
|
53
56
|
"The token is sent only through process stdin to the canonical SKS setup command, verified with Telegram getMe, and stored in the private user secret file. It is never shown again or written to the SKS action log. Revoke it in BotFather if you suspect exposure."
|
|
54
57
|
),
|
|
55
|
-
|
|
58
|
+
ControlKit.keyValueRow("Active Bot", settingsControls.selectedBotDetail),
|
|
59
|
+
ControlKit.keyValueRow("Bot Token", settingsControls.botTokenField),
|
|
60
|
+
NativeView.row([botFatherButton, settingsControls.setupButton])
|
|
56
61
|
]
|
|
57
62
|
)
|
|
58
63
|
|
|
@@ -61,10 +66,11 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
61
66
|
subtitle: "Authorize only the intended Telegram account and private chat.",
|
|
62
67
|
views: [
|
|
63
68
|
NativeView.detail(
|
|
64
|
-
"With Telegram polling running, generate a short-lived
|
|
69
|
+
"With Telegram polling running, generate a short-lived command, open the selected bot from the intended private chat, and send that command. SKS derives the chat and user IDs from Telegram; they are never typed into this screen. Codes are single-use and group chats are rejected. After pairing, try /sks status {}."
|
|
65
70
|
),
|
|
71
|
+
ControlKit.keyValueRow("Pair Command", settingsControls.pairingCommandField),
|
|
66
72
|
pairingDetail,
|
|
67
|
-
pairButton
|
|
73
|
+
NativeView.row([pairButton, settingsControls.copyPairingButton])
|
|
68
74
|
]
|
|
69
75
|
)
|
|
70
76
|
|
|
@@ -97,6 +103,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
@objc private func enterToken() {
|
|
106
|
+
guard !operationInFlight else { return }
|
|
100
107
|
if TelegramPrivateFileStore.operatorEnvironmentOverrideActive() {
|
|
101
108
|
showAttention(
|
|
102
109
|
"Environment-managed token is active",
|
|
@@ -104,31 +111,38 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
104
111
|
)
|
|
105
112
|
return
|
|
106
113
|
}
|
|
107
|
-
|
|
114
|
+
let normalizedToken = settingsControls.consumeTokenInput()
|
|
115
|
+
updateControls()
|
|
116
|
+
guard RemoteCodingSettingsControls.validTokenShape(normalizedToken) else {
|
|
117
|
+
showAttention(
|
|
118
|
+
"Token not saved",
|
|
119
|
+
detail: "That value does not match the BotFather token format. Copy the complete HTTP API token and try again."
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
saveToken(normalizedToken, removeWebhook: false)
|
|
108
124
|
}
|
|
109
125
|
|
|
110
|
-
private func
|
|
126
|
+
private func promptForWebhookRemovalToken() {
|
|
111
127
|
guard !operationInFlight, let window = view.window else { return }
|
|
112
128
|
AlertFactory.textSheet(
|
|
113
129
|
window: window,
|
|
114
|
-
title: "
|
|
115
|
-
message:
|
|
116
|
-
? "Paste this selected bot's BotFather token again. With your consent, SKS will remove that bot's existing webhook without dropping pending updates, verify its live identity, and store the token privately."
|
|
117
|
-
: "Paste the complete HTTP API token for any bot you own in @BotFather. SKS will accept the identity returned by Telegram getMe without assuming a bot name, then store the token privately.",
|
|
130
|
+
title: "Authorize Telegram Webhook Removal",
|
|
131
|
+
message: "Paste this selected bot's BotFather token again. With your consent, SKS will remove that bot's existing webhook without dropping pending updates, verify its live identity, and store the token privately.",
|
|
118
132
|
secure: true,
|
|
119
133
|
placeholder: "123456789:BotFatherToken",
|
|
120
|
-
actionTitle:
|
|
134
|
+
actionTitle: "Remove Webhook & Save"
|
|
121
135
|
) { [weak self] token in
|
|
122
136
|
guard let self, let token else { return }
|
|
123
137
|
let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
124
|
-
guard
|
|
138
|
+
guard RemoteCodingSettingsControls.validTokenShape(normalizedToken) else {
|
|
125
139
|
self.showAttention(
|
|
126
140
|
"Token not saved",
|
|
127
141
|
detail: "That value does not match the BotFather token format. Copy the complete HTTP API token and try again."
|
|
128
142
|
)
|
|
129
143
|
return
|
|
130
144
|
}
|
|
131
|
-
self.saveToken(normalizedToken, removeWebhook:
|
|
145
|
+
self.saveToken(normalizedToken, removeWebhook: true)
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
148
|
|
|
@@ -150,7 +164,10 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
150
164
|
code.range(of: #"^\d{6}-[A-F0-9]{4}$"#, options: .regularExpression) != nil else {
|
|
151
165
|
self.showAttention(
|
|
152
166
|
"Pairing code unavailable",
|
|
153
|
-
detail:
|
|
167
|
+
detail: RemoteCodingSettingsControls.publicFailure(
|
|
168
|
+
result.output,
|
|
169
|
+
fallback: "SKS could not create a pairing code. Refresh status and try again."
|
|
170
|
+
)
|
|
154
171
|
)
|
|
155
172
|
return
|
|
156
173
|
}
|
|
@@ -160,6 +177,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
160
177
|
let postPair = response.post_pair_command ?? "/sks status {}"
|
|
161
178
|
let confirmation = response.confirmation_grammar ?? "/confirm <nonce>"
|
|
162
179
|
let statusGuidance = instruction.contains(postPair) ? "" : " Then try \(postPair)."
|
|
180
|
+
self.settingsControls.setPairingCode(code)
|
|
163
181
|
self.pairingDetail.stringValue = "\(instruction)\(expiration)\(statusGuidance) Confirm prompted actions with \(confirmation)."
|
|
164
182
|
self.pairingDetail.setAccessibilityValue(self.pairingDetail.stringValue)
|
|
165
183
|
self.showProgress("Pairing code ready", detail: "Use the displayed code once in the intended private chat.")
|
|
@@ -167,6 +185,11 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
167
185
|
}
|
|
168
186
|
}
|
|
169
187
|
|
|
188
|
+
@objc private func copyPairingCommand() {
|
|
189
|
+
guard settingsControls.copyPairingCommand() else { return }
|
|
190
|
+
showProgress("Pairing command copied", detail: "Paste it once into the intended private chat with the selected bot.")
|
|
191
|
+
}
|
|
192
|
+
|
|
170
193
|
@objc private func startService() { runLifecycle(.start) }
|
|
171
194
|
@objc private func stopService() { runLifecycle(.stop) }
|
|
172
195
|
@objc private func restartService() { runLifecycle(.restart) }
|
|
@@ -217,7 +240,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
217
240
|
|
|
218
241
|
private func saveToken(_ token: String, removeWebhook: Bool) {
|
|
219
242
|
let normalizedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
220
|
-
guard validTokenShape(normalizedToken) else {
|
|
243
|
+
guard RemoteCodingSettingsControls.validTokenShape(normalizedToken) else {
|
|
221
244
|
showAttention(
|
|
222
245
|
"Token not saved",
|
|
223
246
|
detail: "That value does not match the BotFather token format. Copy the complete token and try again."
|
|
@@ -261,13 +284,13 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
261
284
|
if response?.partial_success == true {
|
|
262
285
|
self.showAttention(
|
|
263
286
|
"Telegram setup partially completed",
|
|
264
|
-
detail:
|
|
287
|
+
detail: RemoteCodingSettingsControls.partialSetupRecovery(response)
|
|
265
288
|
)
|
|
266
289
|
return
|
|
267
290
|
}
|
|
268
291
|
self.showAttention(
|
|
269
292
|
"Token not saved",
|
|
270
|
-
detail:
|
|
293
|
+
detail: RemoteCodingSettingsControls.publicFailure(
|
|
271
294
|
result.output,
|
|
272
295
|
fallback: "Telegram setup could not complete. Check the connection and SKS diagnostics, then try again."
|
|
273
296
|
)
|
|
@@ -280,6 +303,15 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
280
303
|
let receipt = try await restart.value
|
|
281
304
|
self?.endOperation()
|
|
282
305
|
self?.render(receipt)
|
|
306
|
+
self?.settingsControls.renderSelectedBot(
|
|
307
|
+
botID: response.bot_id ?? receipt.bot_id,
|
|
308
|
+
username: response.bot_username,
|
|
309
|
+
configured: true,
|
|
310
|
+
identityValid: true
|
|
311
|
+
)
|
|
312
|
+
if response.bot_state_reset == true {
|
|
313
|
+
self?.settingsControls.resetPairingCommand("Generate a new code because the selected bot changed.")
|
|
314
|
+
}
|
|
283
315
|
let selectedBot = response.bot_username.map { "@\($0)" } ?? "The selected bot"
|
|
284
316
|
self?.showProgress(
|
|
285
317
|
"Bot verified and connected",
|
|
@@ -309,7 +341,7 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
309
341
|
actionTitle: "Continue"
|
|
310
342
|
) { [weak self] confirmed in
|
|
311
343
|
guard confirmed else { return }
|
|
312
|
-
self?.
|
|
344
|
+
self?.promptForWebhookRemovalToken()
|
|
313
345
|
}
|
|
314
346
|
}
|
|
315
347
|
|
|
@@ -349,8 +381,13 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
349
381
|
private func render(_ response: TelegramCenterDoctorResponse) {
|
|
350
382
|
tokenConfigured = response.token_configured
|
|
351
383
|
pollerRunning = response.poller.running
|
|
384
|
+
settingsControls.renderSelectedBot(
|
|
385
|
+
botID: response.bot_id,
|
|
386
|
+
configured: response.token_configured,
|
|
387
|
+
identityValid: response.bot_identity_valid
|
|
388
|
+
)
|
|
352
389
|
let paired = response.paired_chat_count == 1 ? "1 paired chat" : "\(response.paired_chat_count) paired chats"
|
|
353
|
-
let source = tokenSourceDescription(response.token_source)
|
|
390
|
+
let source = RemoteCodingSettingsControls.tokenSourceDescription(response.token_source)
|
|
354
391
|
if response.ok {
|
|
355
392
|
NativeView.setBadge(serviceBadge, text: "Ready", color: .systemGreen)
|
|
356
393
|
serviceDetail.stringValue = "Bot identity verified · \(source) · \(paired) · poller running · audit healthy."
|
|
@@ -384,8 +421,13 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
384
421
|
private func render(_ receipt: TelegramLivenessReceipt) {
|
|
385
422
|
tokenConfigured = receipt.token_configured
|
|
386
423
|
pollerRunning = receipt.running && receipt.poller.running
|
|
424
|
+
settingsControls.renderSelectedBot(
|
|
425
|
+
botID: receipt.bot_id,
|
|
426
|
+
configured: receipt.token_configured,
|
|
427
|
+
identityValid: receipt.bot_identity_valid
|
|
428
|
+
)
|
|
387
429
|
let paired = receipt.paired_chat_count == 1 ? "1 paired chat" : "\(receipt.paired_chat_count) paired chats"
|
|
388
|
-
let source = tokenSourceDescription(receipt.token_source.rawValue)
|
|
430
|
+
let source = RemoteCodingSettingsControls.tokenSourceDescription(receipt.token_source.rawValue)
|
|
389
431
|
NativeView.setBadge(
|
|
390
432
|
serviceBadge,
|
|
391
433
|
text: pollerRunning ? "Running" : "Stopped",
|
|
@@ -424,7 +466,12 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
424
466
|
}
|
|
425
467
|
|
|
426
468
|
private func updateControls() {
|
|
427
|
-
|
|
469
|
+
let environmentOverride = TelegramPrivateFileStore.operatorEnvironmentOverrideActive()
|
|
470
|
+
settingsControls.updateActionControls(
|
|
471
|
+
operationInFlight: operationInFlight,
|
|
472
|
+
tokenConfigured: tokenConfigured,
|
|
473
|
+
environmentOverride: environmentOverride
|
|
474
|
+
)
|
|
428
475
|
pairButton.isEnabled = !operationInFlight && tokenConfigured && pollerRunning
|
|
429
476
|
startButton.isEnabled = !operationInFlight && tokenConfigured && !pollerRunning
|
|
430
477
|
stopButton.isEnabled = !operationInFlight && pollerRunning
|
|
@@ -432,9 +479,10 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
432
479
|
refreshButton.isEnabled = !operationInFlight
|
|
433
480
|
}
|
|
434
481
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
482
|
+
func controlTextDidChange(_ notification: Notification) {
|
|
483
|
+
guard let field = notification.object as? NSTextField,
|
|
484
|
+
field === settingsControls.botTokenField else { return }
|
|
485
|
+
updateControls()
|
|
438
486
|
}
|
|
439
487
|
|
|
440
488
|
private func decode<T: Decodable>(_ type: T.Type, _ output: String) -> T? {
|
|
@@ -442,53 +490,4 @@ final class RemoteCodingViewController: NSViewController, ControlCenterPage {
|
|
|
442
490
|
return try? JSONDecoder().decode(type, from: data)
|
|
443
491
|
}
|
|
444
492
|
|
|
445
|
-
private func publicFailure(_ output: String, fallback: String) -> String {
|
|
446
|
-
guard let data = output.data(using: .utf8),
|
|
447
|
-
let row = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
448
|
-
let error = row["error"] as? String else { return fallback }
|
|
449
|
-
switch error {
|
|
450
|
-
case "telegram_token_invalid", "telegram_token_stdin_empty":
|
|
451
|
-
return "That value is not a complete BotFather HTTP API token."
|
|
452
|
-
case "telegram_token_rejected":
|
|
453
|
-
return "Telegram rejected this token. In @BotFather, select the bot you want SKS to use and copy that bot's current complete token."
|
|
454
|
-
case "telegram_identity_verification_timeout":
|
|
455
|
-
return "Telegram identity verification timed out. Check the network connection and try the selected bot token again."
|
|
456
|
-
case "telegram_identity_verification_network_failed":
|
|
457
|
-
return "Telegram identity verification could not reach the Bot API. Check the network connection and try again."
|
|
458
|
-
case "telegram_identity_verification_failed":
|
|
459
|
-
return "Telegram returned an invalid bot identity response. Try the selected bot's current token again."
|
|
460
|
-
case let value where value.contains("401") || value.contains("Unauthorized"):
|
|
461
|
-
return "Telegram rejected this token. Copy the current token from @BotFather and try again."
|
|
462
|
-
case let value where value.contains("timeout") || value.contains("network"):
|
|
463
|
-
return "Telegram identity verification could not complete. Check the network connection and try again."
|
|
464
|
-
default:
|
|
465
|
-
return fallback
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private func partialSetupRecovery(_ response: TelegramCenterSetupResponse?) -> String {
|
|
470
|
-
var parts: [String] = []
|
|
471
|
-
if response?.webhook_removed == true {
|
|
472
|
-
parts.append("The existing webhook was removed without dropping pending updates.")
|
|
473
|
-
}
|
|
474
|
-
if response?.bot_state_reset == true {
|
|
475
|
-
parts.append("Bot-scoped pairing and poll state were reset safely.")
|
|
476
|
-
}
|
|
477
|
-
if response?.token_stored == false {
|
|
478
|
-
parts.append("The token was not stored; rerun secure setup to finish.")
|
|
479
|
-
}
|
|
480
|
-
if let note = response?.recovery?.note, !note.isEmpty { parts.append(note) }
|
|
481
|
-
return parts.isEmpty
|
|
482
|
-
? "Review the secret-free recovery status, then rerun secure setup."
|
|
483
|
-
: parts.joined(separator: " ")
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private func tokenSourceDescription(_ source: String) -> String {
|
|
487
|
-
switch source {
|
|
488
|
-
case "env": return "operator environment token"
|
|
489
|
-
case "user_secret_file": return "private token file"
|
|
490
|
-
case "none": return "no token source"
|
|
491
|
-
default: return "token source unknown"
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
493
|
}
|
|
@@ -24,7 +24,7 @@ if (pluginVersion && packageVersion && pluginVersion !== packageVersion) {
|
|
|
24
24
|
blockers.push(`marketplace_plugin_version_mismatch:${pluginVersion}!=${packageVersion}`);
|
|
25
25
|
}
|
|
26
26
|
else if (!pluginVersion) {
|
|
27
|
-
|
|
27
|
+
blockers.push('marketplace_plugin_version_absent');
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
notes.push(`marketplace_plugin_version_matches:${pluginVersion}`);
|
|
@@ -78,7 +78,7 @@ else {
|
|
|
78
78
|
const report = {
|
|
79
79
|
schema: 'sks.install-surface-ssot-check.v1',
|
|
80
80
|
ok: blockers.length === 0,
|
|
81
|
-
install_ssot: '
|
|
81
|
+
install_ssot: 'package.json#version',
|
|
82
82
|
package_version: packageVersion || null,
|
|
83
83
|
plugin_version: pluginVersion || null,
|
|
84
84
|
path_sks_version: pathVersion || null,
|
|
@@ -43,6 +43,7 @@ const requiredPackageScripts = [
|
|
|
43
43
|
'typecheck',
|
|
44
44
|
'release:check',
|
|
45
45
|
'release:metadata',
|
|
46
|
+
'release:version-truth',
|
|
46
47
|
'release:check:affected',
|
|
47
48
|
'release:check:fast',
|
|
48
49
|
'release:check:confidence',
|
|
@@ -70,6 +71,7 @@ const requiredReleaseGates = [
|
|
|
70
71
|
'commands:current-surface-only',
|
|
71
72
|
'config:managed-merge',
|
|
72
73
|
'docs:truthfulness',
|
|
74
|
+
'install-surface:ssot',
|
|
73
75
|
'latest-version:guidance',
|
|
74
76
|
'migration:current-surface-e2e',
|
|
75
77
|
'migration:upgrade-safety',
|
|
@@ -83,6 +85,7 @@ const requiredReleaseGates = [
|
|
|
83
85
|
'release:latency-slo',
|
|
84
86
|
'release:proof-truth',
|
|
85
87
|
'release:provenance',
|
|
88
|
+
'release:version-truth',
|
|
86
89
|
'policy:gate-audit',
|
|
87
90
|
'safety:mutation-callsite-coverage',
|
|
88
91
|
'schema:check',
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { assertGate, emitGate, root } from './gate-lib.js';
|
|
6
|
+
const pkg = readJson('package.json');
|
|
7
|
+
const expected = String(pkg.version || '');
|
|
8
|
+
const mismatches = [];
|
|
9
|
+
const warnings = [];
|
|
10
|
+
let checked = 0;
|
|
11
|
+
checkJson('package.json', 'version', pkg.version);
|
|
12
|
+
const lock = readJson('package-lock.json');
|
|
13
|
+
checkJson('package-lock.json', 'version', lock.version);
|
|
14
|
+
checkJson('package-lock.json', 'packages[""].version', lock.packages?.['']?.version);
|
|
15
|
+
const plugin = readJson('plugins/sks/.codex-plugin/plugin.json');
|
|
16
|
+
checkJson('plugins/sks/.codex-plugin/plugin.json', 'version', plugin.version);
|
|
17
|
+
checkRegex('src/core/version.ts', /PACKAGE_VERSION\s*=\s*['"]([^'"]+)['"]/, 'PACKAGE_VERSION');
|
|
18
|
+
checkReExportOrRegex('src/core/fsx.ts', /PACKAGE_VERSION\s*}\s*from\s*['"]\.\/version(?:\.js)?['"]/, /PACKAGE_VERSION\s*=\s*['"]([^'"]+)['"]/, 'PACKAGE_VERSION');
|
|
19
|
+
checkReExportOrRegex('src/bin/sks.ts', /PACKAGE_VERSION\s*}\s*from\s*['"]\.\.\/core\/version(?:\.js)?['"]/, /FAST_PACKAGE_VERSION\s*=\s*['"]([^'"]+)['"]/, 'FAST_PACKAGE_VERSION');
|
|
20
|
+
checkRegex('crates/sks-core/Cargo.toml', /^version\s*=\s*"([^"]+)"/m, 'package.version');
|
|
21
|
+
checkCargoLock('crates/sks-core/Cargo.lock', 'sks-core');
|
|
22
|
+
const dist = readJson('dist/build-manifest.json', null);
|
|
23
|
+
checkJson('dist/build-manifest.json', 'package_version', dist?.package_version);
|
|
24
|
+
checkJson('dist/build-manifest.json', 'version', dist?.version);
|
|
25
|
+
checkChangelog();
|
|
26
|
+
checkReadme();
|
|
27
|
+
checkReleaseMetadataScript();
|
|
28
|
+
checkCargoMetadata();
|
|
29
|
+
const ok = mismatches.length === 0;
|
|
30
|
+
const report = {
|
|
31
|
+
schema: 'sks.release-version-truth.v1',
|
|
32
|
+
ok,
|
|
33
|
+
expected,
|
|
34
|
+
checked,
|
|
35
|
+
mismatches,
|
|
36
|
+
warnings,
|
|
37
|
+
generated_at: new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
const out = path.join(root, '.sneakoscope', 'reports', 'release-version-truth.json');
|
|
40
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
41
|
+
fs.writeFileSync(out, `${JSON.stringify(report, null, 2)}\n`);
|
|
42
|
+
assertGate(ok, 'release version truth mismatch', { expected, mismatches });
|
|
43
|
+
emitGate('release:version-truth', { version: expected, checked, warnings: warnings.length });
|
|
44
|
+
function checkJson(file, field, actual) {
|
|
45
|
+
checked += 1;
|
|
46
|
+
if (actual !== expected)
|
|
47
|
+
mismatch(file, field, actual);
|
|
48
|
+
}
|
|
49
|
+
function checkRegex(file, re, field) {
|
|
50
|
+
checked += 1;
|
|
51
|
+
const text = readText(file);
|
|
52
|
+
const match = text.match(re);
|
|
53
|
+
if (!match)
|
|
54
|
+
mismatch(file, field, null);
|
|
55
|
+
else if (match[1] !== expected)
|
|
56
|
+
mismatch(file, field, match[1]);
|
|
57
|
+
}
|
|
58
|
+
function checkReExportOrRegex(file, reExportRe, literalRe, field) {
|
|
59
|
+
const text = readText(file);
|
|
60
|
+
if (reExportRe.test(text)) {
|
|
61
|
+
checked += 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
checkRegex(file, literalRe, field);
|
|
65
|
+
}
|
|
66
|
+
function checkCargoLock(file, name) {
|
|
67
|
+
checked += 1;
|
|
68
|
+
const text = readText(file);
|
|
69
|
+
const block = text.split(/\n\[\[package\]\]\n/).find((part) => new RegExp(`name\\s*=\\s*"${escapeRe(name)}"`).test(part));
|
|
70
|
+
const match = block?.match(/version\s*=\s*"([^"]+)"/);
|
|
71
|
+
if (!match)
|
|
72
|
+
mismatch(file, `${name}.version`, null);
|
|
73
|
+
else if (match[1] !== expected)
|
|
74
|
+
mismatch(file, `${name}.version`, match[1]);
|
|
75
|
+
}
|
|
76
|
+
function checkChangelog() {
|
|
77
|
+
checked += 1;
|
|
78
|
+
const text = readText('CHANGELOG.md');
|
|
79
|
+
const latest = latestVersionedChangelogSection(text);
|
|
80
|
+
if (latest !== expected)
|
|
81
|
+
mismatch('CHANGELOG.md', 'latest release section', latest);
|
|
82
|
+
}
|
|
83
|
+
function checkReadme() {
|
|
84
|
+
checked += 1;
|
|
85
|
+
const text = readText('README.md');
|
|
86
|
+
const displayed = text.match(/\*\*SKS ([0-9]+\.[0-9]+\.[0-9]+)\*\*/)?.[1] || null;
|
|
87
|
+
if (!displayed)
|
|
88
|
+
mismatch('README.md', 'displayed current version', null);
|
|
89
|
+
else if (displayed !== expected)
|
|
90
|
+
mismatch('README.md', 'displayed current version', displayed);
|
|
91
|
+
}
|
|
92
|
+
function checkReleaseMetadataScript() {
|
|
93
|
+
checked += 1;
|
|
94
|
+
const script = String(pkg.scripts?.['release:metadata'] || '');
|
|
95
|
+
if (!script.includes('dist/scripts/release-metadata-check.js')) {
|
|
96
|
+
mismatch('package.json', 'scripts.release:metadata', script || null, 'node ./dist/scripts/release-metadata-check.js');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function checkCargoMetadata() {
|
|
100
|
+
checked += 1;
|
|
101
|
+
const res = spawnSync('cargo', ['metadata', '--no-deps', '--manifest-path', path.join(root, 'crates/sks-core/Cargo.toml'), '--format-version', '1'], {
|
|
102
|
+
cwd: root,
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
timeout: 30000
|
|
105
|
+
});
|
|
106
|
+
if (res.status !== 0) {
|
|
107
|
+
warnings.push({ file: 'crates/sks-core/Cargo.toml', message: 'cargo metadata unavailable', stderr_tail: tail(res.stderr) });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const metadata = JSON.parse(res.stdout);
|
|
112
|
+
const crate = metadata.packages?.find((row) => row.name === 'sks-core');
|
|
113
|
+
if (crate?.version !== expected)
|
|
114
|
+
mismatch('cargo metadata', 'sks-core.version', crate?.version || null);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
warnings.push({ file: 'cargo metadata', message: `unparseable:${err instanceof Error ? err.message : String(err)}` });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function mismatch(file, field, actual, wanted = expected) {
|
|
121
|
+
mismatches.push({ file, field, expected: wanted, actual: actual ?? null });
|
|
122
|
+
}
|
|
123
|
+
function readJson(rel, fallback) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(readText(rel));
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (arguments.length > 1)
|
|
129
|
+
return fallback;
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function readText(rel) {
|
|
134
|
+
return fs.readFileSync(path.join(root, rel), 'utf8');
|
|
135
|
+
}
|
|
136
|
+
function escapeRe(value) {
|
|
137
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
138
|
+
}
|
|
139
|
+
function latestVersionedChangelogSection(text) {
|
|
140
|
+
for (const match of text.matchAll(/^## \[([^\]]+)\]/gm)) {
|
|
141
|
+
if (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(match[1]))
|
|
142
|
+
return match[1];
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function tail(value, limit = 1000) {
|
|
147
|
+
const text = String(value || '');
|
|
148
|
+
return text.length > limit ? text.slice(-limit) : text;
|
|
149
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "8.2.
|
|
4
|
+
"version": "8.2.1",
|
|
5
5
|
"description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"!dist/scripts/**",
|
|
41
41
|
"dist/scripts/canonical-test-runner.js",
|
|
42
42
|
"dist/scripts/desktop-bridge-transport-client.js",
|
|
43
|
+
"dist/scripts/release-version-truth-check.js",
|
|
43
44
|
"dist/scripts/{agent-guidance-dedup-check.js,agent-message-bus-reader-check.js,all-feature-deep-completion-check.js,appshots-thread-attachment-discovery-check.js,architecture-guard-check.js,certificate-sla-check.js,check-runtime-schemas.js,cli-output-consistency-check.js,codex-current-app-server-v2-check.js,codex-current-binary-identity-check.js,codex-current-capability-check.js,codex-current-core-real-probes-check.js,codex-current-manifest-check.js,codex-current-policy-check.js,codex-current-thread-store-check.js,codex-agent-role-rich-content-check.js,codex-agent-role-sync-check.js,codex-agent-type-routing-check.js,codex-app-execution-profile-check.js,codex-app-fast-ui-preservation-check.js,codex-app-handoff-check.js,codex-app-handoff-launch-check.js,codex-config-load-probe.js,context-graph-check.js,codex-control-all-pipelines-check.js,codex-control-event-stream-ledger-check.js,codex-control-tool-output-continuity-audit-check.js,desktop-bridge-transport-check.js,desktop-bridge-latency-check.js,desktop-bridge-capabilities-check.js,desktop-bridge-real-evidence-check.js,codex-native-agent-role-content-check.js,codex-native-feature-broker-blackbox.js,codex-native-feature-broker-check.js,codex-native-harness-compat-check.js,codex-native-hook-lifecycle-proof-check.js,codex-native-runtime-e2e-fixture.js,codex-plugin-app-template-policy-check.js,codex-sdk-all-pipelines-check.js,codex-sdk-backend-router-check.js,codex-sdk-capability-check.js,codex-sdk-event-stream-ledger-check.js,codex-sdk-real-smoke-check.js,concurrent-session-collision-check.js,context7-evidence-dedupe-check.js,core-skill-card-schema-check.js,core-skill-deployment-snapshot-check.js,core-skill-heldout-validation-check.js,core-skill-no-inference-optimizer-check.js,core-skill-patch-check.js,core-skill-route-runtime-integration-check.js,current-command-surface-check.js,current-surface-update-e2e-check.js,current-upgrade-matrix-check.js,desktop-bridge-unification-check.js,dfix-fixture-check.js,dfix-patch-handoff-check.js,dfix-verification-check.js,dfix-verification-recommendation-check.js,docs-truthfulness-check.js,doctor-codex-app-harness-check.js,doctor-fix-proves-codex-read-check.js,doctor-fixes-codex-app-fast-ui-check.js,evidence-flagship-coverage-check.js,fast-codex-service-tier-proof-check.js,fixtures/fake-codex-config-loader.js,gate-policy-audit-check.js,geo-claim-evidence-check.js,geo-cli-blackbox-check.js,geo-crawler-policy-check.js,git-worktree-checkpoint-check.js,git-worktree-cross-rebase-check.js,git-worktree-diff-envelope-check.js,git-worktree-dirty-lock-check.js,git-worktree-dirty-main-detection-check.js,git-worktree-integration-primary-check.js,git-worktree-manifest-append-check.js,git-worktree-untracked-diff-check.js,goal-artifact-compat-check.js,harness-benchmark-check.js,helper-dedup-check.js,hook-latency-budget-check.js,image-artifact-path-contract-check.js,imagegen-real-smoke-check.js,init-deep-backup-retention-check.js,installed-package-smoke-check.js,lib/codex-sdk-gate-lib.js,lib/ensure-dist-fresh.js,lib/git-worktree-fixture.js,lib/valid-png-fixture.js,local-collab-all-pipelines-final-gpt-check.js,loop-directive-check-lib.js,loop-integration-finalizer-check.js,loop-worker-fixture-child.js,mad-preflight-blocks-unreadable-config-check.js,mad-sks-app-ui-no-mutation-check.js}",
|
|
44
45
|
"dist/scripts/{latest-version-guidance-check.js,mad-sks-launch-sql-plane-boundary-check.js,mad-sks-sql-plane-capability-check.js,mad-sks-sql-plane-lifecycle-hook-decision-check.js,mad-sks-sql-plane-result-lifecycle-check.js,managed-config-merge-check.js,mcp-plugin-inventory-check.js,model-call-concurrency-check.js,mutation-callsite-analysis.js,mutation-callsite-coverage-check.js,naruto-ssot-gate-aliases-check.js,naruto-ssot-pipeline-default-check.js,naruto-ssot-route-normalization-check.js,naruto-ssot-routing-check.js,naruto-worktree-coding-blackbox.js,native-capability-repair-matrix-check.js,native-image-generation-repair-check.js,official-subagent-workflow-check.js,package-published-contract-check.js,packlist-performance-check.js,pipeline-codex-native-doctor-mad-routing-check.js,pipeline-codex-native-doctor-mad-routing-real-blackbox.js,pipeline-codex-native-image-routing-check.js,pipeline-codex-native-image-routing-real-blackbox.js,pipeline-codex-native-loop-routing-check.js,pipeline-codex-native-loop-routing-real-blackbox.js,pipeline-codex-native-qa-routing-check.js,pipeline-codex-native-qa-routing-real-blackbox.js,pipeline-codex-native-research-routing-check.js,pipeline-codex-native-research-routing-real-blackbox.js,ppt-full-e2e-blackbox-check.js,ppt-real-export-adapter-check.js,ppt-real-imagegen-smoke-check.js,ppt-real-imagegen-wiring-check.js,ppt-reexport-rereview-check.js,prepublish-fast-check.js,publish-preflight.js,prepublish-release-check-or-fast.js,probe-memoization-check.js,product-design-plugin-routing-check.js,prompt-placeholder-guard-check.js,proof-root-cause-policy-check.js,provider-badge-context-check.js,python-codex-sdk-all-pipelines-check.js,qa-loop-app-handoff-capability-check.js,qa-loop-app-handoff-check.js,qa-loop-app-handoff-cli-check.js,qa-loop-app-handoff-confirmation-check.js,qa-loop-app-handoff-gate-lifecycle-check.js,qa-loop-app-handoff-launch-check.js,qa-loop-app-handoff-status-lifecycle-check.js,qa-loop-budget-policy-check.js,qa-loop-effort-escalation-check.js,qa-loop-execution-profile-routing-check.js,qa-loop-image-path-exposure-check.js,qa-loop-image-path-prompt-injection-check.js,release-affected-selector-check.js,release-aggressive-resource-governor-check.js,release-cache-glob-hashing-check.js,release-cache-input-classifier-check.js,release-cache-neutralization-report-check.js,release-cache-version-neutral-fixture-check.js,release-check-stamp.js,release-dynamic-presets-check.js,release-full-parallelism-blackbox.js,release-gate-batch-runner-check.js,release-gate-budget-check.js,release-gate-dag-runner-check.js,release-gate-dag-runner.js,release-latency-slo-check.js,release-metadata-check.js,release-pack-receipt.js,release-parallel-speed-budget-check.js,release-physical-gates-check.js,release-proof-truth-check.js,release-provenance-check.js,release-real-check.js,release-runtime-truth-matrix-check.js,release-speed-summary.js,research-blueprint-densifier-check.js,research-claim-builder-check.js,research-complete-package-fixture-check.js,research-execution-profile-routing-check.js,research-final-reviewer-blackbox.js,responses-retry-policy-centralized-check.js,runtime-proof-summary-check.js,runtime-script-pack-closure-check.js,scheduler-batch-dispatch-check.js,scheduler-parallel-proof-consistency-check.js,scheduler-utilization-integral-check.js,search-visibility-gate-lib.js,secret-preservation-check.js,seo-audit-fixture-check.js,seo-cli-blackbox-check.js,seo-geo-geo-disambiguation-check.js,seo-geo-route-identity-check.js,seo-geo-skill-rich-content-check.js,seo-no-mutation-by-default-check.js,shared-memory-fixture-check.js,side-effect-runtime-report-check.js,skill-name-canonicalizer-check.js,skill-surface-modernization-check.js,skills-manifest-continuity-check.js,sks-cli-gate-lib.js}",
|
|
45
46
|
"dist/scripts/{real-execution-check-lib.js,gate-lib.js,runtime-domain-gate-lib.js,typed-routing-gate-lib.js,codex-native-gate-lib.js,skill-fixture-check-lib.js,sks-uninstall-regression-check.js,sksd-daemon-check.js,super-search-provider-interface-check.js,triwiki-cache-key-check.js,triwiki-proof-bank-blackbox.js,triwiki-proof-bank-check.js,triwiki-proof-card-check.js,trust-fixture-check.js,type-surface-codex-app-check.js,ultra-router-auto-router-check.js,uninstall-inventory-check.js,install-surface-ssot-check.js,ux-review-extract-wires-real-extractor-check.js,ux-review-imagegen-blackbox-check.js,ux-review-patch-diff-recheck-check.js,ux-review-real-imagegen-smoke-check.js,ux-review-run-wires-imagegen-check.js,wrongness-fixture-check.js}",
|
package/release-gates.v2.json
CHANGED
|
@@ -703,6 +703,43 @@
|
|
|
703
703
|
],
|
|
704
704
|
"output_contract": "sks.gate-result.v2"
|
|
705
705
|
},
|
|
706
|
+
{
|
|
707
|
+
"id": "release:version-truth",
|
|
708
|
+
"command": "node ./dist/scripts/release-version-truth-check.js",
|
|
709
|
+
"deps": [],
|
|
710
|
+
"resource": [
|
|
711
|
+
"cpu-light",
|
|
712
|
+
"fs-read"
|
|
713
|
+
],
|
|
714
|
+
"side_effect": "hermetic",
|
|
715
|
+
"timeout_ms": 60000,
|
|
716
|
+
"cache": {
|
|
717
|
+
"enabled": false,
|
|
718
|
+
"inputs": [
|
|
719
|
+
"package.json",
|
|
720
|
+
"package-lock.json",
|
|
721
|
+
"plugins/sks/.codex-plugin/plugin.json",
|
|
722
|
+
"src/core/version.ts",
|
|
723
|
+
"src/core/fsx.ts",
|
|
724
|
+
"src/bin/sks.ts",
|
|
725
|
+
"crates/sks-core/Cargo.toml",
|
|
726
|
+
"crates/sks-core/Cargo.lock",
|
|
727
|
+
"dist/build-manifest.json",
|
|
728
|
+
"CHANGELOG.md",
|
|
729
|
+
"README.md",
|
|
730
|
+
"src/scripts/release-version-truth-check.ts"
|
|
731
|
+
]
|
|
732
|
+
},
|
|
733
|
+
"isolation": {
|
|
734
|
+
"home": "temp",
|
|
735
|
+
"codex_home": "temp",
|
|
736
|
+
"report_dir": "per-gate"
|
|
737
|
+
},
|
|
738
|
+
"preset": [
|
|
739
|
+
"release"
|
|
740
|
+
],
|
|
741
|
+
"output_contract": "sks.gate-result.v2"
|
|
742
|
+
},
|
|
706
743
|
{
|
|
707
744
|
"id": "install-surface:ssot",
|
|
708
745
|
"command": "node ./dist/scripts/install-surface-ssot-check.js",
|
|
@@ -714,7 +751,7 @@
|
|
|
714
751
|
"side_effect": "hermetic",
|
|
715
752
|
"timeout_ms": 60000,
|
|
716
753
|
"cache": {
|
|
717
|
-
"enabled":
|
|
754
|
+
"enabled": false,
|
|
718
755
|
"inputs": [
|
|
719
756
|
"package.json",
|
|
720
757
|
"plugins/sks/.codex-plugin/plugin.json",
|
|
@@ -727,7 +764,7 @@
|
|
|
727
764
|
"report_dir": "per-gate"
|
|
728
765
|
},
|
|
729
766
|
"preset": [
|
|
730
|
-
"
|
|
767
|
+
"release"
|
|
731
768
|
],
|
|
732
769
|
"output_contract": "sks.gate-result.v2"
|
|
733
770
|
},
|