CB-539: let a worker profile run on the subscription, explicitly
Add a per-profile subscription: true opt-in that lets a claude-code worker run on the operator's Claude subscription when there is no off-subscription endpoint for it (e.g. sonnet on ccs). When set, the launcher injects neither ANTHROPIC_BASE_URL nor ANTHROPIC_AUTH_TOKEN and skips SubscriptionGuard's base_url requirement for that profile only, logging a WARN naming the profile. subscription: true alongside a baseUrl is refused as contradictory. The default (absent/false) keeps today's hard refusal unchanged; every other profile stays allowlist-checked and SubscriptionGuard is untouched.
This commit is contained in:
@@ -640,4 +640,23 @@ class BridgedConfigTest {
|
||||
assertEquals(1.0f, w.weight(), 0.0001f, "absent weight defaults to 1.0");
|
||||
assertNull(w.maxLoad(), "absent maxLoad defaults to unlimited (null)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subscriptionFlagBindsAndDefaultsFalse(@TempDir Path dir) throws Exception {
|
||||
Path f = dir.resolve("subscription.yaml");
|
||||
Files.writeString(f, """
|
||||
workers:
|
||||
sonnet:
|
||||
subscription: true
|
||||
argv: ["ccs", "sonnet"]
|
||||
opted:
|
||||
baseUrl: http://gx10.gw:8000
|
||||
""");
|
||||
|
||||
BridgedConfig cfg = BridgedConfig.load(f);
|
||||
assertTrue(cfg.workerProfiles().get("sonnet").isSubscription(),
|
||||
"subscription: true binds as an explicit opt-in");
|
||||
assertFalse(cfg.workerProfiles().get("opted").isSubscription(),
|
||||
"a profile without the key stays off-subscription (the default)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.ltms.bridged.worker;
|
||||
|
||||
import dev.ltms.bridged.config.BridgedConfig;
|
||||
import dev.ltms.bridged.guard.GuardException;
|
||||
import dev.ltms.bridged.guard.SubscriptionGuard;
|
||||
import dev.ltms.bridged.herdr.AgentControl;
|
||||
import dev.ltms.bridged.herdr.FakeHerdr;
|
||||
@@ -614,5 +615,84 @@ class ClaudeCodeLauncherTest {
|
||||
|
||||
assertFalse(spawnedArgs(herdr).contains("--model"));
|
||||
assertNull(startEnv(herdr).get("ANTHROPIC_MODEL"));
|
||||
|
||||
// --- CB-539: subscription-profile opt-in ----------------------------------------------------
|
||||
|
||||
/** A claude-code profile on the subscription: no baseUrl (by design), no off-sub endpoint. */
|
||||
private static BridgedConfig.Worker subscriptionCfg(String profile, String baseUrl) {
|
||||
return new BridgedConfig.Worker(
|
||||
profile, baseUrl, "sonnet", null, "BRIDGED_WORKER_TOKEN",
|
||||
List.of("ccs", profile), "tab", "bridged-workers", "w #{n}", null, null, null,
|
||||
null, null, null, Map.of(), null, null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultRefusalIsPreservedForClaudeProfileWithNoBaseUrl() {
|
||||
// Requirement 1: absent subscription:true ⇒ byte-identical refusal to today. A claude-code
|
||||
// profile with no baseUrl and no subscription must still be refused (it would bill the sub).
|
||||
FakeHerdr herdr = new FakeHerdr();
|
||||
BridgedConfig.Worker cfg = new BridgedConfig.Worker(
|
||||
"ltms-local", null, "coder", null, "BRIDGED_WORKER_TOKEN",
|
||||
List.of("ccs", "ltms-local"), "tab", "bridged-workers", "w #{n}", null, null, null);
|
||||
ClaudeCodeLauncher svc = new ClaudeCodeLauncher(new AgentControl(herdr), new WorkspaceControl(herdr),
|
||||
new SubscriptionGuard(Set.of("gx00.gw")), Map.of(cfg.profile(), cfg), cfg.profile(), _ -> null);
|
||||
|
||||
GuardException ex = assertThrows(GuardException.class, () -> svc.spawn("ltms-local", null, null));
|
||||
assertTrue(ex.getMessage().contains("no ANTHROPIC_BASE_URL"),
|
||||
"the refusal names the missing baseUrl: " + ex.getMessage());
|
||||
assertEquals(0, herdr.calls.stream().filter(c -> c.method().equals("agent.start")).count(),
|
||||
"nothing was spawned before the refusal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subscriptionProfileSpawnsWithoutInjectedAnthropicVars() {
|
||||
// Requirement on subscription:true: no baseUrl is required (or injected), and neither
|
||||
// ANTHROPIC_BASE_URL nor ANTHROPIC_AUTH_TOKEN is injected even though the token env would
|
||||
// resolve one if asked.
|
||||
FakeHerdr herdr = new FakeHerdr();
|
||||
BridgedConfig.Worker cfg = subscriptionCfg("sonnet", null);
|
||||
new ClaudeCodeLauncher(new AgentControl(herdr), new WorkspaceControl(herdr),
|
||||
new SubscriptionGuard(Set.of("gx00.gw")), Map.of(cfg.profile(), cfg), cfg.profile(),
|
||||
_ -> "would-be-token").spawn();
|
||||
|
||||
Map<String, String> env = startEnv(herdr);
|
||||
assertNull(env.get("ANTHROPIC_BASE_URL"), "no baseUrl injected for a subscription profile");
|
||||
assertNull(env.get("ANTHROPIC_AUTH_TOKEN"), "no auth token injected for a subscription profile");
|
||||
assertEquals("sonnet", env.get("ANTHROPIC_MODEL"),
|
||||
"the model alias is still injected; only the subscription-boundary vars are dropped");
|
||||
}
|
||||
|
||||
@Test
|
||||
void subscriptionPlusBaseUrlIsRefused() {
|
||||
// Requirement 2: subscription:true + a baseUrl state opposite intents — refuse at spawn,
|
||||
// naming the profile, rather than silently picking a winner.
|
||||
FakeHerdr herdr = new FakeHerdr();
|
||||
BridgedConfig.Worker cfg = subscriptionCfg("sonnet", "http://gx00.gw:8000");
|
||||
ClaudeCodeLauncher svc = new ClaudeCodeLauncher(new AgentControl(herdr), new WorkspaceControl(herdr),
|
||||
new SubscriptionGuard(Set.of("gx00.gw")), Map.of(cfg.profile(), cfg), cfg.profile(), _ -> null);
|
||||
|
||||
IllegalStateException ex = assertThrows(IllegalStateException.class,
|
||||
() -> svc.spawn("sonnet", null, null));
|
||||
assertTrue(ex.getMessage().contains("sonnet"), "refusal names the profile: " + ex.getMessage());
|
||||
assertTrue(ex.getMessage().contains("subscription"), "refusal explains the contradiction: " + ex.getMessage());
|
||||
assertEquals(0, herdr.calls.stream().filter(c -> c.method().equals("agent.start")).count(),
|
||||
"nothing was spawned before the contradiction was refused");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonSubscriptionProfilesAreStillAllowlistChecked() {
|
||||
// Requirement 3: the guard keeps its teeth for every other profile — a base_url whose host is
|
||||
// not on the allowlist is still refused, whether or not any subscription profile exists.
|
||||
FakeHerdr herdr = new FakeHerdr();
|
||||
BridgedConfig.Worker rogue = new BridgedConfig.Worker(
|
||||
"rogue", "http://evil.example.com:8000", "coder", null, "BRIDGED_WORKER_TOKEN",
|
||||
List.of("ccs", "rogue"), "tab", "bridged-workers", "w #{n}", null, null, null);
|
||||
ClaudeCodeLauncher svc = new ClaudeCodeLauncher(new AgentControl(herdr), new WorkspaceControl(herdr),
|
||||
new SubscriptionGuard(Set.of("gx00.gw")), Map.of(rogue.profile(), rogue), rogue.profile(), _ -> null);
|
||||
|
||||
GuardException ex = assertThrows(GuardException.class, () -> svc.spawn("rogue", null, null));
|
||||
assertTrue(ex.getMessage().contains("not on the"), "refusal cites the allowlist: " + ex.getMessage());
|
||||
assertEquals(0, herdr.calls.stream().filter(c -> c.method().equals("agent.start")).count(),
|
||||
"nothing was spawned before the allowlist refusal");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user