sneakoscope 4.1.0 → 4.2.0
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 +16 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/router.js +6 -1
- package/dist/commands/doctor.js +272 -127
- package/dist/core/auto-review.js +1 -1
- package/dist/core/codex/agent-config-file-repair.js +43 -2
- package/dist/core/codex-app/codex-agent-role-sync.js +4 -4
- package/dist/core/codex-control/codex-0142-capability.js +51 -6
- package/dist/core/codex-control/codex-app-server-v2-client.js +2 -2
- package/dist/core/codex-native/codex-native-feature-broker.js +50 -0
- package/dist/core/codex-native/native-capability-postcheck.js +59 -16
- package/dist/core/codex-native/native-capability-repair-matrix.js +77 -13
- package/dist/core/commands/mad-db-command.js +146 -51
- package/dist/core/commands/mad-sks-command.js +51 -61
- package/dist/core/db-safety.js +35 -37
- package/dist/core/doctor/doctor-dirty-planner.js +9 -4
- package/dist/core/doctor/doctor-native-capability-repair.js +42 -7
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -5
- package/dist/core/doctor/doctor-repair-postcheck.js +10 -1
- package/dist/core/doctor/doctor-transaction.js +1 -1
- package/dist/core/doctor/supabase-mcp-repair.js +2 -2
- package/dist/core/feature-registry.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +5 -4
- package/dist/core/mad-db/mad-db-capability.js +203 -74
- package/dist/core/mad-db/mad-db-coordinator.js +287 -0
- package/dist/core/mad-db/mad-db-executor.js +156 -0
- package/dist/core/mad-db/mad-db-ledger.js +1 -1
- package/dist/core/mad-db/mad-db-lock.js +40 -0
- package/dist/core/mad-db/mad-db-operation-store.js +140 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +42 -22
- package/dist/core/mad-db/mad-db-policy.js +195 -0
- package/dist/core/mad-db/mad-db-postconditions.js +30 -0
- package/dist/core/mad-db/mad-db-recovery.js +27 -0
- package/dist/core/mad-db/mad-db-result-lifecycle.js +31 -102
- package/dist/core/mad-db/mad-db-runtime-profile.js +121 -0
- package/dist/core/mad-db/mad-db-target.js +64 -0
- package/dist/core/managed-assets/managed-assets-manifest.js +14 -4
- package/dist/core/pipeline-internals/runtime-core.js +40 -0
- package/dist/core/providers/glm/bench/glm-benchmark-runner.js +4 -3
- package/dist/core/providers/glm/bench/glm-benchmark-types.js +1 -1
- package/dist/core/release/release-gate-dag.js +6 -5
- package/dist/core/routes.js +23 -8
- package/dist/core/update/update-migration-state.js +265 -50
- package/dist/core/update-check.js +6 -6
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +17 -5
- package/dist/core/zellij/zellij-slot-column-anchor.js +5 -1
- package/dist/scripts/check-dist-runtime.js +3 -2
- package/dist/scripts/codex-0142-manifest-check.js +2 -1
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +6 -0
- package/dist/scripts/doctor-dirty-plan-check.js +1 -1
- package/dist/scripts/doctor-transaction-engine-check.js +1 -0
- package/dist/scripts/doctor-warning-only-not-blocker-check.js +18 -1
- package/dist/scripts/loop-directive-check-lib.js +2 -1
- package/dist/scripts/mad-db-capability-check.js +13 -2
- package/dist/scripts/mad-db-command-check.js +7 -5
- package/dist/scripts/mad-db-hook-idempotency-check.js +21 -0
- package/dist/scripts/mad-db-ledger-check.js +2 -1
- package/dist/scripts/mad-db-lifecycle-hook-decision-check.js +5 -4
- package/dist/scripts/mad-db-mad-command-check.js +29 -16
- package/dist/scripts/mad-db-mcp-result-lifecycle-check.js +11 -10
- package/dist/scripts/mad-db-one-cycle-bounded-check.js +15 -18
- package/dist/scripts/mad-db-one-cycle-consumption-check.js +3 -3
- package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +9 -9
- package/dist/scripts/mad-db-operation-lifecycle-ledger-check.js +6 -6
- package/dist/scripts/mad-db-parallel-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-policy-v2-check.js +20 -0
- package/dist/scripts/mad-db-priority-resolver-check.js +5 -5
- package/dist/scripts/mad-db-real-supabase-e2e.js +166 -0
- package/dist/scripts/mad-db-route-identity-check.js +28 -0
- package/dist/scripts/mad-db-runtime-profile-lifecycle-check.js +24 -0
- package/dist/scripts/mad-db-safety-conflict-matrix-check.js +3 -3
- package/dist/scripts/mad-db-skill-policy-snapshot-check.js +15 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +7 -1
- package/dist/scripts/managed-role-manifest-parity-check.js +4 -1
- package/dist/scripts/naruto-real-parallelism-blackbox.js +17 -4
- package/dist/scripts/native-capability-postcheck-check.js +1 -0
- package/dist/scripts/native-capability-repair-matrix-check.js +2 -0
- package/dist/scripts/native-chrome-web-review-repair-check.js +1 -0
- package/dist/scripts/native-computer-use-repair-check.js +1 -0
- package/dist/scripts/release-dag-full-coverage-check.js +6 -0
- package/dist/scripts/release-triwiki-first-runner-blackbox.js +5 -1
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +1 -1
- package/dist/scripts/sks-401-all-feature-regression-blackbox.js +1 -1
- package/dist/scripts/update-concurrent-lock-check.js +1 -0
- package/dist/scripts/update-first-command-migration-check.js +4 -3
- package/package.json +13 -2
- package/schemas/mad-db/mad-db-capability.schema.json +92 -19
- package/schemas/update-migration.schema.json +13 -1
|
@@ -5,7 +5,8 @@ import { managedAgentRoleConfigForFile, managedAgentRoleConfigForRole } from '..
|
|
|
5
5
|
export async function repairAgentConfigFileReferences(input) {
|
|
6
6
|
const root = path.resolve(input.root);
|
|
7
7
|
const configPath = path.join(root, '.codex', 'config.toml');
|
|
8
|
-
const
|
|
8
|
+
const configExists = await fs.stat(configPath).then((stat) => stat.isFile()).catch(() => false);
|
|
9
|
+
const original = configExists ? await fs.readFile(configPath, 'utf8').catch(() => '') : minimalManagedConfigToml();
|
|
9
10
|
const createdFiles = [];
|
|
10
11
|
const repairedPaths = [];
|
|
11
12
|
const removedUnsupportedFields = [];
|
|
@@ -38,7 +39,12 @@ export async function repairAgentConfigFileReferences(input) {
|
|
|
38
39
|
}
|
|
39
40
|
if (edits.length)
|
|
40
41
|
text = applyEdits(original, edits);
|
|
41
|
-
if (input.apply &&
|
|
42
|
+
if (input.apply && !configExists) {
|
|
43
|
+
await ensureDir(path.dirname(configPath));
|
|
44
|
+
await writeTextAtomic(configPath, text.replace(/\n{3,}/g, '\n\n').replace(/\s*$/, '\n'));
|
|
45
|
+
createdFiles.push(configPath);
|
|
46
|
+
}
|
|
47
|
+
else if (input.apply && text !== original) {
|
|
42
48
|
await writeTextAtomic(configPath, text.replace(/\n{3,}/g, '\n\n').replace(/\s*$/, '\n'));
|
|
43
49
|
}
|
|
44
50
|
const effectiveText = input.apply ? await fs.readFile(configPath, 'utf8').catch(() => text) : text;
|
|
@@ -154,4 +160,39 @@ function escapeToml(value) {
|
|
|
154
160
|
function escapeRegExp(value) {
|
|
155
161
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
156
162
|
}
|
|
163
|
+
function minimalManagedConfigToml() {
|
|
164
|
+
return [
|
|
165
|
+
'model = "gpt-5.5"',
|
|
166
|
+
'model_reasoning_effort = "medium"',
|
|
167
|
+
'service_tier = "fast"',
|
|
168
|
+
'',
|
|
169
|
+
'[features]',
|
|
170
|
+
'hooks = true',
|
|
171
|
+
'remote_control = true',
|
|
172
|
+
'multi_agent = true',
|
|
173
|
+
'fast_mode = true',
|
|
174
|
+
'',
|
|
175
|
+
'[mcp_servers.context7]',
|
|
176
|
+
'url = "https://mcp.context7.com/mcp"',
|
|
177
|
+
'',
|
|
178
|
+
agentConfigBlock('native_agent', 'Read-only SKS analysis agent.', './agents/native-agent-intake.toml', ['Analysis', 'Mapper']),
|
|
179
|
+
'',
|
|
180
|
+
agentConfigBlock('team_consensus', 'SKS planning/debate agent.', './agents/team-consensus.toml', ['Consensus', 'Atlas']),
|
|
181
|
+
'',
|
|
182
|
+
agentConfigBlock('implementation_worker', 'SKS bounded implementation worker.', './agents/implementation-worker.toml', ['Builder', 'Mason']),
|
|
183
|
+
'',
|
|
184
|
+
agentConfigBlock('db_safety_reviewer', 'Read-only DB safety reviewer.', './agents/db-safety-reviewer.toml', ['Sentinel', 'Ledger']),
|
|
185
|
+
'',
|
|
186
|
+
agentConfigBlock('qa_reviewer', 'Read-only QA reviewer.', './agents/qa-reviewer.toml', ['Verifier', 'Reviewer']),
|
|
187
|
+
''
|
|
188
|
+
].join('\n');
|
|
189
|
+
}
|
|
190
|
+
function agentConfigBlock(table, description, configFile, nicknames = []) {
|
|
191
|
+
return [
|
|
192
|
+
`[agents.${table}]`,
|
|
193
|
+
`description = "${description}"`,
|
|
194
|
+
`config_file = "${configFile}"`,
|
|
195
|
+
`nickname_candidates = [${nicknames.map((name) => `"${name}"`).join(', ')}]`
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
157
198
|
//# sourceMappingURL=agent-config-file-repair.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { nowIso, writeJsonAtomic, writeTextAtomic, ensureDir } from '../fsx.js';
|
|
4
|
+
import { PACKAGE_VERSION, nowIso, writeJsonAtomic, writeTextAtomic, ensureDir } from '../fsx.js';
|
|
5
5
|
import { repairAgentRoleConfigs } from '../agents/agent-role-config.js';
|
|
6
6
|
import { agentRolePayloadFor, probeCodexAgentTypeSupport } from './codex-agent-type-probe.js';
|
|
7
7
|
const DIRECTIVE_ROLES = [
|
|
@@ -86,12 +86,12 @@ export async function syncCodexAgentRoles(input) {
|
|
|
86
86
|
function roleToml(role, payload) {
|
|
87
87
|
return [
|
|
88
88
|
`name = "${role}"`,
|
|
89
|
-
`description = "SKS managed
|
|
89
|
+
`description = "SKS managed ${PACKAGE_VERSION} directive role: ${role}"`,
|
|
90
90
|
'model_reasoning_effort = "medium"',
|
|
91
91
|
role.includes('implementer') ? 'sandbox_mode = "workspace-write"' : 'sandbox_mode = "read-only"',
|
|
92
92
|
'approval_policy = "never"',
|
|
93
93
|
'developer_instructions = """',
|
|
94
|
-
`You are ${role}. SKS managed
|
|
94
|
+
`You are ${role}. SKS managed ${PACKAGE_VERSION} directive role with bounded ownership.`,
|
|
95
95
|
'Bounded ownership: use only the assigned owner files/directories and treat memory as guidance, not permission.',
|
|
96
96
|
role.includes('implementer') ? 'Maker/checker separation: implementer may patch only owner scope and cannot self-approve.' : 'Maker/checker separation: checker is read-only and must reject missing gates or missing proof artifacts.',
|
|
97
97
|
role.includes('implementer') ? 'Allowed sandbox: workspace-write only within assigned owner scope.' : 'Allowed sandbox: read-only; checker roles cannot mutate.',
|
|
@@ -107,7 +107,7 @@ function roleToml(role, payload) {
|
|
|
107
107
|
].join('\n');
|
|
108
108
|
}
|
|
109
109
|
function isSksManagedDirectiveRole(text) {
|
|
110
|
-
return /SKS managed (?:3\.1\.(?:4|5|6|7|11)|4\.1
|
|
110
|
+
return /SKS managed (?:3\.1\.(?:4|5|6|7|11)|4\.1\.\d+) (?:directive|bounded) role/.test(text)
|
|
111
111
|
|| /\bmessage_role_prefix\s*=/.test(text) && /SKS managed 3\.1\./.test(text);
|
|
112
112
|
}
|
|
113
113
|
function blockersOf(value) {
|
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { CURRENT_CODEX_RELEASE_MANIFEST } from '../codex-compat/codex-release-manifest.js';
|
|
5
5
|
import { compareSemverLike } from '../codex-compat/codex-version-policy.js';
|
|
6
|
-
import { ensureDir, nowIso, runProcess, sha256, writeJsonAtomic } from '../fsx.js';
|
|
6
|
+
import { ensureDir, nowIso, packageRoot, readJson, runProcess, sha256, writeJsonAtomic } from '../fsx.js';
|
|
7
7
|
import { resolveCodexRuntime } from '../codex-runtime/resolve-codex-runtime.js';
|
|
8
8
|
export const CODEX_0142_FEATURE_KEYS = [
|
|
9
9
|
'runtime_identity',
|
|
@@ -31,8 +31,9 @@ export async function detectCodex0142Capability(input = {}) {
|
|
|
31
31
|
return blockedCapability('blocked', null, null, [...runtime.blockers]);
|
|
32
32
|
}
|
|
33
33
|
const versionOk = compareSemverLike(runtime.identity.version, CURRENT_CODEX_RELEASE_MANIFEST.requiredCliVersion) >= 0;
|
|
34
|
-
const schemaProbe = await
|
|
35
|
-
const
|
|
34
|
+
const schemaProbe = await schemaProbeForMode(root, runtime.identity.realpath, input.requireReal === true);
|
|
35
|
+
const implementationRoot = packageRoot();
|
|
36
|
+
const appServerClientText = await fsp.readFile(path.join(implementationRoot, 'src', 'core', 'codex-control', 'codex-app-server-v2-client.ts'), 'utf8').catch(() => '');
|
|
36
37
|
const states = featureStatesFromSchema(schemaProbe.text, schemaProbe.ok, appServerClientText);
|
|
37
38
|
const blockers = [
|
|
38
39
|
...(versionOk ? [] : ['codex_0_142_required']),
|
|
@@ -40,6 +41,7 @@ export async function detectCodex0142Capability(input = {}) {
|
|
|
40
41
|
...Object.values(states).flatMap((state) => state.blockers)
|
|
41
42
|
];
|
|
42
43
|
const realEnough = blockers.length === 0
|
|
44
|
+
&& schemaProbe.mode === 'real-schema'
|
|
43
45
|
&& schemaProbe.sha256 !== null
|
|
44
46
|
&& CURRENT_CODEX_RELEASE_MANIFEST.generatedSchemaSha256 !== 'pending-generated-schema'
|
|
45
47
|
&& schemaProbe.sha256 === CURRENT_CODEX_RELEASE_MANIFEST.generatedSchemaSha256
|
|
@@ -54,7 +56,7 @@ export async function detectCodex0142Capability(input = {}) {
|
|
|
54
56
|
runtime_identity: runtime.identity,
|
|
55
57
|
generated_schema_sha256: schemaProbe.sha256,
|
|
56
58
|
manifest_schema_sha256: CURRENT_CODEX_RELEASE_MANIFEST.generatedSchemaSha256,
|
|
57
|
-
probe_mode: schemaProbe.ok ?
|
|
59
|
+
probe_mode: schemaProbe.ok ? schemaProbe.mode : 'blocked',
|
|
58
60
|
feature_states: states,
|
|
59
61
|
blockers,
|
|
60
62
|
warnings: [
|
|
@@ -133,7 +135,37 @@ function currentTimeState(schemaTextLower, schemaOk, appServerClientText) {
|
|
|
133
135
|
blockers: clientHandlesCurrentTime ? [] : ['codex_0142_current_time_read_handler_not_verified']
|
|
134
136
|
};
|
|
135
137
|
}
|
|
138
|
+
async function schemaProbeForMode(root, codexBin, requireReal) {
|
|
139
|
+
if (!requireReal && process.env.SKS_CODEX_0142_REFRESH !== '1') {
|
|
140
|
+
const shipped = await shippedSchemaProbe();
|
|
141
|
+
if (shipped.ok)
|
|
142
|
+
return shipped;
|
|
143
|
+
}
|
|
144
|
+
return generateSchemaProbe(root, codexBin);
|
|
145
|
+
}
|
|
146
|
+
async function shippedSchemaProbe() {
|
|
147
|
+
const schemaRoot = path.join(packageRoot(), 'schemas', 'codex', 'app-server-0.142');
|
|
148
|
+
const files = await listFiles(schemaRoot).catch(() => []);
|
|
149
|
+
if (!files.length)
|
|
150
|
+
return { ok: false, text: 'shipped schema cache missing', sha256: null, mode: 'shipped-manifest-cache' };
|
|
151
|
+
const rows = await Promise.all(files.map(async (file) => {
|
|
152
|
+
const text = await fsp.readFile(file, 'utf8');
|
|
153
|
+
return {
|
|
154
|
+
relative: path.relative(schemaRoot, file),
|
|
155
|
+
text,
|
|
156
|
+
canonicalText: canonicalSchemaContent(file, text)
|
|
157
|
+
};
|
|
158
|
+
}));
|
|
159
|
+
const joined = rows.map((row) => `${row.relative}\n${row.text}`).join('\n');
|
|
160
|
+
const canonicalJoined = rows.map((row) => `${row.relative}\n${row.canonicalText}`).join('\n');
|
|
161
|
+
return { ok: true, text: joined, sha256: sha256(canonicalJoined), mode: 'shipped-manifest-cache' };
|
|
162
|
+
}
|
|
136
163
|
async function generateSchemaProbe(root, codexBin) {
|
|
164
|
+
const cachePath = await schemaCachePath(root, codexBin);
|
|
165
|
+
const cached = await readJson(cachePath, null).catch(() => null);
|
|
166
|
+
if (cached?.ok === true && typeof cached.text === 'string' && cached.sha256) {
|
|
167
|
+
return { ok: true, text: cached.text, sha256: cached.sha256, mode: 'real-schema' };
|
|
168
|
+
}
|
|
137
169
|
const out = path.join(os.tmpdir(), `sks-codex-0142-schema-${process.pid}-${Date.now()}`);
|
|
138
170
|
await ensureDir(out);
|
|
139
171
|
const result = await runProcess(codexBin, ['app-server', 'generate-json-schema', '--out', out], {
|
|
@@ -150,7 +182,7 @@ async function generateSchemaProbe(root, codexBin) {
|
|
|
150
182
|
timedOut: false
|
|
151
183
|
}));
|
|
152
184
|
if (result.code !== 0)
|
|
153
|
-
return { ok: false, text: `${result.stdout}\n${result.stderr}`, sha256: null };
|
|
185
|
+
return { ok: false, text: `${result.stdout}\n${result.stderr}`, sha256: null, mode: 'real-schema' };
|
|
154
186
|
const files = await listFiles(out);
|
|
155
187
|
const rows = await Promise.all(files.map(async (file) => {
|
|
156
188
|
const text = await fsp.readFile(file, 'utf8');
|
|
@@ -163,7 +195,20 @@ async function generateSchemaProbe(root, codexBin) {
|
|
|
163
195
|
const joined = rows.map((row) => `${row.relative}\n${row.text}`).join('\n');
|
|
164
196
|
const canonicalJoined = rows.map((row) => `${row.relative}\n${row.canonicalText}`).join('\n');
|
|
165
197
|
await fsp.rm(out, { recursive: true, force: true }).catch(() => { });
|
|
166
|
-
|
|
198
|
+
const probe = { ok: true, text: joined, sha256: sha256(canonicalJoined), mode: 'real-schema' };
|
|
199
|
+
await writeJsonAtomic(cachePath, probe).catch(() => undefined);
|
|
200
|
+
return probe;
|
|
201
|
+
}
|
|
202
|
+
async function schemaCachePath(root, codexBin) {
|
|
203
|
+
const realpath = await fsp.realpath(codexBin).catch(() => codexBin);
|
|
204
|
+
const key = sha256(JSON.stringify({
|
|
205
|
+
realpath,
|
|
206
|
+
target: CURRENT_CODEX_RELEASE_MANIFEST.targetTag,
|
|
207
|
+
manifest_schema_sha256: CURRENT_CODEX_RELEASE_MANIFEST.generatedSchemaSha256,
|
|
208
|
+
platform: process.platform,
|
|
209
|
+
arch: process.arch
|
|
210
|
+
}));
|
|
211
|
+
return path.join(root, '.sneakoscope', 'cache', 'codex-0142-schema', `${key}.json`);
|
|
167
212
|
}
|
|
168
213
|
function canonicalSchemaContent(file, text) {
|
|
169
214
|
if (!file.endsWith('.json'))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { nowIso } from '../fsx.js';
|
|
2
|
+
import { nowIso, PACKAGE_VERSION } from '../fsx.js';
|
|
3
3
|
import { resolveCodexRuntime } from '../codex-runtime/resolve-codex-runtime.js';
|
|
4
4
|
export class CodexAppServerV2Client {
|
|
5
5
|
command;
|
|
@@ -28,7 +28,7 @@ export class CodexAppServerV2Client {
|
|
|
28
28
|
clientInfo: {
|
|
29
29
|
name: 'sneakoscope-codex-app-server-v2',
|
|
30
30
|
title: 'Sneakoscope Codex app-server v2',
|
|
31
|
-
version:
|
|
31
|
+
version: PACKAGE_VERSION
|
|
32
32
|
},
|
|
33
33
|
capabilities: {
|
|
34
34
|
experimentalApi: true,
|
|
@@ -16,11 +16,34 @@ import { codexNativeFeatureState, computeCodexNativeInvocationDefaults } from '.
|
|
|
16
16
|
const REPORT_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
|
|
17
17
|
const REQUIRED_SKILL_NAMES = MANAGED_SKILLS.map((skill) => skill.id);
|
|
18
18
|
const REQUIRED_AGENT_ROLES = MANAGED_AGENT_ROLES.map((role) => role.id);
|
|
19
|
+
const invocationMatrixCache = new Map();
|
|
19
20
|
export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd() }) {
|
|
20
21
|
const root = path.resolve(input.root || process.cwd());
|
|
22
|
+
if (input.snapshot) {
|
|
23
|
+
await writeCodexNativeFeatureMatrix(root, input.snapshot, input.missionDir);
|
|
24
|
+
return input.snapshot;
|
|
25
|
+
}
|
|
21
26
|
const deprecatedApplyRepairs = input.applyRepairs === true;
|
|
22
27
|
const mode = input.mode || (deprecatedApplyRepairs || input.repairManagedAssets === true ? 'repair' : 'read-only');
|
|
23
28
|
const repairManagedAssets = mode === 'repair' && (input.repairManagedAssets === true || deprecatedApplyRepairs);
|
|
29
|
+
const managedAssetFingerprint = await readManagedAssetFingerprint(root);
|
|
30
|
+
const cacheKey = JSON.stringify({
|
|
31
|
+
root,
|
|
32
|
+
mode,
|
|
33
|
+
repairManagedAssets,
|
|
34
|
+
codexHome: process.env.CODEX_HOME || null,
|
|
35
|
+
managedAssetFingerprint,
|
|
36
|
+
fixture: [
|
|
37
|
+
process.env.SKS_CODEX_0138_FAKE,
|
|
38
|
+
process.env.SKS_CODEX_0139_FAKE,
|
|
39
|
+
process.env.SKS_CODEX_0140_FAKE,
|
|
40
|
+
process.env.SKS_CODEX_0142_FAKE,
|
|
41
|
+
process.env.SKS_CODEX_PLUGIN_JSON_FAKE
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
if (!input.missionDir && !repairManagedAssets && invocationMatrixCache.has(cacheKey)) {
|
|
45
|
+
return invocationMatrixCache.get(cacheKey);
|
|
46
|
+
}
|
|
24
47
|
const fixtureMode = process.env.SKS_CODEX_0138_FAKE === '1' || process.env.SKS_CODEX_0139_FAKE === '1' || process.env.SKS_CODEX_0140_FAKE === '1' || process.env.SKS_CODEX_0142_FAKE === '1' || process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1';
|
|
25
48
|
const codexBin = fixtureMode ? process.env.CODEX_BIN || 'codex' : await findCodexBinary().catch(() => null);
|
|
26
49
|
const version = codexBin ? await codexVersion(codexBin) : null;
|
|
@@ -190,6 +213,8 @@ export async function buildCodexNativeFeatureMatrix(input = { root: process.cwd(
|
|
|
190
213
|
invocation_defaults: computeCodexNativeInvocationDefaults(matrixBase)
|
|
191
214
|
};
|
|
192
215
|
await writeCodexNativeFeatureMatrix(root, matrix, input.missionDir);
|
|
216
|
+
if (!input.missionDir && !repairManagedAssets)
|
|
217
|
+
invocationMatrixCache.set(cacheKey, matrix);
|
|
193
218
|
return matrix;
|
|
194
219
|
}
|
|
195
220
|
async function inspectManagedSkillState(root) {
|
|
@@ -252,6 +277,31 @@ async function inspectManagedAgentRoleState(root) {
|
|
|
252
277
|
warnings: existingCount > managed.size ? ['non_sks_agent_roles_ignored'] : []
|
|
253
278
|
};
|
|
254
279
|
}
|
|
280
|
+
async function readManagedAssetFingerprint(root) {
|
|
281
|
+
const dirs = [
|
|
282
|
+
path.join(root, '.agents', 'skills'),
|
|
283
|
+
path.join(root, '.codex', 'agents'),
|
|
284
|
+
...(process.env.CODEX_HOME ? [path.join(process.env.CODEX_HOME, 'skills'), path.join(process.env.CODEX_HOME, 'agents')] : [])
|
|
285
|
+
];
|
|
286
|
+
const rows = [];
|
|
287
|
+
for (const dir of dirs) {
|
|
288
|
+
const stat = await fs.stat(dir).catch(() => null);
|
|
289
|
+
rows.push(`${dir}:${stat ? `${stat.mtimeMs}:${stat.size}` : 'missing'}`);
|
|
290
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
291
|
+
for (const entry of entries) {
|
|
292
|
+
const file = path.join(dir, entry.name);
|
|
293
|
+
const childStat = await fs.stat(file).catch(() => null);
|
|
294
|
+
rows.push(`${file}:${entry.isDirectory() ? 'dir' : 'file'}:${childStat ? `${childStat.mtimeMs}:${childStat.size}` : 'missing'}`);
|
|
295
|
+
if (entry.isDirectory()) {
|
|
296
|
+
const skillFile = path.join(file, 'SKILL.md');
|
|
297
|
+
const skillStat = await fs.stat(skillFile).catch(() => null);
|
|
298
|
+
if (skillStat)
|
|
299
|
+
rows.push(`${skillFile}:file:${skillStat.mtimeMs}:${skillStat.size}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return rows;
|
|
304
|
+
}
|
|
255
305
|
export async function writeCodexNativeFeatureMatrix(root, matrix, missionDir) {
|
|
256
306
|
await writeJsonAtomic(path.join(root, REPORT_PATH), matrix);
|
|
257
307
|
if (missionDir)
|
|
@@ -7,13 +7,19 @@ export async function postcheckNativeCapabilities(input) {
|
|
|
7
7
|
const fixture = input.fixture || false;
|
|
8
8
|
const matrix = input.matrix || await buildNativeCapabilityRepairMatrix({ root, fixture, reportPath: null });
|
|
9
9
|
const capabilities = await Promise.all(matrix.capabilities.map((state) => postcheckCapability(root, state, fixture)));
|
|
10
|
-
const
|
|
10
|
+
const coreBlockers = capabilities.flatMap((state) => state.core_blockers || []);
|
|
11
|
+
const routeBlockers = mergeRouteBlockers(capabilities);
|
|
11
12
|
const checked = {
|
|
12
13
|
...matrix,
|
|
13
14
|
generated_at: new Date().toISOString(),
|
|
14
|
-
ok:
|
|
15
|
+
ok: coreBlockers.length === 0,
|
|
15
16
|
capabilities,
|
|
16
|
-
|
|
17
|
+
core_blockers: coreBlockers,
|
|
18
|
+
route_blockers: routeBlockers,
|
|
19
|
+
optional_manual_required: capabilities
|
|
20
|
+
.filter((state) => state.availability === 'manual-required' && state.after !== 'verified')
|
|
21
|
+
.map((state) => state.id),
|
|
22
|
+
blockers: coreBlockers,
|
|
17
23
|
warnings: capabilities.flatMap((state) => state.warnings)
|
|
18
24
|
};
|
|
19
25
|
const reportPath = input.reportPath === null
|
|
@@ -48,17 +54,19 @@ function postcheckImageGeneration(state, fixture) {
|
|
|
48
54
|
return {
|
|
49
55
|
...state,
|
|
50
56
|
after: 'unknown',
|
|
51
|
-
|
|
57
|
+
core_blockers: [],
|
|
58
|
+
route_blockers: mergeStateRouteBlockers(state, 'route-image', ['imagegen_auth_or_codex_app_builtin_missing']),
|
|
59
|
+
blockers: [],
|
|
52
60
|
warnings: [...new Set([...state.warnings, 'image_generation_not_verified_without_real_capability'])]
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
async function postcheckImageFollowupEdit(root, state) {
|
|
56
64
|
const contract = await validateSavedArtifactPathContract(root);
|
|
57
65
|
if (!contract.ok)
|
|
58
|
-
return
|
|
66
|
+
return routeBlocked(state, 'route-image', contract.blockers);
|
|
59
67
|
const sample = path.join(contract.imageArtifacts, 'postcheck-followup-sample.txt');
|
|
60
68
|
if (!(await writeReadSample(sample)))
|
|
61
|
-
return
|
|
69
|
+
return routeBlocked(state, 'route-image', ['image_followup_sample_artifact_unwritable']);
|
|
62
70
|
return verified(state);
|
|
63
71
|
}
|
|
64
72
|
function postcheckComputerUse(state, _fixture) {
|
|
@@ -67,7 +75,9 @@ function postcheckComputerUse(state, _fixture) {
|
|
|
67
75
|
return {
|
|
68
76
|
...state,
|
|
69
77
|
after: 'unknown',
|
|
70
|
-
|
|
78
|
+
core_blockers: [],
|
|
79
|
+
route_blockers: mergeStateRouteBlockers(state, 'route-computer-use', ['computer_use_os_permission_or_capability_unknown']),
|
|
80
|
+
blockers: [],
|
|
71
81
|
warnings: [...new Set([...state.warnings, 'manual_os_permission_required'])]
|
|
72
82
|
};
|
|
73
83
|
}
|
|
@@ -77,7 +87,9 @@ function postcheckChromeWebReview(state, fixture) {
|
|
|
77
87
|
return {
|
|
78
88
|
...state,
|
|
79
89
|
after: 'unknown',
|
|
80
|
-
|
|
90
|
+
core_blockers: [],
|
|
91
|
+
route_blockers: mergeStateRouteBlockers(state, 'route-chrome-web-review', ['codex_chrome_extension_readiness_not_verified']),
|
|
92
|
+
blockers: [],
|
|
81
93
|
warnings: [...new Set([...state.warnings, 'manual_chrome_extension_setup_required'])]
|
|
82
94
|
};
|
|
83
95
|
}
|
|
@@ -88,12 +100,12 @@ async function postcheckAppScreenshot(root, state) {
|
|
|
88
100
|
const dir = path.join(root, '.sneakoscope', 'app-screenshots');
|
|
89
101
|
const registry = path.join(dir, 'screenshot-registry.json');
|
|
90
102
|
if (!(await writeReadSample(path.join(dir, 'postcheck-screenshot-sample.txt')))) {
|
|
91
|
-
return
|
|
103
|
+
return routeBlocked(state, 'route-image', ['app_screenshot_directory_unwritable']);
|
|
92
104
|
}
|
|
93
105
|
await writeJsonAtomic(registry, { schema: 'sks.app-screenshot-registry.v1', generated_at: new Date().toISOString(), screenshots: [] }).catch(() => undefined);
|
|
94
106
|
const json = await readJson(registry, {}).catch(() => ({}));
|
|
95
107
|
if (json.schema !== 'sks.app-screenshot-registry.v1')
|
|
96
|
-
return
|
|
108
|
+
return routeBlocked(state, 'route-image', ['app_screenshot_registry_invalid']);
|
|
97
109
|
return verified(state);
|
|
98
110
|
}
|
|
99
111
|
function postcheckAppHandoff(state, fixture) {
|
|
@@ -102,7 +114,9 @@ function postcheckAppHandoff(state, fixture) {
|
|
|
102
114
|
return {
|
|
103
115
|
...state,
|
|
104
116
|
after: 'unknown',
|
|
105
|
-
|
|
117
|
+
core_blockers: [],
|
|
118
|
+
route_blockers: mergeStateRouteBlockers(state, 'route-app-handoff', ['codex_app_handoff_not_verified']),
|
|
119
|
+
blockers: [],
|
|
106
120
|
warnings: [...new Set([...state.warnings, 'manual_app_handoff_approval_required'])]
|
|
107
121
|
};
|
|
108
122
|
}
|
|
@@ -114,24 +128,53 @@ async function postcheckImagePathExposure(root, state, fixture) {
|
|
|
114
128
|
return {
|
|
115
129
|
...state,
|
|
116
130
|
after: 'degraded',
|
|
131
|
+
availability: 'available-unverified',
|
|
132
|
+
core_blockers: [],
|
|
133
|
+
route_blockers: {},
|
|
117
134
|
blockers: [],
|
|
118
135
|
warnings: [...new Set([...state.warnings, 'using_saved_artifact_path_contract_fallback'])]
|
|
119
136
|
};
|
|
120
137
|
}
|
|
121
|
-
return
|
|
138
|
+
return routeBlocked(state, 'route-image', ['image_path_exposure_missing_without_fallback_contract', ...contract.blockers]);
|
|
122
139
|
}
|
|
123
140
|
async function postcheckSavedArtifactPathContract(root, state) {
|
|
124
141
|
const contract = await validateSavedArtifactPathContract(root);
|
|
125
142
|
if (!contract.ok)
|
|
126
|
-
return
|
|
143
|
+
return routeBlocked(state, 'route-image', contract.blockers);
|
|
127
144
|
if (!(await writeReadSample(path.join(contract.imageArtifacts, 'postcheck-contract-image.txt'))))
|
|
128
|
-
return
|
|
145
|
+
return routeBlocked(state, 'route-image', ['image_artifacts_directory_unwritable']);
|
|
129
146
|
if (!(await writeReadSample(path.join(contract.appScreenshots, 'postcheck-contract-screenshot.txt'))))
|
|
130
|
-
return
|
|
147
|
+
return routeBlocked(state, 'route-image', ['app_screenshots_directory_unwritable']);
|
|
131
148
|
return verified(state);
|
|
132
149
|
}
|
|
133
150
|
function verified(state) {
|
|
134
|
-
return { ...state, after: 'verified', blockers: [] };
|
|
151
|
+
return { ...state, after: 'verified', availability: 'verified', core_blockers: [], route_blockers: {}, blockers: [] };
|
|
152
|
+
}
|
|
153
|
+
function routeBlocked(state, scope, blockers) {
|
|
154
|
+
return {
|
|
155
|
+
...state,
|
|
156
|
+
after: 'blocked',
|
|
157
|
+
core_blockers: [],
|
|
158
|
+
route_blockers: mergeStateRouteBlockers(state, scope, blockers),
|
|
159
|
+
blockers: []
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function mergeStateRouteBlockers(state, scope, blockers) {
|
|
163
|
+
return {
|
|
164
|
+
...(state.route_blockers || {}),
|
|
165
|
+
[scope]: [...new Set([...(state.route_blockers?.[scope] || []), ...blockers])]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function mergeRouteBlockers(states) {
|
|
169
|
+
const merged = {};
|
|
170
|
+
for (const state of states) {
|
|
171
|
+
for (const [scope, blockers] of Object.entries(state.route_blockers || {})) {
|
|
172
|
+
merged[scope] = [
|
|
173
|
+
...new Set([...(merged[scope] || []), ...blockers])
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return merged;
|
|
135
178
|
}
|
|
136
179
|
async function validateSavedArtifactPathContract(root) {
|
|
137
180
|
const contractPath = path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json');
|
|
@@ -24,14 +24,20 @@ export async function buildNativeCapabilityRepairMatrix(input) {
|
|
|
24
24
|
? fixtureNativeFeatureMatrix(fixture)
|
|
25
25
|
: await buildCodexNativeFeatureMatrix({ root, mode: 'read-only' }).catch((err) => ({ ok: false, features: {}, blockers: [messageOf(err)], invocation_defaults: {} }));
|
|
26
26
|
const states = await Promise.all(NATIVE_CAPABILITY_IDS.filter((id) => selected.has(id)).map((id) => stateForCapability(root, id, imageCapability, nativeFeatureMatrix)));
|
|
27
|
-
const
|
|
27
|
+
const coreBlockers = states.flatMap((state) => state.core_blockers || state.blockers);
|
|
28
|
+
const routeBlockers = mergeRouteBlockers(states);
|
|
28
29
|
const warnings = states.flatMap((state) => state.warnings);
|
|
29
30
|
const matrix = {
|
|
30
31
|
schema: 'sks.native-capability-repair-matrix.v1',
|
|
31
32
|
generated_at: nowIso(),
|
|
32
|
-
ok:
|
|
33
|
+
ok: coreBlockers.length === 0,
|
|
33
34
|
capabilities: states,
|
|
34
|
-
|
|
35
|
+
core_blockers: coreBlockers,
|
|
36
|
+
route_blockers: routeBlockers,
|
|
37
|
+
optional_manual_required: states
|
|
38
|
+
.filter((state) => state.availability === 'manual-required' && state.required_for.every((scope) => !isCoreScope(scope)))
|
|
39
|
+
.map((state) => state.id),
|
|
40
|
+
blockers: coreBlockers,
|
|
35
41
|
warnings
|
|
36
42
|
};
|
|
37
43
|
const reportPath = input.reportPath === null
|
|
@@ -49,10 +55,15 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
49
55
|
id,
|
|
50
56
|
before: verified ? 'verified' : 'blocked',
|
|
51
57
|
repairability: verified ? 'auto' : 'manual-required',
|
|
52
|
-
|
|
58
|
+
availability: verified ? 'verified' : 'manual-required',
|
|
59
|
+
required_for: ['route-image'],
|
|
60
|
+
repair_actions: verified ? ['postcheck-imagegen-path-contract'] : ['Sign in to Codex App and enable/use the built-in $imagegen / gpt-image-2 surface, then rerun `sks doctor --capabilities --yes`.'],
|
|
53
61
|
after: null,
|
|
54
62
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
55
|
-
|
|
63
|
+
core_blockers: [],
|
|
64
|
+
route_blockers: verified ? {} : { 'route-image': ['imagegen_auth_or_codex_app_builtin_missing'] },
|
|
65
|
+
manual_actions: verified ? [] : ['Sign in to Codex App and enable/use the built-in $imagegen / gpt-image-2 surface before image routes.'],
|
|
66
|
+
blockers: [],
|
|
56
67
|
warnings: verified ? [] : ['image_generation_not_verified_without_real_capability']
|
|
57
68
|
};
|
|
58
69
|
}
|
|
@@ -75,10 +86,15 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
75
86
|
id,
|
|
76
87
|
before: ok ? 'verified' : 'unknown',
|
|
77
88
|
repairability: ok ? 'auto' : 'manual-required',
|
|
78
|
-
|
|
89
|
+
availability: ok ? 'verified' : 'manual-required',
|
|
90
|
+
required_for: ['route-app-handoff'],
|
|
91
|
+
repair_actions: ok ? ['postcheck-app-handoff'] : ['Open Codex App and approve/enable app handoff, then rerun `sks doctor --capabilities --yes`.'],
|
|
79
92
|
after: null,
|
|
80
93
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
81
|
-
|
|
94
|
+
core_blockers: [],
|
|
95
|
+
route_blockers: ok ? {} : { 'route-app-handoff': ['codex_app_handoff_not_verified'] },
|
|
96
|
+
manual_actions: ok ? [] : ['Open Codex App and approve/enable app handoff before app handoff routes.'],
|
|
97
|
+
blockers: [],
|
|
82
98
|
warnings: ok ? [] : ['manual_app_handoff_approval_required']
|
|
83
99
|
};
|
|
84
100
|
}
|
|
@@ -89,10 +105,15 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
89
105
|
id,
|
|
90
106
|
before: ok ? 'verified' : fallback ? 'degraded' : 'missing',
|
|
91
107
|
repairability: ok ? 'auto' : 'doctor-fix',
|
|
108
|
+
availability: ok ? 'verified' : fallback ? 'available-unverified' : 'unavailable',
|
|
109
|
+
required_for: ['route-image'],
|
|
92
110
|
repair_actions: ok ? ['postcheck-image-path-exposure'] : ['create-saved-artifact-path-contract'],
|
|
93
111
|
after: null,
|
|
94
112
|
artifact_path: path.join(reports, 'saved-artifact-path-contract.json'),
|
|
95
|
-
|
|
113
|
+
core_blockers: [],
|
|
114
|
+
route_blockers: ok || fallback ? {} : { 'route-image': ['image_path_exposure_missing_without_fallback_contract'] },
|
|
115
|
+
manual_actions: [],
|
|
116
|
+
blockers: [],
|
|
96
117
|
warnings: ok ? [] : ['using_saved_artifact_path_contract_fallback']
|
|
97
118
|
};
|
|
98
119
|
}
|
|
@@ -101,11 +122,16 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
101
122
|
return {
|
|
102
123
|
id,
|
|
103
124
|
before: envVerified ? 'verified' : 'unknown',
|
|
125
|
+
availability: envVerified ? 'verified' : 'manual-required',
|
|
126
|
+
required_for: ['route-computer-use'],
|
|
104
127
|
repairability: envVerified ? 'auto' : 'manual-required',
|
|
105
|
-
repair_actions: envVerified ? ['postcheck-computer-use'] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions; run `$CU doctor` for native capability diagnostics, then rerun `sks doctor --
|
|
128
|
+
repair_actions: envVerified ? ['postcheck-computer-use'] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions; run `$CU doctor` for native capability diagnostics, then rerun `sks doctor --capabilities --yes`.'],
|
|
106
129
|
after: null,
|
|
107
130
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
108
|
-
|
|
131
|
+
core_blockers: [],
|
|
132
|
+
route_blockers: envVerified ? {} : { 'route-computer-use': ['computer_use_os_permission_or_capability_unknown'] },
|
|
133
|
+
manual_actions: envVerified ? [] : ['Enable Codex Computer Use and macOS Screen Recording/Accessibility permissions before `$CU` routes.'],
|
|
134
|
+
blockers: [],
|
|
109
135
|
warnings: envVerified ? [] : ['manual_os_permission_required']
|
|
110
136
|
};
|
|
111
137
|
}
|
|
@@ -113,11 +139,16 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
113
139
|
return {
|
|
114
140
|
id,
|
|
115
141
|
before: chromeReady ? 'verified' : 'unknown',
|
|
142
|
+
availability: chromeReady ? 'verified' : 'manual-required',
|
|
143
|
+
required_for: ['route-chrome-web-review'],
|
|
116
144
|
repairability: chromeReady ? 'auto' : 'manual-required',
|
|
117
|
-
repair_actions: chromeReady ? ['postcheck-chrome-extension-readiness'] : ['Install/enable the official Codex Chrome Extension, approve it in Codex App, then rerun `sks doctor --
|
|
145
|
+
repair_actions: chromeReady ? ['postcheck-chrome-extension-readiness'] : ['Install/enable the official Codex Chrome Extension, approve it in Codex App, then rerun `sks doctor --capabilities --yes`; web/browser/localhost verification must use the Chrome extension path first.'],
|
|
118
146
|
after: null,
|
|
119
147
|
artifact_path: path.join(reports, 'native-capability-repair-matrix.json'),
|
|
120
|
-
|
|
148
|
+
core_blockers: [],
|
|
149
|
+
route_blockers: chromeReady ? {} : { 'route-chrome-web-review': ['codex_chrome_extension_readiness_not_verified'] },
|
|
150
|
+
manual_actions: chromeReady ? [] : ['Install/enable the official Codex Chrome Extension before browser/web review routes.'],
|
|
151
|
+
blockers: [],
|
|
121
152
|
warnings: chromeReady ? [] : ['manual_chrome_extension_setup_required']
|
|
122
153
|
};
|
|
123
154
|
}
|
|
@@ -157,14 +188,47 @@ function autoState(id, ready, artifactPath, actions) {
|
|
|
157
188
|
return {
|
|
158
189
|
id,
|
|
159
190
|
before: ready ? 'verified' : 'missing',
|
|
191
|
+
availability: ready ? 'verified' : 'available-unverified',
|
|
192
|
+
required_for: routeScopesForCapability(id),
|
|
160
193
|
repairability: ready ? 'auto' : 'doctor-fix',
|
|
161
194
|
repair_actions: ready ? [`postcheck-${id}`] : actions,
|
|
162
195
|
after: null,
|
|
163
196
|
artifact_path: artifactPath,
|
|
164
|
-
|
|
197
|
+
core_blockers: [],
|
|
198
|
+
route_blockers: ready ? {} : routeBlockerForCapability(id, `${id}_repair_required`),
|
|
199
|
+
manual_actions: [],
|
|
200
|
+
blockers: [],
|
|
165
201
|
warnings: []
|
|
166
202
|
};
|
|
167
203
|
}
|
|
204
|
+
function routeScopesForCapability(id) {
|
|
205
|
+
if (id === 'image_followup_edit' || id === 'saved_artifact_path_contract' || id === 'codex_app_screenshot')
|
|
206
|
+
return ['route-image'];
|
|
207
|
+
if (id === 'app_handoff')
|
|
208
|
+
return ['route-app-handoff'];
|
|
209
|
+
if (id === 'computer_use')
|
|
210
|
+
return ['route-computer-use'];
|
|
211
|
+
if (id === 'chrome_web_review')
|
|
212
|
+
return ['route-chrome-web-review'];
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
function routeBlockerForCapability(id, blocker) {
|
|
216
|
+
const scopes = routeScopesForCapability(id);
|
|
217
|
+
return Object.fromEntries(scopes.map((scope) => [scope, [blocker]]));
|
|
218
|
+
}
|
|
219
|
+
function mergeRouteBlockers(states) {
|
|
220
|
+
const merged = {};
|
|
221
|
+
for (const state of states) {
|
|
222
|
+
for (const [scope, blockers] of Object.entries(state.route_blockers || {})) {
|
|
223
|
+
const next = [...(merged[scope] || []), ...blockers];
|
|
224
|
+
merged[scope] = [...new Set(next)];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return merged;
|
|
228
|
+
}
|
|
229
|
+
function isCoreScope(scope) {
|
|
230
|
+
return scope === 'core-cli' || scope === 'mad-interactive' || scope === 'managed-migration';
|
|
231
|
+
}
|
|
168
232
|
function featureOk(matrix, feature) {
|
|
169
233
|
return matrix?.features?.[feature]?.ok === true;
|
|
170
234
|
}
|