sneakoscope 10.3.2 → 10.3.4

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.
Files changed (71) hide show
  1. package/README.md +22 -12
  2. package/config/skills-hash-ledger.v1.json +7 -1
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/config/skills-manifest.json +13 -7
  7. package/dist/core/agents/agent-effort-policy.js +15 -13
  8. package/dist/core/agents/native-worker-backend-router.js +13 -8
  9. package/dist/core/codex-hooks/codex-hook-managed-install.js +56 -2
  10. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
  11. package/dist/core/codex-native/core-skill-manifest.js +4 -4
  12. package/dist/core/decisions/cli.js +9 -2
  13. package/dist/core/decisions/config.js +7 -0
  14. package/dist/core/decisions/integration.js +54 -12
  15. package/dist/core/decisions/policy.js +27 -4
  16. package/dist/core/decisions/questions.js +39 -9
  17. package/dist/core/decisions/routing.js +15 -10
  18. package/dist/core/decisions/types.js +11 -25
  19. package/dist/core/doctor/current-project-guidance.js +38 -1
  20. package/dist/core/doctor/skill-legacy-surface.js +2 -1
  21. package/dist/core/hooks-runtime/hook-context.js +1 -1
  22. package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
  23. package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
  24. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
  25. package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
  26. package/dist/core/hooks-runtime/subagent-context.js +13 -3
  27. package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
  28. package/dist/core/hooks-runtime.js +46 -8
  29. package/dist/core/init/skills/inventory.js +5 -1
  30. package/dist/core/init/skills.js +4 -4
  31. package/dist/core/init.js +39 -7
  32. package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
  33. package/dist/core/pipeline-internals/runtime-core.js +2 -2
  34. package/dist/core/provider/model-router.js +8 -5
  35. package/dist/core/recallpulse/policy.js +2 -2
  36. package/dist/core/recallpulse.js +3 -3
  37. package/dist/core/release/gate-affected-globs.js +0 -1
  38. package/dist/core/research/mock-result.js +2 -2
  39. package/dist/core/research/research-adversarial-review.js +7 -7
  40. package/dist/core/research/research-claim-synthesizer.js +3 -3
  41. package/dist/core/research/research-falsification-runner.js +3 -3
  42. package/dist/core/research/research-super-search.js +5 -2
  43. package/dist/core/research/research-synthesis-writer.js +2 -2
  44. package/dist/core/research.js +5 -5
  45. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  46. package/dist/core/routes.js +10 -10
  47. package/dist/core/runtime/task-profile.js +9 -5
  48. package/dist/core/subagents/model-policy.js +27 -45
  49. package/dist/core/subagents/model-tiers.js +153 -0
  50. package/dist/core/subagents/naruto-command-args.js +3 -3
  51. package/dist/core/subagents/naruto-help-contract.js +18 -13
  52. package/dist/core/subagents/naruto-host-credentials.js +8 -13
  53. package/dist/core/subagents/official-subagent-config.js +11 -7
  54. package/dist/core/subagents/official-subagent-preparation.js +12 -8
  55. package/dist/core/subagents/official-subagent-prompt.js +33 -33
  56. package/dist/core/subagents/official-subagent-runner.js +3 -3
  57. package/dist/core/subagents/role-model-preferences.js +26 -21
  58. package/dist/core/update/managed-permission-repair.js +462 -0
  59. package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
  60. package/dist/core/update/update-migration-state/simple-stages.js +42 -5
  61. package/dist/core/update/update-migration-state.js +63 -1
  62. package/dist/core/update-check.js +35 -8
  63. package/dist/core/version.js +1 -1
  64. package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
  65. package/dist/scripts/codex-native-gate-lib.js +3 -1
  66. package/dist/scripts/current-surface-update-e2e-check.js +1 -0
  67. package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
  68. package/dist/scripts/release-affected-selector-check.js +1 -2
  69. package/dist/scripts/typed-routing-gate-lib.js +3 -1
  70. package/package.json +1 -1
  71. package/release-gates.v2.json +2 -6
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
17
17
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
18
18
 
19
- Current package: **SKS 10.3.2**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.3.4**. Install the latest stable release from npm.
20
20
 
21
21
  [Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
22
22
 
@@ -39,7 +39,7 @@ sks bootstrap --yes
39
39
 
40
40
  | Capability | What you get |
41
41
  | --- | --- |
42
- | Focused execution | Small tasks stay lightweight; independent work can use official Codex subagents with parent-owned integration. |
42
+ | Focused execution | Answers and tiny edits stay lightweight; implementation work runs through official Codex subagents while the parent orchestrates and integrates. |
43
43
  | Project context | TriWiki indexes repository code and supplies bounded context that can be checked against source. |
44
44
  | Verification | Tests, diagnostics, and release evidence support completion claims. Security and data-integrity checks stay in place. |
45
45
  | Native controls | SKS Center brings connections, updates, MCP servers, and diagnostics together on macOS. |
@@ -138,16 +138,26 @@ configuration, transport checks, and recovery commands.
138
138
 
139
139
  ## Naruto workflow
140
140
 
141
- SKS enforces GPT-6 Astra for every managed child and varies effort by task.
142
- The parent owns decomposition, integration,
143
- and final verification; children receive bounded tasks and do not spawn children.
144
-
145
- | Work | Managed model | Effort |
146
- | --- | --- | --- |
147
- | Tiny mechanical tasks | GPT-6 Astra | low |
148
- | Exploration, large-context reads, and direct tool operation | GPT-6 Astra | medium |
149
- | Implementation | GPT-6 Astra | high |
150
- | Review, debugging, and focused judgment | GPT-6 Astra | max |
141
+ The parent orchestrates: it decomposes the task, spawns a child for each
142
+ disjoint slice, waits, and owns integration and final verification. It does not
143
+ implement slices itself. The SKS PreToolUse hook denies parent source edits
144
+ until the first child starts and while children are still running. Children
145
+ receive bounded tasks and do not spawn children.
146
+
147
+ No model family is pinned. Every child runs the newest model of the tier its
148
+ work needs, read from the Codex models cache, so a new model family is used as
149
+ soon as Codex lists it:
150
+
151
+ | Work | Tier | Effort | Today |
152
+ | --- | --- | --- | --- |
153
+ | Tiny mechanical tasks | fast | low | `gpt-6-luna` |
154
+ | Instructed implementation | balanced | low | `gpt-6-sol` |
155
+ | Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6-sol` |
156
+ | Planning, review, debugging, and focused judgment | deep | max | `gpt-6-astra` |
157
+
158
+ With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
159
+ the plan, and the context, so the parent spends no time on those choices. A
160
+ stored role-model preference on a current model wins in both modes.
151
161
 
152
162
  An active Codex task keeps the user's selected main model, effort, and service
153
163
  tier. Codex native `/goal` remains the persisted goal owner. Parallelism depends
@@ -4,6 +4,7 @@
4
4
  {
5
5
  "canonical_name": "sks",
6
6
  "trusted_sha256": [
7
+ "8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
7
8
  "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
8
9
  "905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
9
10
  "6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
@@ -324,13 +325,14 @@
324
325
  {
325
326
  "canonical_name": "sks-naruto",
326
327
  "trusted_sha256": [
328
+ "b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
329
+ "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
327
330
  "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
328
331
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
329
332
  "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
330
333
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
331
334
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
332
335
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
333
- "f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
334
336
  "49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
335
337
  ]
336
338
  },
@@ -345,6 +347,7 @@
345
347
  {
346
348
  "canonical_name": "sks-pipeline-runner",
347
349
  "trusted_sha256": [
350
+ "6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
348
351
  "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
349
352
  "fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
350
353
  "66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
@@ -372,6 +375,8 @@
372
375
  {
373
376
  "canonical_name": "sks-prompt-pipeline",
374
377
  "trusted_sha256": [
378
+ "f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
379
+ "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
375
380
  "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
376
381
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
377
382
  "480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
@@ -388,6 +393,7 @@
388
393
  {
389
394
  "canonical_name": "sks-reasoning-router",
390
395
  "trusted_sha256": [
396
+ "41e14e8d5b93d743096664869ee68c41c86e911b0bbc165bdcd10e4a45faa4b6",
391
397
  "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
392
398
  "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
393
399
  "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.3.2"
262
+ version = "10.3.4"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.3.2"
3
+ version = "10.3.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -127,7 +127,8 @@ export async function run(_command, args = [], deps = {}) {
127
127
  const doctorFix = flag(args, '--fix');
128
128
  const homeDir = path.resolve(deps.home || process.env.HOME || os.homedir());
129
129
  const globalOnly = doctorGlobalOnlySelection({ args, doctorFix, root, home: homeDir }).global_only;
130
- if (doctorFix) {
130
+ const doctorProfile = doctorProfileFromArgs(args, doctorFix);
131
+ if (doctorFix && doctorProfile !== 'migration') {
131
132
  const conflictScan = await scanHarnessConflicts(root);
132
133
  if (conflictScan.hard_block) {
133
134
  const blocked = {
@@ -151,7 +152,6 @@ export async function run(_command, args = [], deps = {}) {
151
152
  return blocked;
152
153
  }
153
154
  }
154
- const doctorProfile = doctorProfileFromArgs(args, doctorFix);
155
155
  if (!flag(args, '--json')) {
156
156
  cliUi.banner('doctor');
157
157
  cliUi.step(doctorFix ? 'repairing and validating' : 'validating');
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.3.2",
3
+ "package_version": "10.3.4",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
7
7
  "type": "official",
8
- "content_sha256": "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
8
+ "content_sha256": "8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
9
9
  "hash_history": [
10
+ "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
10
11
  "905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
11
12
  "6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
12
13
  "5b17aeb1484e9e2b3791bff6dcb175f30857f47e71b36eb0340e575385e8e82b"
@@ -393,14 +394,15 @@
393
394
  {
394
395
  "canonical_name": "sks-naruto",
395
396
  "type": "core",
396
- "content_sha256": "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
397
+ "content_sha256": "b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
397
398
  "hash_history": [
399
+ "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
400
+ "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
398
401
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
399
402
  "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
400
403
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
401
404
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
402
405
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
403
- "f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
404
406
  "49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
405
407
  ],
406
408
  "deprecated_aliases": []
@@ -418,8 +420,9 @@
418
420
  {
419
421
  "canonical_name": "sks-pipeline-runner",
420
422
  "type": "official",
421
- "content_sha256": "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
423
+ "content_sha256": "6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
422
424
  "hash_history": [
425
+ "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
423
426
  "fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
424
427
  "66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
425
428
  "020ba4da2f96bcda23cee4596b1e790816780178f1917dee748ff4b289cba0e5",
@@ -451,8 +454,10 @@
451
454
  {
452
455
  "canonical_name": "sks-prompt-pipeline",
453
456
  "type": "official",
454
- "content_sha256": "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
457
+ "content_sha256": "f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
455
458
  "hash_history": [
459
+ "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
460
+ "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
456
461
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
457
462
  "480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
458
463
  "5dc8524de9bad66cef133ea5ff2a8052331bd12a7ec85695f727d0d2c8523145"
@@ -471,8 +476,9 @@
471
476
  {
472
477
  "canonical_name": "sks-reasoning-router",
473
478
  "type": "official",
474
- "content_sha256": "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
479
+ "content_sha256": "41e14e8d5b93d743096664869ee68c41c86e911b0bbc165bdcd10e4a45faa4b6",
475
480
  "hash_history": [
481
+ "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
476
482
  "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
477
483
  "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
478
484
  "1595beabf2d31397217311fa1323bd62c801cf6c87db9029ff544c457efec8bb",
@@ -1,6 +1,7 @@
1
1
  import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
2
2
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
3
- import { ASTRA_SUBAGENT_MODEL, decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
3
+ import { decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
4
+ import { MODEL_TIERS, resolveLatestModelTiers } from '../subagents/model-tiers.js';
4
5
  export function decideAgentEffort(input = {}) {
5
6
  return decideOfficialSubagentModel(input);
6
7
  }
@@ -38,14 +39,14 @@ export function decideOfficialSubagentModel(input = {}) {
38
39
  reason: `managed_role:${managedRole.codex_name}:${profile.policy}`,
39
40
  dynamic: true,
40
41
  escalation_triggers: [
41
- 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects Astra Max',
42
+ 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
42
43
  'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
43
44
  'requested model/effort profile unavailable blocks instead of silently falling back'
44
45
  ],
45
46
  downshift_triggers: [
46
- 'instructed UI, logic, backend, or native implementation selects Astra Low',
47
- 'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects Astra Medium',
48
- 'tiny short-context mechanical search/typing/rename work selects Astra Low'
47
+ 'instructed UI, logic, backend, or native implementation selects the balanced tier',
48
+ 'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects the context tier',
49
+ 'tiny short-context mechanical search/typing/rename work selects the balanced tier'
49
50
  ]
50
51
  };
51
52
  }
@@ -92,14 +93,14 @@ export function decideOfficialSubagentModel(input = {}) {
92
93
  reason: routed.reason,
93
94
  dynamic: true,
94
95
  escalation_triggers: [
95
- 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects Astra Max',
96
+ 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
96
97
  'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
97
98
  'requested model/effort profile unavailable blocks instead of silently falling back'
98
99
  ],
99
100
  downshift_triggers: [
100
- 'instructed UI, logic, backend, or native implementation selects Astra Low',
101
- 'long-context, Browser/Chrome, Computer Use, or image-generation execution selects Astra Medium',
102
- 'tiny short-context mechanical work selects Astra Low'
101
+ 'instructed UI, logic, backend, or native implementation selects the balanced tier',
102
+ 'long-context, Browser/Chrome, Computer Use, or image-generation execution selects the context tier',
103
+ 'tiny short-context mechanical work selects the balanced tier'
103
104
  ]
104
105
  };
105
106
  }
@@ -118,21 +119,22 @@ export function buildAgentEffortPolicy(roster = {}) {
118
119
  reason: agent.reasoning_reason,
119
120
  dynamic: true
120
121
  })) : [];
122
+ const latest = resolveLatestModelTiers();
121
123
  return {
122
124
  schema: 'sks.agent-effort-policy.v1',
123
125
  policy_version: 1,
124
126
  dynamic: true,
125
127
  service_tier: 'fast',
126
128
  model_catalog_policy: 'official_subagent_four_profile_matrix',
127
- model_constraint: [ASTRA_SUBAGENT_MODEL],
128
- model_tiers: ['low', 'medium', 'high', 'max'].map((effort) => `${ASTRA_SUBAGENT_MODEL}-${effort}`),
129
+ model_constraint: [...new Set(MODEL_TIERS.map((tier) => latest.models[tier]))],
130
+ model_tiers: MODEL_TIERS.map((tier) => `${tier}:${latest.models[tier]}-${latest.efforts[tier]}`),
129
131
  allowed_efforts: ['low', 'medium', 'high', 'max'],
130
- model_effort_capability: codexModelEffortCapability({ model: ASTRA_SUBAGENT_MODEL }),
132
+ model_effort_capability: codexModelEffortCapability({ model: latest.models.deep }),
131
133
  max_agents: roster.max_agents || 20,
132
134
  agent_count: roster.agent_count || decisions.length,
133
135
  concurrency: roster.concurrency || decisions.length,
134
136
  decisions,
135
- rule: 'All child agents use GPT-6 Astra: Low for tiny short-context mechanical work and instructed implementation, Medium for reads and tool execution, and Max for focused judgment. The parent keeps its user-selected model, reasoning effort, and service tier; explicit Astra effort preferences may override role defaults.'
137
+ rule: 'Every child uses the newest model of the tier its work needs: fast for tiny mechanical work, balanced for instructed implementation, context for reads and tool execution, and deep for focused judgment. With Jev mode on, Jev picks the tier for each spawn. The parent keeps its user-selected model, reasoning effort, and service tier; stored role preferences may override role defaults.'
136
138
  };
137
139
  }
138
140
  export function reasoningProfileName(effort) {
@@ -10,7 +10,8 @@ import { leanEngineeringCompactText, leanPolicyReference } from '../lean-enginee
10
10
  import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
11
11
  import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
12
12
  import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-shield.js';
13
- import { ASTRA_SUBAGENT_MODEL, decideSubagentModel } from '../subagents/model-policy.js';
13
+ import { decideSubagentModel } from '../subagents/model-policy.js';
14
+ import { latestTierModelSet } from '../subagents/model-tiers.js';
14
15
  export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
15
16
  export async function runNativeWorkerBackendRouter(input) {
16
17
  const root = path.resolve(input.agentRoot);
@@ -241,15 +242,19 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
241
242
  const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
242
243
  const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
243
244
  const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
244
- const savedAstraEffort = input.agent?.routed_model_policy === 'user_role_model_preference'
245
- && input.agent?.routed_model === ASTRA_SUBAGENT_MODEL
245
+ const currentModels = latestTierModelSet();
246
+ const savedPreferenceModel = input.agent?.routed_model_policy === 'user_role_model_preference'
247
+ && currentModels.has(String(input.agent?.routed_model || ''))
248
+ ? String(input.agent?.routed_model)
249
+ : '';
250
+ const savedAstraEffort = savedPreferenceModel
246
251
  ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort)
247
252
  : null;
248
253
  const jevModel = !explicitModel && input.agent?.routed_model_policy === 'jev_sealed_routing'
249
254
  ? String(input.agent.routed_model || '').trim()
250
255
  : '';
251
256
  const jevEffort = jevModel ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort) : null;
252
- const selectedModel = explicitModel || jevModel;
257
+ const selectedModel = explicitModel || jevModel || savedPreferenceModel;
253
258
  const taskPolicy = decideSubagentModel({ title: taskKindText, description: riskText, role: input.agent?.role });
254
259
  const routed = narutoOnly
255
260
  ? await routeModel(category, {
@@ -263,14 +268,14 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
263
268
  ...(selectedModel ? { model: selectedModel } : {})
264
269
  })
265
270
  : {
266
- model: ASTRA_SUBAGENT_MODEL,
271
+ model: (explicitModel && currentModels.has(explicitModel) ? explicitModel : '') || savedPreferenceModel || taskPolicy.model,
267
272
  reasoning: explicitReasoning || savedAstraEffort || taskPolicy.modelReasoningEffort,
268
273
  serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
269
274
  };
270
275
  const blockers = [
271
276
  ...(narutoOnly && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
272
- ...(explicitNarutoModelInvalid ? ['naruto_worker_model_outside_gpt_5_6_family'] : []),
273
- ...(!narutoOnly && explicitModel && explicitModel !== ASTRA_SUBAGENT_MODEL ? ['subagent_model_must_be_astra'] : []),
277
+ ...(explicitNarutoModelInvalid ? ['naruto_worker_model_not_current'] : []),
278
+ ...(!narutoOnly && explicitModel && !currentModels.has(explicitModel) ? ['subagent_model_must_be_current'] : []),
274
279
  ...(explicitNarutoReasoningInvalid ? ['naruto_reasoning_override_invalid'] : []),
275
280
  ...(explicitNarutoTierInvalid ? ['naruto_service_tier_override_invalid'] : []),
276
281
  ...(narutoOnly && explicitReasoning && explicitReasoning !== routed.reasoning ? ['naruto_reasoning_override_conflicts_with_policy'] : []),
@@ -306,7 +311,7 @@ export function narutoWorkerBackendBlocker(backend, narutoRequest = true) {
306
311
  if (!narutoRequest)
307
312
  return null;
308
313
  if (backend === 'process')
309
- return 'naruto_gpt_5_6_family_only_process_backend_forbidden';
314
+ return 'naruto_process_backend_forbidden';
310
315
  return null;
311
316
  }
312
317
  function normalizeBackend(value) {
@@ -1,5 +1,7 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
1
3
  import path from 'node:path';
2
- import { ensureDir, nowIso, readText, writeTextAtomic } from '../fsx.js';
4
+ import { ensureDir, nowIso, packageRoot, readText, writeTextAtomic } from '../fsx.js';
3
5
  import { CODEX_HOOK_EVENTS } from '../codex-compat/codex-hook-events.js';
4
6
  import { postToolEvidenceEnabled } from '../verification-profile.js';
5
7
  import { buildCodexCommandHookToml, matcherApplies } from './codex-hook-config-writer.js';
@@ -22,7 +24,7 @@ export async function installManagedCodexHooks(root, opts = {}) {
22
24
  const requirementsPath = path.resolve(root, opts.requirementsPath || path.join('.codex', 'requirements.toml'));
23
25
  const scriptPath = path.join(managedDir, 'sks-managed-hook.sh');
24
26
  const tomlPath = path.join(managedDir, 'sks-managed-hooks.toml');
25
- const binCommand = opts.binCommand || 'sks hook';
27
+ const binCommand = opts.binCommand || await defaultManagedHookCommand();
26
28
  const installedEvents = CODEX_HOOK_EVENTS.filter((event) => event !== 'PostToolUse' || postToolEvidenceEnabled(root));
27
29
  const hooksToml = installedEvents.map((event) => buildCodexCommandHookToml({
28
30
  event,
@@ -77,6 +79,32 @@ export async function installManagedCodexHooks(root, opts = {}) {
77
79
  blockers: actual?.blockers || []
78
80
  };
79
81
  }
82
+ export async function defaultManagedHookCommand() {
83
+ const entrypoint = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
84
+ if (!(await isRegularFile(entrypoint)))
85
+ return 'sks hook';
86
+ return `${shellQuote(process.execPath)} ${shellQuote(entrypoint)} hook`;
87
+ }
88
+ export async function retargetLiveManagedHookScript(env = process.env) {
89
+ const home = env.HOME || os.homedir();
90
+ const codexHome = path.resolve(env.CODEX_HOME || path.join(home, '.codex'));
91
+ const requirementsPath = path.join(codexHome, 'requirements.toml');
92
+ const requirements = await readText(requirementsPath, '');
93
+ const configuredDir = managedDirFromRequirements(String(requirements || ''), requirementsPath);
94
+ const managedDir = configuredDir || path.join(codexHome, 'managed-hooks');
95
+ const resolvedDir = path.resolve(managedDir);
96
+ const script = path.join(resolvedDir, 'sks-managed-hook.sh');
97
+ if (!pathIsInside(codexHome, script)) {
98
+ return { ok: false, status: 'absent', script, command: null };
99
+ }
100
+ if (!(await isRegularFile(script))) {
101
+ return { ok: true, status: 'absent', script, command: null };
102
+ }
103
+ const command = await defaultManagedHookCommand();
104
+ await writeTextAtomic(script, managedHookScript(command));
105
+ await chmodExecutable(script);
106
+ return { ok: true, status: 'rewritten', script, command };
107
+ }
80
108
  function managedHookScript(binCommand) {
81
109
  return [
82
110
  '#!/usr/bin/env sh',
@@ -86,6 +114,32 @@ function managedHookScript(binCommand) {
86
114
  `exec ${binCommand} "$subcommand" "$@"`
87
115
  ].join('\n') + '\n';
88
116
  }
117
+ function shellQuote(value) {
118
+ if (/[\0\r\n]/.test(value))
119
+ throw new Error('hook_command_path_invalid');
120
+ return `'${value.replaceAll("'", `'\\''`)}'`;
121
+ }
122
+ function managedDirFromRequirements(text, requirementsPath) {
123
+ const match = text.match(/^\s*managed_dir\s*=\s*(.+)\s*$/m);
124
+ if (!match?.[1])
125
+ return null;
126
+ const raw = match[1].trim().replace(/^['"]|['"]$/g, '');
127
+ if (!raw || raw.includes('\0'))
128
+ return null;
129
+ return path.resolve(path.dirname(requirementsPath), raw);
130
+ }
131
+ function pathIsInside(root, target) {
132
+ const relative = path.relative(path.resolve(root), path.resolve(target));
133
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
134
+ }
135
+ async function isRegularFile(file) {
136
+ try {
137
+ return (await fs.stat(file)).isFile();
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
89
143
  function mergeRequirementsToml(existing, opts) {
90
144
  const withoutManagedOnly = String(existing || '')
91
145
  .replace(/^\s*allow_managed_hooks_only\s*=.*$/gm, '')
@@ -18,6 +18,7 @@ const SETTINGS_V2_KEYS = new Set([
18
18
  'schema', 'listen_host', 'listen_port', 'provider_registry', 'route_policy',
19
19
  'provider_session_pins', 'client_capability_sha256', 'allowed_origins',
20
20
  'connect_timeout_ms', 'idle_timeout_ms', 'official_passthrough',
21
+ 'auth_priority_enabled',
21
22
  ]);
22
23
  const TRANSFERABLE_V1_KEYS = [
23
24
  'listen_host', 'listen_port', 'allowed_origins', 'connect_timeout_ms', 'idle_timeout_ms',
@@ -14,13 +14,13 @@ const CORE_SKILL_DEFINITIONS = [
14
14
  display_name: 'naruto',
15
15
  route: '$Naruto',
16
16
  purpose: 'run a Codex official subagent workflow with official agent threads while parent integration remains owner.',
17
- discovery: 'Run $sks-naruto for explicit parallel official subagents.',
18
- when: 'Use when the user explicitly invokes $Naruto or the selected route requires bounded parallel delegation.',
19
- workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out on the Astra Low/Astra Medium lanes. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. Use gpt-6-astra for every child, preserving parent settings and ignoring child-model overrides. Route tiny mechanical and mass shards to Astra Low, broad search and exploration shards to Astra Medium, instructed ordinary coding execution to Astra Low, and planning, analysis, review, or other judgment to Astra Max. Keep the four task-class profiles and compatibility IDs; explicit Astra effort preferences, including High, remain supported; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
17
+ discovery: 'Run $sks-naruto to delegate implementation to child subagents.',
18
+ when: 'Use when the user invokes $sks-naruto or $sks-work, or the selected route is Naruto: ordinary implementation work, where the parent orchestrates and children implement.',
19
+ workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread; the SKS PreToolUse gate denies parent-thread source edits until the first child thread starts and while children are still running, and .sneakoscope artifacts stay parent-writable. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. Every child runs the newest model of the tier its work needs (fast, balanced, context, or deep); no model family is pinned. When Jev mode is on, Jev picks the tier for each new spawn and SKS seals it. A user role preference stays authoritative. Do not choose child models yourself. Keep the four task-class profiles and compatibility IDs; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
20
20
  safety: 'Preserve user-authored content, inherit the parent permission mode, do not spawn nested subagents, do not inject the full pack or the full TriWiki context into every child, and do not fall back to another model, process runtime, custom scheduler, or worker pool. The historical Naruto process runtime is removed; stop with explicit blocker evidence when the official path is unavailable.',
21
21
  cli: 'sks naruto run "<task>" [--agents N] [--max-threads N] [--json]; sks naruto status|subagents|proof [--mission <id>] [--json]; sks naruto parent-summary --mission <id> --stdin [--json]',
22
22
  evidence: 'subagent-plan.json, subagent-events.jsonl, subagent-parent-summary.json, subagent-evidence.json, naruto-summary.json, and naruto-gate.json.',
23
- fallback: 'Return explicit official-subagent availability blockers and continue parent-owned only when the sealed task still has meaningful in-scope work; never fabricate process, PID, or subagent evidence.'
23
+ fallback: 'When official subagents are unavailable, report the explicit availability blocker; the orchestration gate releases parent edits only after its bounded denials, and that release is recorded. Never fabricate process, PID, or subagent evidence.'
24
24
  },
25
25
  {
26
26
  id: 'sks-core-qa-loop',
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { printJson } from '../../cli/output.js';
4
4
  import { nowIso, writeJsonAtomic } from '../fsx.js';
5
5
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
6
- import { jevEnabled, readDecisionConfig, writeDecisionConfig } from './config.js';
6
+ import { jevEnabled, readDecisionConfig, writeDecisionConfig, jevCapabilityActive } from './config.js';
7
7
  import { buildEvaluationReport } from './evaluation.js';
8
8
  import { OPENROUTER_DECISIONS_ENDPOINT, OPENROUTER_DECISIONS_MODEL, requestOpenRouterDecision } from './openrouter.js';
9
9
  import { buildDecisionBundle } from './questions.js';
@@ -152,7 +152,14 @@ export async function statusReport(env) {
152
152
  source: resolved.source,
153
153
  preview: resolved.key_preview
154
154
  },
155
- capabilities: config.capabilities,
155
+ capabilities: {
156
+ context: { ...config.capabilities.context, ready: jevCapabilityActive(config, 'context') },
157
+ plan: { ...config.capabilities.plan, ready: jevCapabilityActive(config, 'plan') },
158
+ recovery: config.capabilities.recovery
159
+ },
160
+ decision_points: jevEnabled(config)
161
+ ? ['turn_tier', 'spawn_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation']
162
+ : [],
156
163
  recovery: RECOVERY_CAPABILITY,
157
164
  nextStep,
158
165
  notes: [
@@ -70,6 +70,13 @@ export async function writeDecisionConfig(patch, env = process.env) {
70
70
  export function jevEnabled(config) {
71
71
  return config.mode === 'jev' && config.consentCloud === true;
72
72
  }
73
+ export function jevCapabilityActive(config, capability) {
74
+ if (!jevEnabled(config))
75
+ return false;
76
+ if (capability === 'recovery')
77
+ return config.capabilities.recovery.ready;
78
+ return true;
79
+ }
73
80
  function normalizeConfig(raw) {
74
81
  const fallback = defaultDecisionConfig();
75
82
  if (!isRecord(raw) || raw.schema !== DECISION_CONFIG_SCHEMA)
@@ -1,6 +1,6 @@
1
1
  import { sha256 } from '../fsx.js';
2
2
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
3
- import { jevEnabled, readDecisionConfig } from './config.js';
3
+ import { jevCapabilityActive, jevEnabled, readDecisionConfig } from './config.js';
4
4
  import { requestOpenRouterDecision } from './openrouter.js';
5
5
  import { compileDecision } from './policy.js';
6
6
  import { buildDecisionBundle, validatePlanCoverage } from './questions.js';
@@ -8,7 +8,7 @@ import { applyRecoveryEffect, listProductionRecoveryCandidates } from './recover
8
8
  import { assembleRoutingSelection, buildRoutingCandidates } from './routing.js';
9
9
  import { buildDecisionReceipt } from './receipt.js';
10
10
  import { applyOptionalContextSelection, graphFileDigest, hydrateContextCandidates, sourceSnapshotDigest } from './state.js';
11
- import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
11
+ import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE } from './types.js';
12
12
  let testOverrides = null;
13
13
  const appliedEffects = new Set();
14
14
  export function setDecisionTestOverrides(overrides) {
@@ -43,16 +43,16 @@ async function decideOfficialSubagentPreparationInner(input, started) {
43
43
  overrides?.observe?.({ mode: config.mode, eligible: false, compiled: missing.compiled, receipt: missing.receipt });
44
44
  return missing;
45
45
  }
46
- const contextCandidates = config.capabilities.context.ready
46
+ const contextCandidates = jevCapabilityActive(config, 'context')
47
47
  ? await hydrateContextCandidates(input.root, input.attention, input.changedPaths || [])
48
48
  : [];
49
- const planCandidates = config.capabilities.plan.ready && input.requestedSource === 'automatic'
49
+ const planCandidates = jevCapabilityActive(config, 'plan') && input.requestedSource === 'automatic'
50
50
  ? buildAutomaticPlanCandidates(input)
51
51
  : [];
52
52
  const routingCandidates = input.requestedSource === 'automatic'
53
53
  ? buildRoutingCandidates({ roles: input.routingRoles || [] })
54
54
  : [];
55
- const recoveryCandidates = config.capabilities.recovery.ready
55
+ const recoveryCandidates = jevCapabilityActive(config, 'recovery')
56
56
  ? [...listProductionRecoveryCandidates()]
57
57
  : [];
58
58
  const optionalContext = contextCandidates.filter((row) => !row.pinned && row.excerpt);
@@ -134,7 +134,7 @@ export async function consultJevTurnModel(input) {
134
134
  const overrides = activeOverrides();
135
135
  const env = input.env || process.env;
136
136
  const config = overrides?.config ?? await readDecisionConfig(env);
137
- const none = (reason) => ({ called: false, model: null, effort: null, reason });
137
+ const none = (reason) => ({ called: false, model: null, effort: null, tier: null, reason });
138
138
  if (!jevEnabled(config))
139
139
  return none('off');
140
140
  const resolved = await resolveOpenRouterApiKey({ env });
@@ -159,14 +159,56 @@ export async function consultJevTurnModel(input) {
159
159
  ...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
160
160
  });
161
161
  if (!transport.ok)
162
- return { called: true, model: null, effort: null, reason: transport.reason };
162
+ return { called: true, model: null, effort: null, tier: null, reason: transport.reason };
163
163
  const compiled = compileDecision(bundle, transport.response);
164
164
  if (compiled.kind !== 'apply')
165
- return { called: true, model: null, effort: null, reason: compiled.reason };
166
- const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, model: effect.model }] : [])));
165
+ return { called: true, model: null, effort: null, tier: null, reason: compiled.reason };
166
+ const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, tier: effect.tier }] : [])));
167
167
  const model = selected?.models[roleId] || null;
168
- const sealed = model ? sealedRoutingModel(model) : null;
169
- return { called: true, model: sealed?.id || null, effort: sealed?.effort || null, reason: sealed ? 'applied' : 'keep_baseline' };
168
+ const effort = selected?.efforts[roleId] || null;
169
+ const tier = selected?.tiers[roleId] || null;
170
+ if (!model || !effort || !tier)
171
+ return { called: true, model: null, effort: null, tier: null, reason: 'keep_baseline' };
172
+ return { called: true, model, effort, tier, reason: 'applied' };
173
+ }
174
+ export async function consultJevToolDelegation(input) {
175
+ const overrides = activeOverrides();
176
+ const env = input.env || process.env;
177
+ const config = overrides?.config ?? await readDecisionConfig(env);
178
+ const none = (reason) => ({ called: false, choice: null, reason });
179
+ if (!jevEnabled(config))
180
+ return none('off');
181
+ const resolved = await resolveOpenRouterApiKey({ env });
182
+ if (!resolved.key)
183
+ return none('missing_key');
184
+ const goal = String(input.missionGoal || '').trim();
185
+ const toolName = String(input.toolName || '').trim();
186
+ if (!goal || !toolName)
187
+ return none('empty_candidate');
188
+ const targets = input.targets.map((row) => String(row || '').trim()).filter(Boolean);
189
+ const bundle = buildDecisionBundle({
190
+ projectId: sha256(input.root).slice(0, 32),
191
+ workflowRunId: 'delegation',
192
+ workflowRevision: 'delegation',
193
+ sourceDigest: sha256(JSON.stringify([toolName, targets])).slice(0, 32),
194
+ graphDigest: null,
195
+ goal,
196
+ delegationCandidate: { toolName, targets, missionGoal: goal }
197
+ });
198
+ const transport = await requestOpenRouterDecision(bundle, {
199
+ env,
200
+ deadlineMs: DESIGN_DEFAULTS.deadlineMs,
201
+ ...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
202
+ });
203
+ if (!transport.ok)
204
+ return { called: true, choice: null, reason: transport.reason };
205
+ const compiled = compileDecision(bundle, transport.response);
206
+ if (compiled.kind !== 'apply')
207
+ return { called: true, choice: null, reason: compiled.reason };
208
+ const effect = compiled.effects.find((row) => row.kind === 'select_delegation');
209
+ if (!effect || effect.kind !== 'select_delegation')
210
+ return { called: true, choice: null, reason: 'keep_baseline' };
211
+ return { called: true, choice: effect.choice, reason: 'applied' };
170
212
  }
171
213
  export function effectAlreadyConsumed(identity) {
172
214
  return appliedEffects.has(identity);
@@ -210,7 +252,7 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
210
252
  evidence.push(`plan:${selectedPlan.id}`);
211
253
  }
212
254
  if (effect.kind === 'select_routing') {
213
- routingEffects.push({ roleId: effect.roleId, model: effect.model });
255
+ routingEffects.push({ roleId: effect.roleId, tier: effect.tier });
214
256
  }
215
257
  if (effect.kind === 'omit_role') {
216
258
  omittedRoutingRoles.push(effect.roleId);