@whittlelabs/sifter 0.30.16 → 0.30.18

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 (4) hide show
  1. package/README.md +18 -0
  2. package/bin.js +117 -32
  3. package/bin.js.map +2 -2
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -69,6 +69,22 @@ Once the backend answers, one more line names the page it sent you to, which is
69
69
 
70
70
  Pairing writes credentials to `~/.sifter/<profile>/config.json` (mode 0600), or `~/.sifter/config.json` for the default profile.
71
71
 
72
+ The environment also decides where a review may fetch your standards documents from: that environment's Sift API and nowhere else. A Sifter paired with production allows `https://api.sift.whittlelabs.com`; `stage` and `test` allow their own `api.sift` host, and `studio` allows `http://localhost:3008`. The Sifter finds its environment from the Jobs URL in its `sifter.yaml`, so an existing pairing needs nothing re-run. `run` and `doctor` print the list in use on their `overlay-hosts` line:
73
+
74
+ ```
75
+ PASS overlay-hosts https://api.sift.whittlelabs.com
76
+ ```
77
+
78
+ A backend this build does not know gets no default, and `overlay-hosts` warns that reviews would run without standards. Name the origin that backend signs standards URLs with under the `claude-code` executor in `sifter.yaml`. Your own list always replaces the environment's:
79
+
80
+ ```yaml
81
+ executors:
82
+ claude-code:
83
+ type: claude-code
84
+ overlayAllowedHosts:
85
+ - http://box.local:3008
86
+ ```
87
+
72
88
  ## Run
73
89
 
74
90
  ```bash
@@ -118,6 +134,8 @@ Sixteen checks over everything the Sifter needs before it can review anything: a
118
134
 
119
135
  `init` and `run` run the relevant subset themselves, so `doctor` is for when the Sifter looks healthy and nothing is happening.
120
136
 
137
+ The login check asks the CLI itself (`claude auth status`), so a login kept in the macOS Keychain counts, and it asks with the settings a review loads. A signed-out CLI fails it with the remedy `claude auth login`; a signed-in one passes as, for example, `signed in (claude.ai, max; not verified)`, because only `--deep` proves the login works. It never prints the email or organization the CLI reports. A `claude` too old to have `auth status` is checked the old way, by looking for its credentials file.
138
+
121
139
  ```bash
122
140
  whittle-sifter doctor --deep # also spend one real turn proving the claude login works
123
141
  whittle-sifter doctor --json # the verdicts as JSON, and nothing else
package/bin.js CHANGED
@@ -12261,6 +12261,8 @@ var require_environment = __commonJS({
12261
12261
  Object.defineProperty(exports2, "__esModule", { value: true });
12262
12262
  exports2.resolveEnvironment = resolveEnvironment;
12263
12263
  exports2.applyEnvironment = applyEnvironment;
12264
+ exports2.environmentPolledBy = environmentPolledBy;
12265
+ exports2.executorDefaultsFor = executorDefaultsFor;
12264
12266
  exports2.isWorthReporting = isWorthReporting;
12265
12267
  exports2.renderEnvironmentReport = renderEnvironmentReport;
12266
12268
  exports2.renderVerificationLine = renderVerificationLine;
@@ -12301,6 +12303,28 @@ var require_environment = __commonJS({
12301
12303
  }
12302
12304
  };
12303
12305
  }
12306
+ function environmentPolledBy(brand, jobsApiUrl) {
12307
+ const wanted = normalizeUrl(jobsApiUrl);
12308
+ for (const [name, entry] of Object.entries(brand.environments ?? {})) {
12309
+ if (normalizeUrl(entry.jobsApiUrl) === wanted)
12310
+ return name;
12311
+ }
12312
+ return null;
12313
+ }
12314
+ function executorDefaultsFor(brand, jobsApiUrl) {
12315
+ const name = environmentPolledBy(brand, jobsApiUrl);
12316
+ const tier = name === null ? void 0 : brand.environments?.[name]?.executorDefaults;
12317
+ if (!tier)
12318
+ return brand.executorDefaults;
12319
+ const merged = { ...brand.executorDefaults ?? {} };
12320
+ for (const [executor, settings] of Object.entries(tier)) {
12321
+ merged[executor] = { ...merged[executor] ?? {}, ...settings };
12322
+ }
12323
+ return merged;
12324
+ }
12325
+ function normalizeUrl(url) {
12326
+ return url.replace(/\/+$/, "").toLowerCase();
12327
+ }
12304
12328
  function isWorthReporting(resolved) {
12305
12329
  return resolved.environment !== null || resolved.keep.source === "flag" || resolved.jobs.source === "flag";
12306
12330
  }
@@ -12448,9 +12472,15 @@ var require_checks = __commonJS({
12448
12472
  const user = config.executors[WORKSPACE_EXECUTOR];
12449
12473
  if (user === void 0)
12450
12474
  return null;
12451
- const defaults = subject.brand.executorDefaults?.[WORKSPACE_EXECUTOR] ?? {};
12475
+ const defaults = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[WORKSPACE_EXECUTOR] ?? {};
12452
12476
  return { ...defaults, ...user };
12453
12477
  }
12478
+ function claudeSettingsOf(config) {
12479
+ return config.executors[WORKSPACE_EXECUTOR]?.claudeSettings;
12480
+ }
12481
+ function describeLogin(status) {
12482
+ return [status.authMethod ?? "unknown method", status.subscriptionType].filter((part) => part !== null).join(", ");
12483
+ }
12454
12484
  function runRoot(config, subject, osTmpDir) {
12455
12485
  const settings = claudeCodeSettings(config, subject);
12456
12486
  const tmpDir = settings?.tmpDir;
@@ -12485,7 +12515,7 @@ var require_checks = __commonJS({
12485
12515
  return (0, apply_1.expandHome)(subject.requestedTmpDir);
12486
12516
  }
12487
12517
  const settings = {
12488
- ...subject.brand.executorDefaults?.[WORKSPACE_EXECUTOR] ?? {},
12518
+ ...(0, environment_1.executorDefaultsFor)(subject.brand, subject.brand.keepRegistration.jobsApiUrl)?.[WORKSPACE_EXECUTOR] ?? {},
12489
12519
  ...plan.keptBlock ?? {}
12490
12520
  };
12491
12521
  const tmpDir = settings.tmpDir;
@@ -12745,6 +12775,14 @@ var require_checks = __commonJS({
12745
12775
  if (probes.ambientAnthropicKey()) {
12746
12776
  return warn("claude-auth", "ANTHROPIC_API_KEY is set and will bill that account, shadowing any interactive login", "unset ANTHROPIC_API_KEY if you meant to use your Claude Code login");
12747
12777
  }
12778
+ const settingSources = claudeSettingsOf(config) === "user" ? "user" : "";
12779
+ const status = await probes.claudeAuthStatus(settingSources);
12780
+ if (status !== null) {
12781
+ if (!status.loggedIn) {
12782
+ return fail("claude-auth", "the claude CLI is not signed in", settingSources === "" ? "run `claude auth login`; if your login or provider is configured in Claude Code settings (apiKeyHelper, or an env block), set `claudeSettings: user` on the claude-code executor instead" : "run `claude auth login`");
12783
+ }
12784
+ return pass("claude-auth", `signed in (${describeLogin(status)}; not verified)`);
12785
+ }
12748
12786
  const store = await probes.claudeCredentialStore();
12749
12787
  if (store === null) {
12750
12788
  return warn("claude-auth", "no credential store found for the claude CLI (a macOS login may live in the Keychain, which this cannot read)", "run `claude` once and sign in, or run doctor --deep to spend one turn proving it");
@@ -12823,7 +12861,8 @@ var require_checks = __commonJS({
12823
12861
  const hosts = settings.overlayAllowedHosts;
12824
12862
  const origins = Array.isArray(hosts) ? hosts.filter((h) => typeof h === "string") : [];
12825
12863
  if (origins.length === 0) {
12826
- return warn("overlay-hosts", "the overlay allowlist is empty, so every review will run standards-blind", `add overlayAllowedHosts to the ${WORKSPACE_EXECUTOR} executor, or re-run init to restore the defaults`);
12864
+ const fallback = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[WORKSPACE_EXECUTOR]?.overlayAllowedHosts;
12865
+ return warn("overlay-hosts", "the overlay allowlist is empty, so every review will run standards-blind", Array.isArray(fallback) && fallback.length > 0 ? `remove overlayAllowedHosts from the ${WORKSPACE_EXECUTOR} executor to use this tier's default` : `add overlayAllowedHosts to the ${WORKSPACE_EXECUTOR} executor, naming the origin your backend signs standards URLs with (this build names none for Jobs at ${(0, environment_1.hostOf)(config.auth.jobsApiUrl)})`);
12827
12866
  }
12828
12867
  return pass("overlay-hosts", origins.join(", "));
12829
12868
  });
@@ -12904,7 +12943,7 @@ var require_checks = __commonJS({
12904
12943
  };
12905
12944
  function mergedExecutorConfigs(config, subject) {
12906
12945
  const user = config.executors;
12907
- const defaults = subject.brand.executorDefaults;
12946
+ const defaults = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl);
12908
12947
  if (!defaults)
12909
12948
  return user;
12910
12949
  const merged = {};
@@ -21357,6 +21396,7 @@ var require_probes = __commonJS({
21357
21396
  };
21358
21397
  Object.defineProperty(exports2, "__esModule", { value: true });
21359
21398
  exports2.createProbes = createProbes;
21399
+ exports2.parseClaudeAuthStatus = parseClaudeAuthStatus;
21360
21400
  var child_process_1 = require("child_process");
21361
21401
  var fs_1 = require("fs");
21362
21402
  var promises_1 = require("fs/promises");
@@ -21575,6 +21615,10 @@ var require_probes = __commonJS({
21575
21615
  const candidate = path_1.default.join(configDir, ".credentials.json");
21576
21616
  return (0, fs_1.existsSync)(candidate) ? candidate : null;
21577
21617
  },
21618
+ async claudeAuthStatus(settingSources) {
21619
+ const outcome = await runForAnyExit("claude", ["--setting-sources", settingSources, "auth", "status", "--json"], VERSION_TIMEOUT_MS);
21620
+ return outcome === null ? null : parseClaudeAuthStatus(outcome);
21621
+ },
21578
21622
  ambientAnthropicKey: () => (process.env.ANTHROPIC_API_KEY ?? "").length > 0,
21579
21623
  async claudeRoundTrip() {
21580
21624
  const output = await runForOutput(
@@ -21595,13 +21639,37 @@ var require_probes = __commonJS({
21595
21639
  const outcome = await runCapturing(bin, args, timeoutMs);
21596
21640
  return outcome.ok ? outcome.stdout : null;
21597
21641
  }
21642
+ async function runForAnyExit(bin, args, timeoutMs) {
21643
+ const outcome = await runCapturing(bin, args, timeoutMs);
21644
+ if (outcome.ok)
21645
+ return outcome.stdout;
21646
+ return outcome.exited ? outcome.stdout : null;
21647
+ }
21648
+ function parseClaudeAuthStatus(stdout) {
21649
+ let parsed;
21650
+ try {
21651
+ parsed = JSON.parse(stdout);
21652
+ } catch {
21653
+ return null;
21654
+ }
21655
+ if (parsed === null || typeof parsed !== "object")
21656
+ return null;
21657
+ const { loggedIn, authMethod, subscriptionType } = parsed;
21658
+ if (typeof loggedIn !== "boolean")
21659
+ return null;
21660
+ return {
21661
+ loggedIn,
21662
+ authMethod: typeof authMethod === "string" && authMethod.length > 0 ? authMethod : null,
21663
+ subscriptionType: typeof subscriptionType === "string" && subscriptionType.length > 0 ? subscriptionType : null
21664
+ };
21665
+ }
21598
21666
  function runCapturing(bin, args, timeoutMs) {
21599
21667
  return new Promise((resolve) => {
21600
21668
  let child;
21601
21669
  try {
21602
21670
  child = (0, child_process_1.spawn)(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
21603
21671
  } catch (err) {
21604
- resolve({ ok: false, reason: describe(err) });
21672
+ resolve({ ok: false, reason: describe(err), exited: false });
21605
21673
  return;
21606
21674
  }
21607
21675
  let stdout = "";
@@ -21615,7 +21683,7 @@ var require_probes = __commonJS({
21615
21683
  };
21616
21684
  const timer = setTimeout(() => {
21617
21685
  child.kill("SIGKILL");
21618
- settle({ ok: false, reason: `did not finish within ${Math.round(timeoutMs / 1e3)}s` });
21686
+ settle({ ok: false, reason: `did not finish within ${Math.round(timeoutMs / 1e3)}s`, exited: false });
21619
21687
  }, timeoutMs);
21620
21688
  child.stdout?.on("data", (chunk) => {
21621
21689
  stdout += chunk.toString("utf8");
@@ -21625,7 +21693,7 @@ var require_probes = __commonJS({
21625
21693
  });
21626
21694
  child.on("error", (err) => {
21627
21695
  clearTimeout(timer);
21628
- settle({ ok: false, reason: describe(err) });
21696
+ settle({ ok: false, reason: describe(err), exited: false });
21629
21697
  });
21630
21698
  child.on("close", (code, signal) => {
21631
21699
  clearTimeout(timer);
@@ -21635,7 +21703,8 @@ var require_probes = __commonJS({
21635
21703
  }
21636
21704
  const status = code === null ? `was killed by ${signal ?? "a signal"}` : `exited ${code}`;
21637
21705
  const said = stderr.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
21638
- settle({ ok: false, reason: said ? `${status}: ${said}` : status });
21706
+ const reason = said ? `${status}: ${said}` : status;
21707
+ settle(code === null ? { ok: false, reason, exited: false } : { ok: false, reason, exited: true, stdout: stdout.trim() });
21639
21708
  });
21640
21709
  });
21641
21710
  }
@@ -25750,6 +25819,7 @@ var require_shuttle = __commonJS({
25750
25819
  var output_gates_1 = require_output_gates();
25751
25820
  var keep_1 = require_dist2();
25752
25821
  var apply_1 = require_apply();
25822
+ var environment_1 = require_environment();
25753
25823
  var version_check_1 = require_version_check();
25754
25824
  var typed_exit_1 = require_typed_exit();
25755
25825
  var logger_1 = require_logger();
@@ -25951,7 +26021,7 @@ var require_shuttle = __commonJS({
25951
26021
  });
25952
26022
  const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
25953
26023
  registerBuiltInExecutors(registry, this.brand.executorAllowlist);
25954
- const executors = registry.build(mergeExecutorDefaults(this.brand.executorDefaults, this.config.executors));
26024
+ const executors = registry.build(mergeExecutorDefaults((0, environment_1.executorDefaultsFor)(this.brand, this.config.auth.jobsApiUrl), this.config.executors));
25955
26025
  const byStrategy = /* @__PURE__ */ new Map();
25956
26026
  for (const executor of executors) {
25957
26027
  for (const strategy of executor.strategies)
@@ -26734,8 +26804,8 @@ var require_dist5 = __commonJS({
26734
26804
  "../../packages/shuttle/dist/index.js"(exports2) {
26735
26805
  "use strict";
26736
26806
  Object.defineProperty(exports2, "__esModule", { value: true });
26737
- exports2.scaffoldedConfigPath = exports2.planScaffold = exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.environmentReportJson = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorEnvironmentError = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
26738
- exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = void 0;
26807
+ exports2.DEFAULT_REFUSAL_MESSAGE = exports2.DEFAULT_MONTHLY_PERIOD = exports2.DEFAULT_DAILY_PERIOD = exports2.SUBSTRATE_BRAND = exports2.verificationJson = exports2.resolveEnvironment = exports2.renderVerificationLine = exports2.renderEnvironmentReport = exports2.isWorthReporting = exports2.hostOf = exports2.executorDefaultsFor = exports2.environmentReportJson = exports2.environmentPolledBy = exports2.applyEnvironment = exports2.renderTemplate = exports2.expandHome = exports2.resolvePaths = exports2.SpendStore = exports2.AuditLogStore = exports2.createSpendTrackerObserver = exports2.createAuditLogObserver = exports2.createLoggerObserver = exports2.SpendCapExceeded = exports2.ObserverChain = exports2.createCustomScriptExecutor = exports2.createWebhookExecutor = exports2.createHttpApiExecutor = exports2.createClaudeCodeExecutor = exports2.ExecutorEnvironmentError = exports2.ExecutorRegistry = exports2.AgentCredentialRevokedError = exports2.runPreflight = exports2.renderLines = exports2.renderJson = exports2.printReport = exports2.hasFailure = exports2.createProbes = exports2.checksFor = exports2.PREFLIGHT_FLOORS = exports2.PREFLIGHT_EXIT_CODE = exports2.CHECKS = exports2.readResponseLatestVersion = exports2.checkResponseMinVersion = exports2.compareSemver = exports2.UpgradeRequiredError = exports2.LATEST_VERSION_HEADER = exports2.MIN_VERSION_HEADER = exports2.loadConfig = exports2.Shuttle = exports2.createCli = void 0;
26808
+ exports2.revokePairing = exports2.listPairings = exports2.PairingConfigStore = exports2.pair = exports2.scaffoldedConfigPath = exports2.planScaffold = void 0;
26739
26809
  var cli_1 = require_cli();
26740
26810
  Object.defineProperty(exports2, "createCli", { enumerable: true, get: function() {
26741
26811
  return cli_1.createCli;
@@ -26867,9 +26937,15 @@ var require_dist5 = __commonJS({
26867
26937
  Object.defineProperty(exports2, "applyEnvironment", { enumerable: true, get: function() {
26868
26938
  return environment_1.applyEnvironment;
26869
26939
  } });
26940
+ Object.defineProperty(exports2, "environmentPolledBy", { enumerable: true, get: function() {
26941
+ return environment_1.environmentPolledBy;
26942
+ } });
26870
26943
  Object.defineProperty(exports2, "environmentReportJson", { enumerable: true, get: function() {
26871
26944
  return environment_1.environmentReportJson;
26872
26945
  } });
26946
+ Object.defineProperty(exports2, "executorDefaultsFor", { enumerable: true, get: function() {
26947
+ return environment_1.executorDefaultsFor;
26948
+ } });
26873
26949
  Object.defineProperty(exports2, "hostOf", { enumerable: true, get: function() {
26874
26950
  return environment_1.hostOf;
26875
26951
  } });
@@ -26934,7 +27010,7 @@ var import_path = require("path");
26934
27010
  var import_promises = require("fs/promises");
26935
27011
  var import_yaml = __toESM(require_dist3());
26936
27012
  var import_shuttle = __toESM(require_dist5());
26937
- var buildVersion = true ? "0.30.16" : pkg.version;
27013
+ var buildVersion = true ? "0.30.18" : pkg.version;
26938
27014
  var sifterBrand = {
26939
27015
  product: {
26940
27016
  id: "sifter",
@@ -26975,24 +27051,12 @@ var sifterBrand = {
26975
27051
  git: "2.20.0",
26976
27052
  claude: "2.0.45"
26977
27053
  },
26978
- // Overlay-fetch origins for the signed standards-content URLs each Sift
26979
- // tier signs (SIFT_PUBLIC_API_URL). Without these, the executor's SSRF
26980
- // guard silently skips the standards overlay and every review runs
26981
- // standards-blind (2026-07-13). User config overrides per key.
27054
+ // The overlay-fetch allowlist is NOT here: each tier signs its standards
27055
+ // URLs with its own Sift origin (SIFT_PUBLIC_API_URL), so the origin is a
27056
+ // property of the tier and lives on its `environments` entry below. A
27057
+ // Sifter paired to production allows production's origin and nothing else.
26982
27058
  executorDefaults: {
26983
27059
  "claude-code": {
26984
- overlayAllowedHosts: [
26985
- "https://api.sift.whittlelabs.com",
26986
- "https://api.sift.stage.whittlelabs.com",
26987
- // The test tier signs overlay URLs too, and a Sifter the E2E suite
26988
- // runs on the runner reaches Sift by this host rather than by the
26989
- // in-cluster service name the hosted worker uses. Without it that
26990
- // Sifter passes its own preflight — the check asks only that the list
26991
- // is non-empty — and then runs every review standards-blind, which is
26992
- // the exact silence of 2026-07-13.
26993
- "https://api.sift.test.whittlelabs.com",
26994
- "http://localhost:3008"
26995
- ],
26996
27060
  // Transcript retention is a diagnostic switch a person flips for one
26997
27061
  // session, so it rides the environment rather than a file they would
26998
27062
  // have to remember to unset. The shared package reads no env of its
@@ -27016,22 +27080,40 @@ var sifterBrand = {
27016
27080
  // `studio` is the local dev fleet on this machine, reachable over Tailscale
27017
27081
  // at the `studio.whittlelabs.com` hostname rather than `localhost`, because
27018
27082
  // a pairing made from a phone or a second machine has to resolve too.
27083
+ //
27084
+ // Each entry also carries the origin that tier signs overlay URLs with.
27085
+ // A run matches its tier by the Jobs URL its config polls (written from the
27086
+ // pairing, and held equal to it by `pairing-environment`), so an existing
27087
+ // pairing picks up its own tier's origin without re-pairing, and a user's
27088
+ // own `overlayAllowedHosts` still wins. Without the right origin the
27089
+ // executor's SSRF guard silently skips the standards overlay and every
27090
+ // review runs standards-blind (2026-07-13); tests/overlay-hosts.test.ts
27091
+ // holds every tier to naming its own.
27019
27092
  environments: {
27020
27093
  prod: {
27021
27094
  keepApiUrl: "https://keep.whittlelabs.com",
27022
- jobsApiUrl: "https://jobs.whittlelabs.com"
27095
+ jobsApiUrl: "https://jobs.whittlelabs.com",
27096
+ executorDefaults: overlayOrigin("https://api.sift.whittlelabs.com")
27023
27097
  },
27024
27098
  stage: {
27025
27099
  keepApiUrl: "https://keep.stage.whittlelabs.com",
27026
- jobsApiUrl: "https://jobs.stage.whittlelabs.com"
27100
+ jobsApiUrl: "https://jobs.stage.whittlelabs.com",
27101
+ executorDefaults: overlayOrigin("https://api.sift.stage.whittlelabs.com")
27027
27102
  },
27028
27103
  test: {
27029
27104
  keepApiUrl: "https://keep.test.whittlelabs.com",
27030
- jobsApiUrl: "https://jobs.test.whittlelabs.com"
27105
+ jobsApiUrl: "https://jobs.test.whittlelabs.com",
27106
+ // The public host, because a Sifter the E2E suite runs on the runner
27107
+ // reaches Sift by it rather than by the in-cluster service name the
27108
+ // test tier's hosted worker uses (that worker sets its own list).
27109
+ executorDefaults: overlayOrigin("https://api.sift.test.whittlelabs.com")
27031
27110
  },
27032
27111
  studio: {
27033
27112
  keepApiUrl: "http://studio.whittlelabs.com:3007",
27034
- jobsApiUrl: "http://studio.whittlelabs.com:3005"
27113
+ jobsApiUrl: "http://studio.whittlelabs.com:3005",
27114
+ // The dev fleet's Sift signs with SIFT_PUBLIC_API_URL from its .env,
27115
+ // which is localhost.
27116
+ executorDefaults: overlayOrigin("http://localhost:3008")
27035
27117
  }
27036
27118
  },
27037
27119
  keepRegistration: {
@@ -27119,6 +27201,9 @@ var sifterBrand = {
27119
27201
  }
27120
27202
  ]
27121
27203
  };
27204
+ function overlayOrigin(origin) {
27205
+ return { "claude-code": { overlayAllowedHosts: [origin] } };
27206
+ }
27122
27207
  async function buildExecutorBlock(executor, ctx) {
27123
27208
  switch (executor) {
27124
27209
  case "claude-code": {