sneakoscope 1.0.6 → 1.0.7

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 CHANGED
@@ -4,6 +4,8 @@ Fast legacy-free proof-first Codex trust layer with image-based Voxel TriWiki.
4
4
 
5
5
  Sneakoscope Codex (`sks`) is a Codex CLI/App harness that makes repeatable Codex work auditable.
6
6
 
7
+ SKS **1.0.7** is the Ultimate Final Completion seal for the Codex trust harness: Computer Use live evidence is an opt-in, local-only macOS evidence path with explicit `probe_only`, `live_capture_attempted`, `live_capture_success`, and `live_capture_blocked` modes; `codex-lb setup` reports durable persistence versus `process_only_ephemeral` honestly; and docs/release readiness checks block mock/probe/live overclaims.
8
+
7
9
  SKS **1.0.6** is the final precision polish for the Codex trust harness: hook compatibility is classified as upstream schema plus an SKS zero-warning strict subset, `sks codex-lb setup` previews and applies the exact choices the user selected, and Computer Use has an optional live smoke surface for macOS capability/evidence status.
8
10
 
9
11
  SKS **1.0.5** sealed the prior trust harness: hook outputs were validated against both vendored OpenAI Codex CLI `rust-v0.131.0` schemas and runtime semantic parser rules, codex-lb setup survived macOS user-session launches through env-file/Keychain/launchctl-aware repair surfaces, and Computer Use became the preferred macOS visual evidence capability when available.
@@ -20,8 +22,42 @@ Hybrid-free **`1.0.1`** already delivered the CLI entrypoint/router/registry/Tru
20
22
 
21
23
  SKS does not try to clone every other harness. It focuses on one thing: making Codex work auditable, visual-evidence-bound, safety-gated, and reproducible through Completion Proof.
22
24
 
23
- ![Sneakoscope Codex architecture and pipeline](https://raw.githubusercontent.com/mandarange/Sneakoscope-Codex/dev/docs/assets/sneakoscope-architecture-pipeline.jpg)
25
+ ![Sneakoscope Codex Trust Layer](docs/assets/sneakoscope-architecture-pipeline.jpg)
26
+
27
+
28
+ ## 1.0.7 Ultimate Final Completion
29
+
30
+ 1.0.7 does not add a new route, skill, or competing harness. It closes the last trust gaps around live visual evidence, persistence truth, and release documentation.
31
+
32
+ Computer Use live evidence stays optional and explicit:
33
+
34
+ ```bash
35
+ sks computer-use smoke --json
36
+ sks computer-use smoke --real --capture-screenshot --json
37
+ sks computer-use smoke --real --require-real --json
38
+ npm run computer-use:live-evidence
39
+ ```
40
+
41
+ Default smoke is `probe_only` and never attempts screen capture. Real mode records `live_capture_attempted`, `live_capture_success`, or `live_capture_blocked`; if Codex App, macOS permission, or the official Computer Use capture surface is unavailable, SKS writes a structured blocker instead of fabricated evidence. Browser Use evidence and manual screenshots remain separate sources. Computer Use screenshots are local-only by default, carry SHA-256 metadata, and link to Image Voxel only when a mission-local anchor can be made.
42
+
43
+ codex-lb setup now reports the exact persistence truth:
44
+
45
+ ```bash
46
+ sks codex-lb setup --host lb.example.com --api-key-stdin --plan --json
47
+ sks codex-lb setup --host lb.example.com --api-key-stdin --yes --no-env-file --no-keychain --no-launchctl --shell-profile skip --json
48
+ npm run codex-lb:persistence-truth
49
+ ```
50
+
51
+ Durable modes are `durable_env_file`, `durable_keychain`, `durable_launchctl`, and `shell_profile`. If all durable modes are disabled, setup is classified as `process_only_ephemeral`, emits `next_shell_requires_setup_or_env`, and warns that Codex App GUI launches may not see credentials. Use `sks codex-lb setup --write-env-file --keychain --launchctl` to recover durable persistence.
52
+
53
+ Documentation truthfulness is now a release invariant:
54
+
55
+ ```bash
56
+ npm run docs:truthfulness
57
+ npm run release:readiness
58
+ ```
24
59
 
60
+ The hook compatibility surface remains the upstream schema plus the SKS zero-warning strict subset; SKS does not claim to mirror every upstream runtime parser rule or guarantee universal Computer Use availability.
25
61
 
26
62
  ## 1.0.6 Final Precision Polish
27
63
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.0.6"
79
+ version = "1.0.7"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.0.6"
3
+ version = "1.0.7"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 1.0.6"),
7
+ Some("--version") => println!("sks-rs 1.0.7"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.0.6';
2
+ const FAST_PACKAGE_VERSION = '1.0.7';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--version' || args[0] === '-v' || args[0] === 'version') {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "typescript": true,
5
5
  "mjs_runtime_files": 0,
6
6
  "files": [
@@ -286,6 +286,8 @@
286
286
  "core/commands/wiki-command.js",
287
287
  "core/commands/wrongness-command.d.ts",
288
288
  "core/commands/wrongness-command.js",
289
+ "core/computer-use-live-evidence.d.ts",
290
+ "core/computer-use-live-evidence.js",
289
291
  "core/computer-use-status.d.ts",
290
292
  "core/computer-use-status.js",
291
293
  "core/context7-client.d.ts",
@@ -1,3 +1,4 @@
1
+ import { type CodexLbPersistenceSummary } from '../core/codex-lb/codex-lb-setup.js';
1
2
  type CodexLbStatusSnapshot = Awaited<ReturnType<typeof codexLbStatus>>;
2
3
  /** Install-time shim reconciliation; fields vary by `status`. */
3
4
  export type SksPostinstallShimResult = {
@@ -59,6 +60,7 @@ export type ConfigureCodexLbResult = {
59
60
  plan?: Record<string, unknown>;
60
61
  applied_actions?: Array<Record<string, unknown>>;
61
62
  drift?: string[];
63
+ persistence?: CodexLbPersistenceSummary;
62
64
  config_path?: string;
63
65
  env_path?: string;
64
66
  metadata_path?: string;
@@ -12,7 +12,7 @@ import { codexLaunchCommand, platformTmuxInstallHint, tmuxReadiness, tmuxReadine
12
12
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
13
13
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
14
14
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
15
- import { buildCodexLbSetupPlan, installCodexLbShellProfileSnippet } from '../core/codex-lb/codex-lb-setup.js';
15
+ import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
16
16
  const DEFAULT_CODEX_APP_PLUGINS = [
17
17
  ['browser', 'openai-bundled'],
18
18
  ['chrome', 'openai-bundled'],
@@ -324,6 +324,7 @@ export async function configureCodexLb(opts = {}) {
324
324
  run_health_check: opts.runHealth === true,
325
325
  allow_insecure_localhost: opts.allowInsecureHttp === true || opts.allowInsecureLocalhost === true
326
326
  };
327
+ const selectedPersistenceModes = selectedCodexLbPersistenceModes(setupAnswers);
327
328
  const plan = buildCodexLbSetupPlan(setupAnswers, {
328
329
  home,
329
330
  configPath,
@@ -341,6 +342,7 @@ export async function configureCodexLb(opts = {}) {
341
342
  const insecureLocalWarning = /^http:\/\//i.test(baseUrl) && !/^http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::|\/|$)/i.test(baseUrl) && !opts.allowInsecureHttp
342
343
  ? ['codex-lb base URL uses http outside localhost; prefer https or pass an explicit allow flag in the calling surface.']
343
344
  : [];
345
+ const beforeState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
344
346
  const appliedActions = [];
345
347
  await ensureDir(path.dirname(configPath));
346
348
  const current = await readText(configPath, '');
@@ -381,6 +383,7 @@ export async function configureCodexLb(opts = {}) {
381
383
  const authReconcile = await reconcileCodexLbAuthConflict({ ...opts, home, status: codexLb }).catch((err) => ({ status: 'failed', reason: 'exception', error: err.message }));
382
384
  const finalCodexLb = await codexLbStatus({ ...opts, home, configPath, envPath });
383
385
  const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
386
+ const afterState = await captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile });
384
387
  const drift = detectCodexLbSetupDrift({
385
388
  useDefaultProvider,
386
389
  writeEnvFile,
@@ -391,21 +394,41 @@ export async function configureCodexLb(opts = {}) {
391
394
  envFile: finalCodexLb.env_file,
392
395
  keychain,
393
396
  codexEnvironment,
394
- shellProfileResult
397
+ shellProfileResult,
398
+ beforeState,
399
+ afterState
395
400
  });
401
+ const appliedPersistenceModes = appliedCodexLbPersistenceModes({
402
+ writeEnvFile,
403
+ storeKeychain,
404
+ syncLaunchctl,
405
+ shellProfile,
406
+ envFile: finalCodexLb.env_file,
407
+ keychain,
408
+ codexEnvironment,
409
+ shellProfileResult,
410
+ apiKeySource: finalCodexLb.env_loader?.api_key?.source || null
411
+ });
412
+ const persistence = codexLbPersistenceSummary({
413
+ selectedModes: selectedPersistenceModes,
414
+ appliedModes: appliedPersistenceModes,
415
+ processOnly: appliedPersistenceModes.includes('process_only_ephemeral')
416
+ });
417
+ const warnings = [...insecureLocalWarning, ...persistence.warnings];
396
418
  return {
397
419
  ok: ok && drift.length === 0,
398
420
  status: ok && drift.length === 0 ? 'configured' : drift.length ? 'setup_choice_drift' : (codexEnvironment.status || codexLogin.status),
399
421
  plan: plan,
400
422
  applied_actions: appliedActions,
401
423
  drift,
424
+ persistence,
402
425
  config_path: configPath,
403
426
  env_path: envPath,
404
427
  metadata_path: metadataPath,
405
428
  base_url: baseUrl,
406
429
  env_key: 'CODEX_LB_API_KEY',
407
430
  keychain,
408
- warnings: insecureLocalWarning,
431
+ warnings,
409
432
  auth_reconcile: authReconcile,
410
433
  codex_lb: finalCodexLb,
411
434
  codex_environment: codexEnvironment,
@@ -1339,7 +1362,9 @@ function detectCodexLbSetupDrift(state = {}) {
1339
1362
  drift.push('default_provider_selected_despite_no_default_provider');
1340
1363
  if (state.writeEnvFile && state.envFile !== true)
1341
1364
  drift.push('env_file_not_written');
1342
- if (!state.writeEnvFile && state.envFile === true)
1365
+ if (!state.writeEnvFile && state.beforeState && state.afterState && state.beforeState.envHash !== state.afterState.envHash)
1366
+ drift.push('env_file_changed_despite_no_env_file');
1367
+ if (!state.writeEnvFile && !state.beforeState && state.envFile === true)
1343
1368
  drift.push('env_file_written_despite_no_env_file');
1344
1369
  if (!state.storeKeychain && state.keychain?.status && state.keychain.status !== 'skipped')
1345
1370
  drift.push('keychain_touched_despite_no_keychain');
@@ -1347,8 +1372,54 @@ function detectCodexLbSetupDrift(state = {}) {
1347
1372
  drift.push('launchctl_synced_despite_no_launchctl');
1348
1373
  if (state.shellProfile === 'skip' && state.shellProfileResult?.status === 'installed')
1349
1374
  drift.push('shell_profile_written_despite_skip');
1375
+ if (state.shellProfile === 'skip' && state.beforeState && state.afterState && state.beforeState.profileHash !== state.afterState.profileHash)
1376
+ drift.push('shell_profile_changed_despite_skip');
1350
1377
  return drift;
1351
1378
  }
1379
+ async function captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile } = {}) {
1380
+ const profileFiles = profileFilesForDrift(home, shellProfile);
1381
+ return {
1382
+ configHash: await fileHashOrMissing(configPath),
1383
+ envHash: await fileHashOrMissing(envPath),
1384
+ profileHash: (await Promise.all(profileFiles.map((file) => fileHashOrMissing(file)))).join('|')
1385
+ };
1386
+ }
1387
+ async function fileHashOrMissing(file) {
1388
+ const text = await readText(file, null).catch(() => null);
1389
+ return text === null ? 'missing' : await sha256Text(String(text));
1390
+ }
1391
+ function profileFilesForDrift(home, shellProfile) {
1392
+ const targets = {
1393
+ zsh: path.join(home, '.zshrc'),
1394
+ bash: path.join(home, '.bashrc'),
1395
+ fish: path.join(home, '.config', 'fish', 'config.fish')
1396
+ };
1397
+ if (shellProfile === 'zsh')
1398
+ return [targets.zsh];
1399
+ if (shellProfile === 'bash')
1400
+ return [targets.bash];
1401
+ if (shellProfile === 'fish')
1402
+ return [targets.fish];
1403
+ if (shellProfile === 'all')
1404
+ return [targets.zsh, targets.bash, targets.fish];
1405
+ return [targets.zsh, targets.bash, targets.fish];
1406
+ }
1407
+ function appliedCodexLbPersistenceModes(state = {}) {
1408
+ const modes = [];
1409
+ if (state.writeEnvFile && state.envFile === true)
1410
+ modes.push('durable_env_file');
1411
+ if (state.storeKeychain && state.keychain?.ok === true)
1412
+ modes.push('durable_keychain');
1413
+ if (state.syncLaunchctl && state.codexEnvironment?.launch_environment?.status === 'synced')
1414
+ modes.push('durable_launchctl');
1415
+ if (state.shellProfile !== 'skip' && state.shellProfileResult?.status === 'installed')
1416
+ modes.push('shell_profile');
1417
+ if (!modes.length && state.apiKeySource === 'process.env')
1418
+ modes.push('process_only_ephemeral');
1419
+ if (!modes.length)
1420
+ modes.push('none');
1421
+ return modes;
1422
+ }
1352
1423
  export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
1353
1424
  if (process.env.SKS_SKIP_CODEX_FAST_MODE_REPAIR === '1')
1354
1425
  return { status: 'skipped', reason: 'SKS_SKIP_CODEX_FAST_MODE_REPAIR=1' };
@@ -6,7 +6,7 @@ import { flag, readOption } from '../cli/args.js';
6
6
  import { printJson } from '../cli/output.js';
7
7
  import { codexLbMetrics, readCodexLbCircuit, recordCodexLbHealthEvent, resetCodexLbCircuit, codexLbProofEvidence } from '../core/codex-lb-circuit.js';
8
8
  import { checkCodexLbResponseChain, codexLbStatus, configureCodexLb, formatCodexLbStatusText, releaseCodexLbAuthHold, repairCodexLbAuth, unselectCodexLbProvider } from '../cli/install-helpers.js';
9
- import { buildCodexLbSetupPlan, renderCodexLbSetupPlan } from '../core/codex-lb/codex-lb-setup.js';
9
+ import { buildCodexLbSetupPlan, codexLbPersistenceSummary, renderCodexLbSetupPlan } from '../core/codex-lb/codex-lb-setup.js';
10
10
  export async function run(command, args = []) {
11
11
  const root = await projectRoot();
12
12
  const action = args[0] || 'status';
@@ -111,7 +111,7 @@ export async function run(command, args = []) {
111
111
  return;
112
112
  }
113
113
  if (flag(args, '--plan')) {
114
- const result = { schema: 'sks.codex-lb-setup-plan-result.v1', ok: plan.blockers.length === 0, plan, writes: false };
114
+ const result = { schema: 'sks.codex-lb-setup-plan-result.v1', ok: plan.blockers.length === 0, plan, writes: false, expected_actions: plan.expected_actions, persistence: plan.persistence };
115
115
  if (flag(args, '--json'))
116
116
  return printJson(result);
117
117
  process.stdout.write(renderCodexLbSetupPlan(plan));
@@ -119,6 +119,7 @@ export async function run(command, args = []) {
119
119
  process.exitCode = 1;
120
120
  return;
121
121
  }
122
+ const processOnly = plan.persistence.effective_mode === 'process_only_ephemeral';
122
123
  if (options.interactive && !options.yes) {
123
124
  process.stdout.write(renderCodexLbSetupPlan(plan));
124
125
  const confirm = (await ask('Apply this codex-lb setup plan? [y/N] ')).trim();
@@ -130,6 +131,39 @@ export async function run(command, args = []) {
130
131
  process.exitCode = 1;
131
132
  return;
132
133
  }
134
+ if (processOnly) {
135
+ const confirmProcessOnly = (await ask('This setup keeps credentials only in the current process. Type process-only to continue: ')).trim();
136
+ if (confirmProcessOnly !== 'process-only') {
137
+ const result = { schema: 'sks.codex-lb-setup.v1', ok: false, status: 'process_only_cancelled', plan, applied_actions: [], persistence: plan.persistence };
138
+ if (flag(args, '--json')) {
139
+ printJson(result);
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ console.log('codex-lb setup cancelled: process-only ephemeral setup was not confirmed.');
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ }
148
+ }
149
+ else if (processOnly && !options.yes) {
150
+ const result = {
151
+ schema: 'sks.codex-lb-setup.v1',
152
+ ok: false,
153
+ status: 'process_only_requires_yes',
154
+ plan,
155
+ applied_actions: [],
156
+ persistence: plan.persistence,
157
+ guidance: ['Pass --yes to acknowledge process_only_ephemeral setup, or enable --write-env-file, --keychain, --launchctl, or --shell-profile.']
158
+ };
159
+ if (flag(args, '--json')) {
160
+ printJson(result);
161
+ process.exitCode = 1;
162
+ return;
163
+ }
164
+ console.error('codex-lb setup would be process-only ephemeral. Pass --yes to acknowledge, or enable a durable persistence mode.');
165
+ process.exitCode = 1;
166
+ return;
133
167
  }
134
168
  const result = await configureCodexLb({
135
169
  host: options.host,
@@ -152,6 +186,8 @@ export async function run(command, args = []) {
152
186
  if (flag(args, '--json'))
153
187
  return printJson(shaped);
154
188
  console.log(`codex-lb configured: ${result.base_url || result.status}`);
189
+ if (shaped.persistence?.warning)
190
+ console.log(`warning: ${shaped.persistence.warning}`);
155
191
  if (!result.ok)
156
192
  process.exitCode = 1;
157
193
  return;
@@ -190,6 +226,18 @@ export async function run(command, args = []) {
190
226
  process.exitCode = 1;
191
227
  }
192
228
  function shapeCodexLbStatus(status = {}) {
229
+ const mode = status.env_loader?.api_key?.source === 'env-file'
230
+ ? 'durable_env_file'
231
+ : status.env_loader?.api_key?.source === 'keychain'
232
+ ? 'durable_keychain'
233
+ : status.env_loader?.api_key?.source === 'process.env'
234
+ ? 'process_only_ephemeral'
235
+ : 'none';
236
+ const persistence = codexLbPersistenceSummary({
237
+ selectedModes: mode === 'none' ? [] : [mode],
238
+ appliedModes: mode === 'none' ? ['none'] : [mode],
239
+ processOnly: mode === 'process_only_ephemeral'
240
+ });
193
241
  return {
194
242
  schema: 'sks.codex-lb-status.v1',
195
243
  ...status,
@@ -201,6 +249,7 @@ function shapeCodexLbStatus(status = {}) {
201
249
  source: status.env_loader?.api_key?.source || null,
202
250
  redacted: true
203
251
  },
252
+ persistence,
204
253
  env_loader: status.env_loader || null,
205
254
  env_auto_load: Boolean(status.env_file && status.env_key_configured),
206
255
  guidance: status.ok ? [] : [
@@ -1,5 +1,6 @@
1
1
  export type CodexLbApiKeySource = 'hidden_prompt' | 'stdin' | 'cli_option' | 'keychain_existing';
2
2
  export type CodexLbShellProfileChoice = 'zsh' | 'bash' | 'fish' | 'all' | 'skip';
3
+ export type CodexLbPersistenceMode = 'durable_env_file' | 'durable_keychain' | 'durable_launchctl' | 'shell_profile' | 'process_only_ephemeral' | 'none';
3
4
  export type CodexLbSetupActionType = 'write_config_provider' | 'select_default_provider' | 'write_env_file' | 'store_keychain' | 'sync_launchctl' | 'install_shell_profile_snippet' | 'run_health_check' | 'write_metadata';
4
5
  export interface CodexLbSetupAnswers {
5
6
  host_or_base_url: string;
@@ -22,9 +23,21 @@ export interface CodexLbSetupPlan {
22
23
  schema: 'sks.codex-lb-setup-plan.v1';
23
24
  base_url: string;
24
25
  actions: CodexLbSetupAction[];
26
+ expected_actions: CodexLbSetupAction[];
27
+ selected_persistence_modes: CodexLbPersistenceMode[];
28
+ persistence: CodexLbPersistenceSummary;
25
29
  redactions: string[];
30
+ warnings: string[];
26
31
  blockers: string[];
27
32
  }
33
+ export interface CodexLbPersistenceSummary {
34
+ selected_modes: CodexLbPersistenceMode[];
35
+ applied_modes: CodexLbPersistenceMode[];
36
+ effective_mode: CodexLbPersistenceMode;
37
+ durable: boolean;
38
+ warning: string | null;
39
+ warnings: string[];
40
+ }
28
41
  export declare function buildCodexLbSetupPlan(answers: CodexLbSetupAnswers, opts?: {
29
42
  home?: string;
30
43
  configPath?: string;
@@ -43,4 +56,10 @@ export declare function installCodexLbShellProfileSnippet(opts: {
43
56
  skipped?: boolean;
44
57
  reason?: string;
45
58
  }>;
59
+ export declare function selectedCodexLbPersistenceModes(answers: Pick<CodexLbSetupAnswers, 'write_env_file' | 'store_keychain' | 'sync_launchctl' | 'install_shell_profile'>): CodexLbPersistenceMode[];
60
+ export declare function codexLbPersistenceSummary({ selectedModes, appliedModes, processOnly }?: {
61
+ selectedModes?: CodexLbPersistenceMode[];
62
+ appliedModes?: CodexLbPersistenceMode[];
63
+ processOnly?: boolean;
64
+ }): CodexLbPersistenceSummary;
46
65
  //# sourceMappingURL=codex-lb-setup.d.ts.map
@@ -35,11 +35,21 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
35
35
  actions.push({ type: 'run_health_check', target: 'codex-lb response chain', effect: 'run codex-lb health check after apply' });
36
36
  }
37
37
  actions.push({ type: 'write_metadata', target: metadataPath, effect: 'write redacted setup metadata and key fingerprint with chmod 0600' });
38
+ const selectedModes = selectedCodexLbPersistenceModes(answers);
39
+ const persistence = codexLbPersistenceSummary({
40
+ selectedModes,
41
+ appliedModes: selectedModes.length ? [] : ['process_only_ephemeral'],
42
+ processOnly: selectedModes.length === 0
43
+ });
38
44
  return {
39
45
  schema: 'sks.codex-lb-setup-plan.v1',
40
46
  base_url: baseUrl,
41
47
  actions,
48
+ expected_actions: actions,
49
+ selected_persistence_modes: selectedModes.length ? selectedModes : ['process_only_ephemeral'],
50
+ persistence,
42
51
  redactions: ['CODEX_LB_API_KEY', 'api_key', 'sk-*', 'sk-clb-*'],
52
+ warnings: persistence.warnings,
43
53
  blockers
44
54
  };
45
55
  }
@@ -51,6 +61,8 @@ export function renderCodexLbSetupPlan(plan) {
51
61
  ];
52
62
  for (const action of plan.actions)
53
63
  lines.push(`- ${action.type}: ${action.target} (${action.effect})`);
64
+ if (plan.persistence.warning)
65
+ lines.push(`warning: ${plan.persistence.warning}`);
54
66
  if (plan.blockers.length) {
55
67
  lines.push('blockers:');
56
68
  for (const blocker of plan.blockers)
@@ -74,6 +86,52 @@ export async function installCodexLbShellProfileSnippet(opts) {
74
86
  }
75
87
  return { ok: true, status: 'installed', files };
76
88
  }
89
+ export function selectedCodexLbPersistenceModes(answers) {
90
+ const modes = [];
91
+ if (answers.write_env_file)
92
+ modes.push('durable_env_file');
93
+ if (answers.store_keychain)
94
+ modes.push('durable_keychain');
95
+ if (answers.sync_launchctl)
96
+ modes.push('durable_launchctl');
97
+ if (answers.install_shell_profile !== 'skip')
98
+ modes.push('shell_profile');
99
+ return modes;
100
+ }
101
+ export function codexLbPersistenceSummary({ selectedModes = [], appliedModes = [], processOnly = false } = {}) {
102
+ const selected = normalizePersistenceModes(selectedModes);
103
+ const applied = normalizePersistenceModes(appliedModes);
104
+ const effective = applied.find((mode) => mode !== 'process_only_ephemeral' && mode !== 'none')
105
+ || selected.find((mode) => mode !== 'process_only_ephemeral' && mode !== 'none')
106
+ || (processOnly || applied.includes('process_only_ephemeral') || selected.length === 0 ? 'process_only_ephemeral' : 'none');
107
+ const durable = ['durable_env_file', 'durable_keychain', 'durable_launchctl', 'shell_profile'].some((mode) => applied.includes(mode) || selected.includes(mode));
108
+ const warnings = effective === 'process_only_ephemeral'
109
+ ? [
110
+ 'process_only_ephemeral',
111
+ 'next_shell_requires_setup_or_env',
112
+ 'Codex App GUI launch may not see credentials'
113
+ ]
114
+ : [];
115
+ return {
116
+ selected_modes: selected.length ? selected : ['process_only_ephemeral'],
117
+ applied_modes: applied.length ? applied : (effective === 'none' ? ['none'] : [effective]),
118
+ effective_mode: effective,
119
+ durable,
120
+ warning: warnings[0] || null,
121
+ warnings
122
+ };
123
+ }
124
+ function normalizePersistenceModes(modes = []) {
125
+ const allowed = new Set([
126
+ 'durable_env_file',
127
+ 'durable_keychain',
128
+ 'durable_launchctl',
129
+ 'shell_profile',
130
+ 'process_only_ephemeral',
131
+ 'none'
132
+ ]);
133
+ return [...new Set(modes.filter((mode) => allowed.has(mode)))];
134
+ }
77
135
  function profileTargets(home, choice) {
78
136
  const targets = {
79
137
  zsh: path.join(home, '.zshrc'),
@@ -1,10 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import { nowIso, projectRoot, writeJsonAtomic } from '../fsx.js';
3
3
  import { createMission, findLatestMission } from '../mission.js';
4
- import { flag } from '../../cli/args.js';
4
+ import { flag, readOption } from '../../cli/args.js';
5
5
  import { printJson } from '../../cli/output.js';
6
6
  import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
7
7
  import { computerUseLiveSmoke, computerUseStatusReport } from '../computer-use-status.js';
8
+ import { computerUseLiveEvidencePath } from '../computer-use-live-evidence.js';
8
9
  export async function computerUseCommand(command, args = []) {
9
10
  const root = await projectRoot();
10
11
  const action = args[0] || 'import';
@@ -21,18 +22,33 @@ export async function computerUseCommand(command, args = []) {
21
22
  return;
22
23
  }
23
24
  if (action === 'smoke') {
24
- const evidencePath = path.join(root, '.sneakoscope', 'reports', 'computer-use-smoke-evidence.json');
25
+ const missionArg = readOption(args, '--mission', null);
26
+ const missionId = missionArg === 'latest' ? await findLatestMission(root) : missionArg;
27
+ const route = readOption(args, '--route', null);
28
+ const evidencePath = computerUseLiveEvidencePath(root, { missionId });
25
29
  const result = await computerUseLiveSmoke({
30
+ root,
26
31
  real: flag(args, '--real'),
27
32
  requireReal: flag(args, '--require-real'),
33
+ captureScreenshot: flag(args, '--capture-screenshot'),
28
34
  allowAction: flag(args, '--allow-action'),
35
+ route,
36
+ missionId,
29
37
  evidencePath
30
38
  });
31
39
  if (flag(args, '--json')) {
32
40
  printJson(result);
33
41
  }
34
42
  else {
35
- console.log(`Computer Use smoke: ${result.status}`);
43
+ console.log(`Computer Use smoke: ${result.status} (${result.evidence_mode})`);
44
+ if (result.live_evidence_path)
45
+ console.log(`Live evidence: ${result.live_evidence_path}`);
46
+ if (result.blockers?.length)
47
+ for (const blocker of result.blockers)
48
+ console.log(`- blocker: ${blocker}`);
49
+ if (result.warnings?.length)
50
+ for (const warning of result.warnings)
51
+ console.log(`- warning: ${warning}`);
36
52
  if (result.guidance?.length)
37
53
  for (const line of result.guidance)
38
54
  console.log(`- ${line}`);
@@ -0,0 +1,109 @@
1
+ import type { ComputerUseStatus } from './computer-use-status.js';
2
+ export type ComputerUseEvidenceMode = 'probe_only' | 'live_capture_attempted' | 'live_capture_success' | 'live_capture_blocked';
3
+ export type ComputerUseCaptureStatus = 'not_attempted' | 'captured' | 'blocked' | 'failed' | 'redacted' | 'local_only';
4
+ export interface ComputerUseLiveEvidence {
5
+ schema: 'sks.computer-use-live-evidence.v1';
6
+ generated_at: string;
7
+ route: string | null;
8
+ mission_id: string | null;
9
+ mode: ComputerUseEvidenceMode;
10
+ status: ComputerUseStatus;
11
+ platform: string;
12
+ mock: false;
13
+ capability: {
14
+ codex_app_installed: boolean;
15
+ capability_detected: boolean;
16
+ external_capability_blocked: boolean;
17
+ blocker: ComputerUseStatus | null;
18
+ };
19
+ capture: {
20
+ screenshot: {
21
+ attempted: boolean;
22
+ status: ComputerUseCaptureStatus;
23
+ path: string | null;
24
+ sha256: string | null;
25
+ redacted: boolean;
26
+ local_only: boolean;
27
+ };
28
+ action: {
29
+ attempted: boolean;
30
+ status: ComputerUseCaptureStatus;
31
+ actions: unknown[];
32
+ redacted: boolean;
33
+ local_only: boolean;
34
+ };
35
+ };
36
+ image_voxel: {
37
+ linked: boolean;
38
+ ledger_path: string | null;
39
+ anchor_ids: string[];
40
+ reason: string | null;
41
+ };
42
+ privacy: {
43
+ shared_triwiki_publish_allowed: false;
44
+ contains_screen_content: boolean;
45
+ redaction_required: boolean;
46
+ };
47
+ blockers: string[];
48
+ warnings: string[];
49
+ }
50
+ export interface ComputerUseScreenshotCaptureAdapter {
51
+ captureScreenshot: (opts: {
52
+ root: string;
53
+ route: string | null;
54
+ missionId: string | null;
55
+ outputPath: string;
56
+ }) => Promise<{
57
+ ok: boolean;
58
+ path?: string | null;
59
+ data?: Buffer | Uint8Array | string | null;
60
+ blocker?: string | null;
61
+ warning?: string | null;
62
+ redacted?: boolean;
63
+ localOnly?: boolean;
64
+ }>;
65
+ }
66
+ export interface CodexAppComputerUseCapabilityAdapter {
67
+ detect: () => Promise<{
68
+ codex_app_installed: boolean;
69
+ capability_detected: boolean;
70
+ external_capability_blocked?: boolean;
71
+ blocker?: ComputerUseStatus | null;
72
+ }>;
73
+ }
74
+ export interface BuildComputerUseLiveEvidenceOptions {
75
+ root?: string;
76
+ route?: string | null;
77
+ missionId?: string | null;
78
+ statusReport: Record<string, any>;
79
+ realOptIn?: boolean;
80
+ captureScreenshot?: boolean;
81
+ allowAction?: boolean;
82
+ screenshotAdapter?: ComputerUseScreenshotCaptureAdapter | null;
83
+ capabilityAdapter?: CodexAppComputerUseCapabilityAdapter | null;
84
+ evidencePath?: string | null;
85
+ }
86
+ export declare function computerUseLiveEvidencePath(root?: string, opts?: {
87
+ missionId?: string | null;
88
+ }): string;
89
+ export declare function readComputerUseLiveEvidence(root?: string, opts?: {
90
+ missionId?: string | null;
91
+ path?: string | null;
92
+ }): Promise<{
93
+ ok: boolean;
94
+ path: string;
95
+ evidence: ComputerUseLiveEvidence;
96
+ } | {
97
+ ok: boolean;
98
+ path: null;
99
+ evidence: null;
100
+ }>;
101
+ export declare function writeComputerUseLiveEvidence(file: string, evidence: ComputerUseLiveEvidence): Promise<{
102
+ ok: boolean;
103
+ path: string;
104
+ evidence: ComputerUseLiveEvidence;
105
+ }>;
106
+ export declare function buildComputerUseLiveEvidence(opts: BuildComputerUseLiveEvidenceOptions): Promise<ComputerUseLiveEvidence>;
107
+ export declare function isComputerUseLiveEvidence(value: unknown): value is ComputerUseLiveEvidence;
108
+ export declare function isComputerUseEvidenceMode(value: unknown): value is ComputerUseEvidenceMode;
109
+ //# sourceMappingURL=computer-use-live-evidence.d.ts.map
@@ -0,0 +1,276 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, exists, nowIso, packageRoot, readJson, rel, sha256, writeJsonAtomic } from './fsx.js';
4
+ import { addVisualAnchor, ingestImage, missionImageLedgerPath } from './wiki-image/image-voxel-ledger.js';
5
+ export function computerUseLiveEvidencePath(root = packageRoot(), opts = {}) {
6
+ if (opts.missionId) {
7
+ return path.join(root, '.sneakoscope', 'missions', opts.missionId, 'computer-use-live-evidence.json');
8
+ }
9
+ return path.join(root, '.sneakoscope', 'reports', 'computer-use-live-evidence.json');
10
+ }
11
+ export async function readComputerUseLiveEvidence(root = packageRoot(), opts = {}) {
12
+ const candidates = [
13
+ opts.path || null,
14
+ opts.missionId ? computerUseLiveEvidencePath(root, { missionId: opts.missionId }) : null,
15
+ computerUseLiveEvidencePath(root)
16
+ ].filter(Boolean);
17
+ for (const file of candidates) {
18
+ const parsed = await readJson(file, null).catch(() => null);
19
+ if (isComputerUseLiveEvidence(parsed))
20
+ return { ok: true, path: file, evidence: parsed };
21
+ }
22
+ return { ok: false, path: null, evidence: null };
23
+ }
24
+ export async function writeComputerUseLiveEvidence(file, evidence) {
25
+ await ensureDir(path.dirname(file));
26
+ await writeJsonAtomic(file, evidence);
27
+ return { ok: true, path: file, evidence };
28
+ }
29
+ export async function buildComputerUseLiveEvidence(opts) {
30
+ const root = opts.root || packageRoot();
31
+ const status = normalizeComputerUseStatus(opts.statusReport?.status);
32
+ const realOptIn = opts.realOptIn === true;
33
+ const route = opts.route || null;
34
+ const missionId = opts.missionId || null;
35
+ const warnings = [];
36
+ const blockers = [];
37
+ const capability = await detectCapability(opts);
38
+ const platform = String(opts.statusReport?.platform || process.platform);
39
+ let mode = realOptIn && status !== 'not_macos' ? 'live_capture_attempted' : 'probe_only';
40
+ const evidence = {
41
+ schema: 'sks.computer-use-live-evidence.v1',
42
+ generated_at: nowIso(),
43
+ route,
44
+ mission_id: missionId,
45
+ mode,
46
+ status,
47
+ platform,
48
+ mock: false,
49
+ capability,
50
+ capture: {
51
+ screenshot: {
52
+ attempted: false,
53
+ status: 'not_attempted',
54
+ path: null,
55
+ sha256: null,
56
+ redacted: false,
57
+ local_only: true
58
+ },
59
+ action: {
60
+ attempted: false,
61
+ status: 'not_attempted',
62
+ actions: [],
63
+ redacted: false,
64
+ local_only: true
65
+ }
66
+ },
67
+ image_voxel: {
68
+ linked: false,
69
+ ledger_path: missionId ? rel(root, missionImageLedgerPath(root, missionId)) : null,
70
+ anchor_ids: [],
71
+ reason: null
72
+ },
73
+ privacy: {
74
+ shared_triwiki_publish_allowed: false,
75
+ contains_screen_content: false,
76
+ redaction_required: false
77
+ },
78
+ blockers,
79
+ warnings
80
+ };
81
+ if (!realOptIn) {
82
+ evidence.image_voxel.reason = 'probe_only_no_live_capture_attempted';
83
+ return evidence;
84
+ }
85
+ if (status === 'not_macos') {
86
+ blockers.push('not_macos');
87
+ evidence.mode = 'probe_only';
88
+ evidence.image_voxel.reason = 'not_macos';
89
+ return evidence;
90
+ }
91
+ if (status !== 'available') {
92
+ blockers.push(status);
93
+ evidence.mode = 'live_capture_blocked';
94
+ evidence.capture.screenshot.status = opts.captureScreenshot ? 'blocked' : 'not_attempted';
95
+ evidence.capture.screenshot.attempted = opts.captureScreenshot === true;
96
+ evidence.image_voxel.reason = status;
97
+ return evidence;
98
+ }
99
+ if (opts.captureScreenshot) {
100
+ await attemptScreenshotCapture(root, evidence, opts);
101
+ }
102
+ else {
103
+ evidence.image_voxel.reason = 'capture_screenshot_not_requested';
104
+ }
105
+ if (opts.allowAction) {
106
+ evidence.capture.action.attempted = true;
107
+ evidence.capture.action.status = 'blocked';
108
+ blockers.push('computer_use_action_adapter_missing');
109
+ warnings.push('Computer Use smoke never runs click/type actions without an official non-destructive Codex App adapter.');
110
+ }
111
+ if (evidence.capture.screenshot.status === 'captured') {
112
+ evidence.mode = 'live_capture_success';
113
+ evidence.privacy.contains_screen_content = true;
114
+ }
115
+ else if (blockers.length) {
116
+ evidence.mode = 'live_capture_blocked';
117
+ }
118
+ return evidence;
119
+ }
120
+ export function isComputerUseLiveEvidence(value) {
121
+ if (!value || typeof value !== 'object')
122
+ return false;
123
+ const evidence = value;
124
+ return evidence.schema === 'sks.computer-use-live-evidence.v1'
125
+ && isComputerUseEvidenceMode(evidence.mode)
126
+ && evidence.mock === false
127
+ && typeof evidence.capture === 'object'
128
+ && typeof evidence.privacy === 'object';
129
+ }
130
+ export function isComputerUseEvidenceMode(value) {
131
+ return value === 'probe_only'
132
+ || value === 'live_capture_attempted'
133
+ || value === 'live_capture_success'
134
+ || value === 'live_capture_blocked';
135
+ }
136
+ async function detectCapability(opts) {
137
+ if (opts.capabilityAdapter) {
138
+ const detected = await opts.capabilityAdapter.detect();
139
+ return {
140
+ codex_app_installed: Boolean(detected.codex_app_installed),
141
+ capability_detected: Boolean(detected.capability_detected),
142
+ external_capability_blocked: detected.external_capability_blocked === true,
143
+ blocker: detected.blocker || null
144
+ };
145
+ }
146
+ const status = normalizeComputerUseStatus(opts.statusReport?.status);
147
+ const installed = Boolean(opts.statusReport?.app?.app?.installed);
148
+ const available = status === 'available';
149
+ return {
150
+ codex_app_installed: installed,
151
+ capability_detected: available,
152
+ external_capability_blocked: status !== 'available',
153
+ blocker: available ? null : status
154
+ };
155
+ }
156
+ async function attemptScreenshotCapture(root, evidence, opts) {
157
+ evidence.capture.screenshot.attempted = true;
158
+ const outputPath = screenshotArtifactPath(root, opts);
159
+ const adapter = opts.screenshotAdapter || null;
160
+ if (!adapter) {
161
+ evidence.capture.screenshot.status = 'blocked';
162
+ evidence.blockers.push('codex_app_capability_missing');
163
+ evidence.warnings.push('Official Codex App Computer Use screenshot adapter is not exposed to this CLI process.');
164
+ evidence.image_voxel.reason = 'computer_use_capture_adapter_missing';
165
+ return;
166
+ }
167
+ try {
168
+ const result = await adapter.captureScreenshot({
169
+ root,
170
+ route: opts.route || null,
171
+ missionId: opts.missionId || null,
172
+ outputPath
173
+ });
174
+ if (!result.ok) {
175
+ evidence.capture.screenshot.status = 'blocked';
176
+ evidence.blockers.push(redactCaptureMessage(result.blocker || 'external_capability_blocked'));
177
+ if (result.warning)
178
+ evidence.warnings.push(redactCaptureMessage(result.warning));
179
+ evidence.image_voxel.reason = result.blocker || 'screenshot_capture_blocked';
180
+ return;
181
+ }
182
+ const capturedPath = await materializeScreenshot(result, outputPath);
183
+ const data = await fsp.readFile(capturedPath);
184
+ evidence.capture.screenshot.status = result.localOnly === false ? 'captured' : 'captured';
185
+ evidence.capture.screenshot.path = rel(root, capturedPath);
186
+ evidence.capture.screenshot.sha256 = sha256(data);
187
+ evidence.capture.screenshot.redacted = result.redacted === true;
188
+ evidence.capture.screenshot.local_only = result.localOnly !== false;
189
+ evidence.privacy.redaction_required = result.redacted === true;
190
+ await linkScreenshotToImageVoxel(root, evidence, capturedPath, opts);
191
+ }
192
+ catch (err) {
193
+ evidence.capture.screenshot.status = 'failed';
194
+ evidence.blockers.push('screenshot_capture_failed');
195
+ evidence.image_voxel.reason = redactCaptureMessage(err instanceof Error ? err.message : String(err));
196
+ }
197
+ }
198
+ async function materializeScreenshot(result, outputPath) {
199
+ if (result.path && await exists(result.path))
200
+ return path.resolve(result.path);
201
+ if (result.data === undefined || result.data === null)
202
+ throw new Error('screenshot_capture_returned_no_path_or_data');
203
+ await ensureDir(path.dirname(outputPath));
204
+ const bytes = typeof result.data === 'string' ? Buffer.from(result.data, 'base64') : Buffer.from(result.data);
205
+ await fsp.writeFile(outputPath, bytes);
206
+ return outputPath;
207
+ }
208
+ async function linkScreenshotToImageVoxel(root, evidence, screenshotPath, opts) {
209
+ if (!opts.missionId) {
210
+ evidence.image_voxel.reason = 'mission_id_missing';
211
+ return;
212
+ }
213
+ if (!evidence.capture.screenshot.sha256) {
214
+ evidence.image_voxel.reason = 'screenshot_sha256_missing';
215
+ return;
216
+ }
217
+ const relative = rel(root, screenshotPath);
218
+ if (relative.startsWith('..')) {
219
+ evidence.image_voxel.reason = 'screenshot_path_outside_project';
220
+ return;
221
+ }
222
+ try {
223
+ const imageId = evidence.capture.screenshot.sha256;
224
+ const ingested = await ingestImage(root, relative, {
225
+ missionId: opts.missionId,
226
+ source: 'computer_use_live_screenshot',
227
+ id: imageId,
228
+ capturedAt: evidence.generated_at
229
+ });
230
+ const image = ingested.image;
231
+ const anchorId = `computer-use-${imageId.slice(0, 16)}-screen`;
232
+ const anchor = await addVisualAnchor(root, {
233
+ id: anchorId,
234
+ missionId: opts.missionId,
235
+ imageId,
236
+ bbox: [0, 0, image.width || 1, image.height || 1],
237
+ label: 'Computer Use live screenshot',
238
+ source: 'computer_use_live_screenshot',
239
+ evidencePath: relative,
240
+ route: opts.route || '$Computer-Use',
241
+ trustScore: 0.86
242
+ });
243
+ evidence.image_voxel.linked = Boolean(anchor.ok);
244
+ evidence.image_voxel.ledger_path = rel(root, missionImageLedgerPath(root, opts.missionId));
245
+ evidence.image_voxel.anchor_ids = anchor.ok ? [anchorId] : [];
246
+ evidence.image_voxel.reason = anchor.ok ? null : (anchor.validation?.issues || ['image_voxel_anchor_failed']).join(',');
247
+ }
248
+ catch (err) {
249
+ evidence.image_voxel.reason = redactCaptureMessage(err instanceof Error ? err.message : String(err));
250
+ }
251
+ }
252
+ function screenshotArtifactPath(root, opts) {
253
+ if (opts.missionId) {
254
+ return path.join(root, '.sneakoscope', 'missions', opts.missionId, 'computer-use-live-screenshot.png');
255
+ }
256
+ return path.join(root, '.sneakoscope', 'reports', 'computer-use-live-screenshot.png');
257
+ }
258
+ function normalizeComputerUseStatus(value) {
259
+ const allowed = [
260
+ 'available',
261
+ 'codex_app_missing',
262
+ 'macos_permission_missing',
263
+ 'codex_app_capability_missing',
264
+ 'external_capability_blocked',
265
+ 'not_macos',
266
+ 'unknown'
267
+ ];
268
+ return allowed.includes(value) ? value : 'unknown';
269
+ }
270
+ function redactCaptureMessage(value) {
271
+ return String(value || 'unknown')
272
+ .replace(/sk-[A-Za-z0-9_-]+/g, '[redacted]')
273
+ .replace(/CODEX_LB_API_KEY=([^\s]+)/g, 'CODEX_LB_API_KEY=[redacted]')
274
+ .slice(0, 500);
275
+ }
276
+ //# sourceMappingURL=computer-use-live-evidence.js.map
@@ -1,3 +1,4 @@
1
+ export type { ComputerUseCaptureStatus, ComputerUseEvidenceMode, ComputerUseLiveEvidence } from './computer-use-live-evidence.js';
1
2
  export type ComputerUseStatus = 'available' | 'codex_app_missing' | 'macos_permission_missing' | 'codex_app_capability_missing' | 'external_capability_blocked' | 'not_macos' | 'unknown';
2
3
  export declare function computerUseStatusReport(opts?: any): Promise<any>;
3
4
  export declare function computerUseEvidenceSkeleton(status?: ComputerUseStatus): {
@@ -11,7 +12,8 @@ export declare function computerUseEvidenceSkeleton(status?: ComputerUseStatus):
11
12
  export declare function computerUseLiveSmoke(opts?: any): Promise<{
12
13
  schema: string;
13
14
  ok: boolean;
14
- mode: string;
15
+ mode: import("./computer-use-live-evidence.js").ComputerUseEvidenceMode;
16
+ evidence_mode: import("./computer-use-live-evidence.js").ComputerUseEvidenceMode;
15
17
  status: any;
16
18
  platform: any;
17
19
  codex_app: {
@@ -22,12 +24,17 @@ export declare function computerUseLiveSmoke(opts?: any): Promise<{
22
24
  screen_recording: any;
23
25
  accessibility: string;
24
26
  };
27
+ live_evidence_path: any;
28
+ image_voxel_linked: boolean;
29
+ blockers: string[];
30
+ warnings: string[];
25
31
  evidence: {
26
32
  screenshot_captured: boolean;
27
33
  action_captured: boolean;
28
34
  image_voxel_linked: boolean;
29
35
  evidence_path: any;
30
36
  };
37
+ live_evidence: import("./computer-use-live-evidence.js").ComputerUseLiveEvidence | null;
31
38
  external_capability_blocked: boolean;
32
39
  mock: boolean;
33
40
  guidance: any;
@@ -1,4 +1,5 @@
1
1
  import { codexAppIntegrationStatus } from './codex-app.js';
2
+ import { buildComputerUseLiveEvidence, computerUseLiveEvidencePath, writeComputerUseLiveEvidence } from './computer-use-live-evidence.js';
2
3
  export async function computerUseStatusReport(opts = {}) {
3
4
  if (process.platform !== 'darwin' && !opts.forceMacos) {
4
5
  return baseReport('not_macos', {
@@ -54,47 +55,65 @@ export function computerUseEvidenceSkeleton(status = 'unknown') {
54
55
  export async function computerUseLiveSmoke(opts = {}) {
55
56
  const realOptIn = opts.real === true || process.env.SKS_TEST_REAL_COMPUTER_USE === '1';
56
57
  const requireReal = opts.requireReal === true;
58
+ const captureScreenshot = opts.captureScreenshot === true || requireReal;
57
59
  const status = await computerUseStatusReport(opts);
58
- const available = status.status === 'available';
59
- const realAllowed = realOptIn && available;
60
- const evidencePath = opts.evidencePath || null;
60
+ const root = opts.root || process.cwd();
61
+ const evidencePath = opts.evidencePath || (realOptIn ? computerUseLiveEvidencePath(root, { missionId: opts.missionId || null }) : null);
62
+ const liveEvidence = realOptIn
63
+ ? await buildComputerUseLiveEvidence({
64
+ root,
65
+ route: opts.route || null,
66
+ missionId: opts.missionId || null,
67
+ statusReport: status,
68
+ realOptIn,
69
+ captureScreenshot,
70
+ allowAction: opts.allowAction === true,
71
+ screenshotAdapter: opts.screenshotAdapter || null,
72
+ capabilityAdapter: opts.capabilityAdapter || null,
73
+ evidencePath
74
+ })
75
+ : null;
76
+ if (liveEvidence && evidencePath)
77
+ await writeComputerUseLiveEvidence(evidencePath, liveEvidence);
78
+ const evidenceMode = liveEvidence?.mode || 'probe_only';
79
+ const liveCaptureSuccess = evidenceMode === 'live_capture_success';
80
+ const blockers = [...(liveEvidence?.blockers || [])];
81
+ if (!realOptIn && status.status === 'not_macos')
82
+ blockers.push('not_macos');
83
+ const warnings = [...(liveEvidence?.warnings || [])];
84
+ const ok = requireReal ? liveCaptureSuccess : true;
61
85
  const smoke = {
62
- schema: 'sks.computer-use-live-smoke.v1',
63
- ok: realAllowed || !requireReal,
64
- mode: realOptIn ? 'optional_real' : 'optional_probe',
86
+ schema: 'sks.computer-use-live-smoke.v2',
87
+ ok,
88
+ mode: evidenceMode,
89
+ evidence_mode: evidenceMode,
65
90
  status: status.status,
66
91
  platform: status.platform || process.platform,
67
92
  codex_app: {
68
93
  installed: Boolean(status.app?.app?.installed),
69
- capability_detected: available
94
+ capability_detected: status.status === 'available'
70
95
  },
71
96
  permission: {
72
97
  screen_recording: status.permission_status || 'unknown',
73
98
  accessibility: 'unknown'
74
99
  },
100
+ live_evidence_path: liveEvidence && evidencePath ? evidencePath : null,
101
+ image_voxel_linked: liveEvidence?.image_voxel?.linked === true,
102
+ blockers,
103
+ warnings,
75
104
  evidence: {
76
- screenshot_captured: false,
77
- action_captured: false,
78
- image_voxel_linked: false,
79
- evidence_path: evidencePath
105
+ screenshot_captured: liveEvidence?.capture?.screenshot?.status === 'captured',
106
+ action_captured: liveEvidence?.capture?.action?.status === 'captured',
107
+ image_voxel_linked: liveEvidence?.image_voxel?.linked === true,
108
+ evidence_path: liveEvidence && evidencePath ? evidencePath : null
80
109
  },
110
+ live_evidence: liveEvidence,
81
111
  external_capability_blocked: status.external_capability_blocked === true || ['codex_app_missing', 'macos_permission_missing', 'codex_app_capability_missing', 'unknown'].includes(status.status),
82
112
  mock: false,
83
- guidance: realAllowed
84
- ? ['Computer Use capability appears available. Capture screenshots through official Codex Computer Use in the active route; SKS smoke does not synthesize evidence.']
113
+ guidance: status.status === 'available' && realOptIn
114
+ ? ['Computer Use capability appears available. SKS records only official, local-only live evidence and does not synthesize screenshots.']
85
115
  : status.guidance || []
86
116
  };
87
- if (available && realOptIn && evidencePath) {
88
- const { writeJsonAtomic } = await import('./fsx.js');
89
- await writeJsonAtomic(evidencePath, smoke);
90
- return {
91
- ...smoke,
92
- evidence: {
93
- ...smoke.evidence,
94
- evidence_path: evidencePath
95
- }
96
- };
97
- }
98
117
  return smoke;
99
118
  }
100
119
  function baseReport(status, extra = {}) {
@@ -51,6 +51,7 @@ export async function evidenceCandidatesForProof(root, proof = {}, opts = {}) {
51
51
  addDbCandidates(candidates, proof.evidence?.db || proof.evidence?.db_safety, missionId);
52
52
  addTestCandidates(candidates, proof.evidence?.tests, missionId);
53
53
  addWrongnessCandidates(candidates, proof.evidence?.wrongness, missionId);
54
+ addComputerUseCandidates(candidates, proof.evidence?.computer_use, missionId);
54
55
  if (missionId && routeRequiresImageVoxelAnchors(route)) {
55
56
  candidates.push({ kind: 'image_voxel', relPath: `.sneakoscope/missions/${missionId}/image-voxel-ledger.json`, source: proof.evidence?.image_voxels?.mock ? 'mock' : sourceForProof(proof) });
56
57
  }
@@ -140,6 +141,13 @@ function addWrongnessCandidates(candidates, wrongness = null, missionId = null)
140
141
  if (missionId)
141
142
  candidates.push({ kind: 'image_wrongness', relPath: `.sneakoscope/missions/${missionId}/image-wrongness-ledger.json`, source: 'real', optional: true, ignoreStale: true });
142
143
  }
144
+ function addComputerUseCandidates(candidates, computerUse = null, missionId = null) {
145
+ const relPath = computerUse?.live_evidence_path || computerUse?.live_evidence?.path || null;
146
+ if (relPath)
147
+ candidates.push({ kind: 'computer_use', relPath: normalizeRelPath(relPath, missionId), source: 'real', ignoreStale: true });
148
+ else if (missionId)
149
+ candidates.push({ kind: 'computer_use', relPath: `.sneakoscope/missions/${missionId}/computer-use-live-evidence.json`, source: 'blocked', optional: true, ignoreStale: true });
150
+ }
143
151
  function uniqueCandidates(candidates) {
144
152
  const seen = new Set();
145
153
  const out = [];
@@ -200,6 +208,8 @@ function inferKind(relPath = '') {
200
208
  return 'wrongness';
201
209
  if (/image-voxel-ledger\.json$|visual-anchors\.json$|image-assets\.json$/.test(relPath))
202
210
  return 'image_voxel';
211
+ if (/computer-use-live-evidence\.json$|computer-use.*evidence/.test(relPath))
212
+ return 'computer_use';
203
213
  if (/scout/.test(relPath))
204
214
  return 'scout';
205
215
  if (/db/.test(relPath))
@@ -1,4 +1,4 @@
1
- export declare const PACKAGE_VERSION = "1.0.6";
1
+ export declare const PACKAGE_VERSION = "1.0.7";
2
2
  export declare const DEFAULT_PROCESS_TAIL_BYTES: number;
3
3
  export declare const DEFAULT_PROCESS_TIMEOUT_MS: number;
4
4
  export interface RunProcessOptions {
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '1.0.6';
8
+ export const PACKAGE_VERSION = '1.0.7';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -5,6 +5,7 @@ import { ensureRouteImageEvidence } from '../wiki-image/route-image-evidence.js'
5
5
  import { readScoutProofEvidence } from '../scouts/scout-proof-evidence.js';
6
6
  import { wrongnessProofEvidence } from '../triwiki-wrongness/wrongness-proof-linker.js';
7
7
  import { computerUseStatusReport } from '../computer-use-status.js';
8
+ import { readComputerUseLiveEvidence } from '../computer-use-live-evidence.js';
8
9
  export async function finalizeRouteWithProof(root, { missionId, route, gateFile = null, gate = null, artifacts = [], visualEvidence = null, dbEvidence = null, testEvidence = null, commandEvidence = null, claims = [], unverified = [], blockers = [], statusHint = 'verified_partial', strict = false, mock = false, fixClaim = false, requireRelation = false, visualClaim = undefined } = {}) {
9
10
  const policy = routeFinalizerPolicy(route, { strict, fixClaim, requireRelation, visualClaim });
10
11
  const localBlockers = [...blockers];
@@ -27,13 +28,33 @@ export async function finalizeRouteWithProof(root, { missionId, route, gateFile
27
28
  const computerUse = policy.requires_image_voxel_anchors
28
29
  ? await computerUseStatusReport().catch((err) => ({ schema: 'sks.computer-use-status.v1', status: 'unknown', ok: false, guidance: [err.message], evidence: { status: 'unknown' } }))
29
30
  : null;
31
+ const computerUseLive = policy.requires_image_voxel_anchors
32
+ ? await readComputerUseLiveEvidence(root, { missionId }).catch(() => ({ ok: false, path: null, evidence: null }))
33
+ : null;
30
34
  if (computerUse && computerUse.status !== 'available') {
31
35
  unverified.push(`Computer Use evidence unavailable: ${computerUse.status}. Visual claim remains verified_partial unless explicit screenshot/image evidence covers it.`);
32
36
  }
37
+ if (computerUse && !computerUseLive?.evidence) {
38
+ unverified.push('Computer Use live evidence is missing for this visual route; high-confidence visual claims require live evidence or explicit screenshot/image coverage.');
39
+ if (strict)
40
+ localBlockers.push('computer_use_live_evidence_missing');
41
+ }
42
+ if (computerUseLive?.evidence?.mode === 'probe_only') {
43
+ unverified.push('Computer Use evidence mode is probe_only; visual claim confidence is capped below high confidence.');
44
+ }
45
+ if (computerUseLive?.evidence?.mode === 'live_capture_blocked') {
46
+ unverified.push(`Computer Use live capture blocked: ${(computerUseLive.evidence.blockers || []).join(',') || computerUseLive.evidence.status}.`);
47
+ }
48
+ if (computerUseLive?.evidence && computerUseLive.evidence.image_voxel?.linked !== true && statusHint === 'verified') {
49
+ unverified.push(`Computer Use live evidence is not linked to Image Voxel: ${computerUseLive.evidence.image_voxel?.reason || 'missing_relation'}.`);
50
+ }
33
51
  if (Number(wrongnessEvidence?.high_severity_active || 0) > 0) {
34
52
  localBlockers.push('active_high_severity_wrongness');
35
53
  }
36
- const visualComputerUseDowngrade = Boolean(computerUse && computerUse.status !== 'available' && statusHint === 'verified');
54
+ const visualComputerUseDowngrade = Boolean(statusHint === 'verified' && ((computerUse && computerUse.status !== 'available')
55
+ || !computerUseLive?.evidence
56
+ || computerUseLive.evidence.mode !== 'live_capture_success'
57
+ || computerUseLive.evidence.image_voxel?.linked !== true));
37
58
  const status = localBlockers.length
38
59
  ? (strict ? 'blocked' : statusHint === 'verified' ? 'verified_partial' : statusHint)
39
60
  : visualComputerUseDowngrade ? 'verified_partial'
@@ -61,7 +82,11 @@ export async function finalizeRouteWithProof(root, { missionId, route, gateFile
61
82
  ok: Boolean(computerUse.ok),
62
83
  mad_sks_independent: computerUse.mad_sks_independent === true,
63
84
  external_capability_blocked: computerUse.external_capability_blocked === true,
64
- evidence: computerUse.evidence || null
85
+ evidence_mode: computerUseLive?.evidence?.mode || 'missing',
86
+ live_evidence_path: computerUseLive?.path || null,
87
+ image_voxel_linked: computerUseLive?.evidence?.image_voxel?.linked === true,
88
+ evidence: computerUse.evidence || null,
89
+ live_evidence: computerUseLive?.evidence || null
65
90
  } } : {}),
66
91
  route_gate: gate || (gateFile ? { source: gateFile } : null)
67
92
  };
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "1.0.6";
1
+ export declare const PACKAGE_VERSION = "1.0.7";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '1.0.6';
1
+ export const PACKAGE_VERSION = '1.0.7';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "1.0.6",
4
+ "version": "1.0.7",
5
5
  "description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -101,12 +101,16 @@
101
101
  "hooks:strict-subset-check": "node ./scripts/codex-hook-strict-subset-check.mjs",
102
102
  "codex-lb:setup-fixture": "node ./scripts/codex-lb-setup-fixture-check.mjs",
103
103
  "codex-lb:setup-truthfulness": "node ./scripts/codex-lb-setup-truthfulness-check.mjs",
104
+ "codex-lb:persistence-truth": "node ./scripts/codex-lb-persistence-truth-check.mjs",
104
105
  "codex-lb:missing-env-regression": "node ./scripts/codex-lb-missing-env-regression.mjs",
105
106
  "computer-use:policy-check": "node ./scripts/computer-use-policy-check.mjs",
106
107
  "computer-use:visual-route-fixture": "node ./scripts/computer-use-visual-route-fixture-check.mjs",
107
108
  "computer-use:live-optional": "node ./scripts/computer-use-live-optional-check.mjs",
109
+ "computer-use:live-evidence": "node ./scripts/computer-use-live-evidence-check.mjs",
110
+ "docs:truthfulness": "node ./scripts/docs-truthfulness-check.mjs",
111
+ "release:readiness": "node ./scripts/release-readiness-report.mjs",
108
112
  "coverage": "node --experimental-test-coverage --test \"test/**/*.test.mjs\"",
109
- "release:check": "npm run typecheck:suppressions && npm run build && npm run typecheck && npm run typecheck:contracts && npm run test:types && npm run schema:check && npm run dist:check && npm run codex:compat && npm run hooks:codex-validate && npm run hooks:warning-check && npm run hooks:semantic-check && npm run hooks:strict-subset-check && npm run codex-lb:setup-fixture && npm run codex-lb:setup-truthfulness && npm run codex-lb:missing-env-regression && npm run computer-use:policy-check && npm run computer-use:visual-route-fixture && npm run computer-use:live-optional && npm run package-boundary:check && npm run blackbox:command-import-smoke && npm run test:blackbox && npm run repo-audit && npm run changelog:check && npm run cli-entrypoint:check && npm run legacy-free:check && npm run architecture:check && npm run route-modularity:check && npm run command-budget:check && npm run pipeline-budget:check && npm run pipeline-runtime:check && npm run packcheck && npm run feature:check && npm run feature-quality:check && npm run all-features:selftest && npm run scout-engines:check && npm run scouts:parser-check && npm run scouts:selftest && npm run scouts:check && npm run trust:check && npm run wrongness:fixtures && npm run wrongness:check && npm run git-hygiene:check && npm run git-precommit:fixture && npm run shared-memory:check && npm run git-collaboration:e2e && npm run evidence:check && npm run safety:check && npm run chaos:check && npm run bench:core && npm run feature-fixtures:strict && npm run selftest && npm run test:unit && npm run test:integration:mock && npm run test:e2e:mock && npm run rust:check && npm run rust:smoke && npm run perf:gate && npm run blackbox:matrix && npm run blackbox:check && npm run sizecheck && npm run registry:check && npm run typescript:migration-report && node ./scripts/release-check-stamp.mjs write",
113
+ "release:check": "npm run typecheck:suppressions && npm run build && npm run typecheck && npm run typecheck:contracts && npm run test:types && npm run schema:check && npm run dist:check && npm run codex:compat && npm run hooks:codex-validate && npm run hooks:warning-check && npm run hooks:semantic-check && npm run hooks:strict-subset-check && npm run codex-lb:setup-fixture && npm run codex-lb:setup-truthfulness && npm run codex-lb:persistence-truth && npm run codex-lb:missing-env-regression && npm run computer-use:policy-check && npm run computer-use:visual-route-fixture && npm run computer-use:live-optional && npm run computer-use:live-evidence && npm run docs:truthfulness && npm run release:readiness && npm run package-boundary:check && npm run blackbox:command-import-smoke && npm run test:blackbox && npm run repo-audit && npm run changelog:check && npm run cli-entrypoint:check && npm run legacy-free:check && npm run architecture:check && npm run route-modularity:check && npm run command-budget:check && npm run pipeline-budget:check && npm run pipeline-runtime:check && npm run packcheck && npm run feature:check && npm run feature-quality:check && npm run all-features:selftest && npm run scout-engines:check && npm run scouts:parser-check && npm run scouts:selftest && npm run scouts:check && npm run trust:check && npm run wrongness:fixtures && npm run wrongness:check && npm run git-hygiene:check && npm run git-precommit:fixture && npm run shared-memory:check && npm run git-collaboration:e2e && npm run evidence:check && npm run safety:check && npm run chaos:check && npm run bench:core && npm run feature-fixtures:strict && npm run selftest && npm run test:unit && npm run test:integration:mock && npm run test:e2e:mock && npm run rust:check && npm run rust:smoke && npm run perf:gate && npm run blackbox:matrix && npm run blackbox:check && npm run sizecheck && npm run registry:check && npm run typescript:migration-report && node ./scripts/release-check-stamp.mjs write && npm run release:readiness",
110
114
  "release:publish": "npm run publish:npm",
111
115
  "publish:dry": "npm --cache /tmp/sks-npm-cache publish --dry-run --registry https://registry.npmjs.org/ --access public",
112
116
  "publish:npm": "npm --cache /tmp/sks-npm-cache publish --registry https://registry.npmjs.org/ --access public",