@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.
- package/README.md +18 -0
- package/bin.js +117 -32
- package/bin.js.map +2 -2
- 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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
26979
|
-
//
|
|
26980
|
-
//
|
|
26981
|
-
//
|
|
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": {
|