Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ccfc034
refactor(onboard): route provider consumers through adapter
rsliter Sep 8, 2026
ebdaaaa
merge: refresh slice 6 with main
rsliter Sep 8, 2026
701c921
fix: complete async onboarding provider consumers
rsliter Sep 8, 2026
dd262ef
test: use typed provider metadata in reconciliation
rsliter Sep 8, 2026
c2f188d
test: tighten provider lifecycle fixtures
rsliter Sep 8, 2026
18b3215
merge: refresh slice 6 with main
rsliter Sep 8, 2026
70e8eea
test: match exact Hermes provider lookup
rsliter Sep 8, 2026
f561081
merge: refresh slice 6 with main
rsliter Sep 8, 2026
6e43b11
test: match provider commands by token
rsliter Sep 8, 2026
40c715c
refactor(recovery): route managed providers through adapter
rsliter Sep 8, 2026
be4b1de
chore(ci): lower provider adapter fan-in budgets
rsliter Sep 8, 2026
0c9c063
fix: complete provider adapter migration
rsliter Sep 8, 2026
08ecc05
test(recovery): tighten provider adapter cutover
rsliter Sep 8, 2026
2b61808
merge: refresh slice 7 with slice 6
rsliter Sep 8, 2026
6d6f321
test: focus Hermes provider store contract
rsliter Sep 8, 2026
22d3c16
test(recovery): preserve adapter result literals
rsliter Sep 8, 2026
49ce5d9
merge: refresh slice 7 with slice 6
rsliter Sep 9, 2026
0f1dfbe
refactor: remove eager provider reconciliation wrapper
rsliter Sep 9, 2026
a42c053
merge: refresh slice 6 with main
rsliter Sep 9, 2026
f9e199d
test(recovery): prove missing provider fixture
rsliter Sep 9, 2026
44ea6bc
merge: refresh slice 7 with slice 6
rsliter Sep 9, 2026
370fd71
fix: reconcile providers inside creation lock
rsliter Sep 9, 2026
7ad10bc
merge: refresh slice 7 with slice 6
rsliter Sep 9, 2026
05ee80e
merge: refresh slice 6 with main
rsliter Sep 9, 2026
cb788fe
merge: refresh slice 7 with slice 6
rsliter Sep 9, 2026
2c748d6
merge: refresh slice 6 onto canonical main
rsliter Sep 9, 2026
cc647e8
merge: refresh slice 7 with slice 6
rsliter Sep 9, 2026
d4d0100
Merge remote-tracking branch 'refs/remotes/origin/main' into codex/98…
rsliter Sep 9, 2026
886374c
Merge remote-tracking branch 'refs/remotes/origin/main' into codex/98…
rsliter Sep 9, 2026
8d1466f
Merge remote-tracking branch 'refs/remotes/origin/main' into codex/98…
rsliter Sep 9, 2026
a077e08
Merge remote-tracking branch 'refs/remotes/origin/main' into codex/98…
rsliter Sep 9, 2026
2a08381
merge: refresh slice 7 onto canonical main
rsliter Sep 9, 2026
2d00307
merge: refresh slice 7 onto canonical main
rsliter Sep 9, 2026
0c1dc19
merge: refresh slice 7 onto canonical main
rsliter Sep 9, 2026
2cfcae6
merge: refresh slice 7 onto canonical main
rsliter Sep 9, 2026
edc3101
docs(adapters): describe managed provider ownership
rsliter Sep 9, 2026
73eacfe
merge: refresh slice 7 onto canonical main
rsliter Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"src/lib/actions/sandbox/process-recovery.ts": 26,
"src/lib/adapters/docker/index.ts": 42,
"src/lib/adapters/openshell/client.ts": 18,
"src/lib/adapters/openshell/command-argv.ts": 23,
"src/lib/adapters/openshell/command-argv.ts": 22,
"src/lib/adapters/openshell/resolve.ts": 25,
"src/lib/adapters/openshell/runtime.ts": 53,
"src/lib/adapters/openshell/runtime.ts": 52,
"src/lib/adapters/openshell/timeouts.ts": 32,
"src/lib/agent/defs.ts": 32,
"src/lib/cli/branding.ts": 80,
Expand Down
12 changes: 8 additions & 4 deletions src/lib/actions/sandbox/rebuild-credential-preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,19 @@ export async function preflightRebuildCredentials(
const rebuildProvider = sb.provider;

if (rebuildProvider === hermesProviderAuth.HERMES_PROVIDER_NAME) {
if (!(await preflightHermesProviderCredentials(sb.hermesAuthMethod, rebuildCredentialEnv, log))) {
if (
!(await preflightHermesProviderCredentials(sb.hermesAuthMethod, rebuildCredentialEnv, log))
) {
bail("Missing Hermes Provider credentials");
return false;
}
return true;
}

if (!rebuildCredentialEnv) {
if (!checkRebuildGatewayProviderOrBail(rebuildProvider, rebuildCredentialEnv, log, bail)) {
if (
!(await checkRebuildGatewayProviderOrBail(rebuildProvider, rebuildCredentialEnv, log, bail))
) {
return false;
}
log(
Expand All @@ -178,11 +182,11 @@ export async function preflightRebuildCredentials(
`Preflight credential check: ${rebuildCredentialEnv} → ${credentialValue ? "present" : "MISSING"}`,
);
if (
!checkRebuildGatewayProviderOrBail(rebuildProvider, rebuildCredentialEnv, log, bail, {
!(await checkRebuildGatewayProviderOrBail(rebuildProvider, rebuildCredentialEnv, log, bail, {
allowProviderReconfigure: options.allowMissingGatewayProviderWithHostCredential,
hostCredentialAvailable: Boolean(credentialValue),
onProviderReconfigureRequired: options.onGatewayProviderReconfigureRequired,
})
}))
) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe("rebuildSandbox DCode recovered provider", () => {
configureDcodeSession(harness);
setGatewayProviderMetadata(
harness,
"Name: compatible-endpoint\nType: openai\nCredential keys: COMPATIBLE_API_KEY\n",
"Name: compatible-endpoint\nType: openai\nCredential keys: COMPATIBLE_API_KEY\nConfig keys: <none>\n",
);

await expect(
Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/rebuild-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ async function rebuildSandboxUnlocked(
};
}
const providerRegistration = providerReconfigure
? inspectRebuildGatewayProviderRegistration(
? await inspectRebuildGatewayProviderRegistration(
providerReconfigure.provider,
log,
"Delete-edge",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/actions/sandbox/rebuild-preflight-phase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,13 @@ export async function runRebuildPreflightPhase(
resumeConfig.credentialEnv && hydrateCredentialEnv(resumeConfig.credentialEnv),
);
if (
!checkRebuildGatewayCredentialReuseOrBail(
!(await checkRebuildGatewayCredentialReuseOrBail(
sandboxName,
resumeConfig,
hostCredentialAvailable,
log,
bail,
)
))
) {
return null;
}
Expand Down
174 changes: 34 additions & 140 deletions src/lib/actions/sandbox/rebuild-provider-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
canRecreateMissingRebuildGatewayProvider,
checkRebuildGatewayCredentialReuseOrBail,
checkRebuildGatewayProviderOrBail,
classifyRebuildGatewayProviderRegistration,
inspectRebuildGatewayProviderRegistration,
shouldVerifyRebuildGatewayProvider,
} from "./rebuild-provider-preflight";
Expand Down Expand Up @@ -55,7 +54,7 @@ afterEach(() => {
});

describe("shouldVerifyRebuildGatewayProvider", () => {
it("requires remote registrations while allowing reconstructible local registrations", () => {
it("requires remote registrations while allowing reconstructible local registrations", async () => {
expect(shouldVerifyRebuildGatewayProvider("nvidia-prod")).toBe(true);
expect(shouldVerifyRebuildGatewayProvider("ollama-local")).toBe(false);
expect(shouldVerifyRebuildGatewayProvider("vllm-local")).toBe(false);
Expand All @@ -64,14 +63,16 @@ describe("shouldVerifyRebuildGatewayProvider", () => {
const bail = vi.fn(() => {
throw new Error("local provider must not require an existing gateway registration");
});
expect(checkRebuildGatewayProviderOrBail("ollama-local", null, log, bail)).toBe(true);
await expect(checkRebuildGatewayProviderOrBail("ollama-local", null, log, bail)).resolves.toBe(
true,
);
expect(log).not.toHaveBeenCalled();
expect(bail).not.toHaveBeenCalled();
});
});

describe("canRecreateMissingRebuildGatewayProvider", () => {
it("requires a canonical provider and its exact credential binding (#6114)", () => {
it("requires a canonical provider and its exact credential binding (#6114)", async () => {
expect(
canRecreateMissingRebuildGatewayProvider("compatible-endpoint", "COMPATIBLE_API_KEY"),
).toBe(true);
Expand All @@ -88,115 +89,8 @@ describe("canRecreateMissingRebuildGatewayProvider", () => {
});
});

describe("classifyRebuildGatewayProviderRegistration", () => {
it("distinguishes explicit absence from an indeterminate lookup failure (#6114)", () => {
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr: "Error: provider 'compatible-endpoint' not found",
},
"compatible-endpoint",
),
).toBe("missing");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr:
"Error: × code: 'Some requested entity was not found', message: \"provider not found\"",
},
"compatible-endpoint",
),
).toBe("missing");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr:
'Error: status: NotFound, message: "provider not found", details: [], metadata: MetadataMap { headers: {} }',
},
"compatible-endpoint",
),
).toBe("missing");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 7,
stderr: "gateway transport unavailable",
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 7,
stderr: "provider lookup failed because gateway was not found",
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{ status: 1, stderr: "provider lookup not found" },
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{ status: 1, stderr: "provider 'other-provider' not found" },
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 7,
stderr: 'Error: status: Unavailable, message: "provider not found"',
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr: 'Error: status: NotFound, message: "gateway not found"',
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr: [
'Error: status: NotFound, message: "gateway not found"',
'Error: status: Unavailable, message: "provider not found"',
].join("\n"),
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(
classifyRebuildGatewayProviderRegistration(
{
status: 1,
stderr:
'Error: status: NotFound, message: "gateway not found"; status: Unavailable, message: "provider not found"',
},
"compatible-endpoint",
),
).toBe("indeterminate");
expect(classifyRebuildGatewayProviderRegistration({ status: 0 }, "compatible-endpoint")).toBe(
"registered",
);
});
});

describe("inspectRebuildGatewayProviderRegistration", () => {
it("pins the delete-edge lookup to the frozen target under hostile ambient selectors (#10514)", () => {
it("pins the delete-edge lookup to the frozen target under hostile ambient selectors (#10514)", async () => {
vi.stubEnv("OPENSHELL_GATEWAY", "hostile-gateway");
vi.stubEnv("OPENSHELL_WORKSPACE", "hostile-workspace");
vi.stubEnv("OPENSHELL_LOCAL_TLS_DIR", "/hostile/tls");
Expand All @@ -212,14 +106,14 @@ describe("inspectRebuildGatewayProviderRegistration", () => {
localTlsDir: "/authority/tls",
};

expect(
await expect(
inspectRebuildGatewayProviderRegistration(
"compatible-endpoint",
vi.fn(),
"Delete-edge",
runtimeSelection,
),
).toBe("missing");
).resolves.toBe("missing");

expect(runOpenshell).toHaveBeenCalledWith(
["provider", "get", "compatible-endpoint"],
Expand All @@ -238,27 +132,27 @@ describe("inspectRebuildGatewayProviderRegistration", () => {
});

describe("checkRebuildGatewayCredentialReuseOrBail", () => {
it("accepts an exact complete registry route and gateway provider identity", () => {
expect(
it("accepts an exact complete registry route and gateway provider identity", async () => {
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", config(), false, vi.fn(), throwingBail, {
readGatewayProviderMetadata: () => exactGatewayProvider,
readGatewayProviderMetadata: async () => exactGatewayProvider,
readRecordedProviderEndpoints: () => [],
}),
).toBe(true);
).resolves.toBe(true);
});

it("preserves normal host-key validation without reading gateway recovery metadata", () => {
it("preserves normal host-key validation without reading gateway recovery metadata", async () => {
const readGatewayProviderMetadata = vi.fn();
expect(
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", config(), true, vi.fn(), throwingBail, {
readGatewayProviderMetadata,
readRecordedProviderEndpoints: vi.fn(),
}),
).toBe(true);
).resolves.toBe(true);
expect(readGatewayProviderMetadata).not.toHaveBeenCalled();
});

it("preserves Bedrock Runtime rebuilds with explicit AWS authentication", () => {
it("preserves Bedrock Runtime rebuilds with explicit AWS authentication", async () => {
const readGatewayProviderMetadata = vi.fn();
const bedrock = config({
provider: "compatible-anthropic-endpoint",
Expand All @@ -273,17 +167,17 @@ describe("checkRebuildGatewayCredentialReuseOrBail", () => {
},
});

expect(
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", bedrock, false, vi.fn(), throwingBail, {
hasBedrockRuntimeAwsAuth: () => true,
readGatewayProviderMetadata,
readRecordedProviderEndpoints: vi.fn(),
}),
).toBe(true);
).resolves.toBe(true);
expect(readGatewayProviderMetadata).not.toHaveBeenCalled();
});

it("rejects Bedrock Runtime before deletion when neither AWS nor compatible auth exists", () => {
it("rejects Bedrock Runtime before deletion when neither AWS nor compatible auth exists", async () => {
const errors = vi.spyOn(console, "error").mockImplementation(() => undefined);
const bedrock = config({
provider: "compatible-anthropic-endpoint",
Expand All @@ -298,18 +192,18 @@ describe("checkRebuildGatewayCredentialReuseOrBail", () => {
},
});

expect(() =>
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", bedrock, false, vi.fn(), throwingBail, {
hasBedrockRuntimeAwsAuth: () => false,
readGatewayProviderMetadata: () => ({
readGatewayProviderMetadata: async () => ({
name: "compatible-anthropic-endpoint",
type: "openai",
credentialKeys: ["NEMOCLAW_BEDROCK_RUNTIME_ADAPTER_TOKEN"],
configKeys: ["OPENAI_BASE_URL"],
}),
readRecordedProviderEndpoints: () => [],
}),
).toThrow("Missing Bedrock Runtime authentication");
).rejects.toThrow("Missing Bedrock Runtime authentication");

const diagnostics = errors.mock.calls.flat().join(" ");
expect(diagnostics).toContain("AWS_BEARER_TOKEN_BEDROCK");
Expand Down Expand Up @@ -340,47 +234,47 @@ describe("checkRebuildGatewayCredentialReuseOrBail", () => {
},
}),
],
])("rejects %s before destructive rebuild work", (_label, unsafeConfig) => {
])("rejects %s before destructive rebuild work", async (_label, unsafeConfig) => {
vi.spyOn(console, "error").mockImplementation(() => undefined);
expect(() =>
await expect(
checkRebuildGatewayCredentialReuseOrBail(
"alpha",
unsafeConfig,
false,
vi.fn(),
throwingBail,
{
readGatewayProviderMetadata: () => exactGatewayProvider,
readGatewayProviderMetadata: async () => exactGatewayProvider,
readRecordedProviderEndpoints: () => [],
},
),
).toThrow("Unsafe gateway credential reuse");
).rejects.toThrow("Unsafe gateway credential reuse");
});

it("rejects spoofed gateway bindings", () => {
it("rejects spoofed gateway bindings", async () => {
vi.spyOn(console, "error").mockImplementation(() => undefined);
const spoofedProvider = {
...exactGatewayProvider,
credentialKeys: ["ATTACKER_KEY"],
};
expect(() =>
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", config(), false, vi.fn(), throwingBail, {
readGatewayProviderMetadata: () => spoofedProvider,
readGatewayProviderMetadata: async () => spoofedProvider,
readRecordedProviderEndpoints: () => [],
}),
).toThrow("no compatible non-secret identity");
).rejects.toThrow("no compatible non-secret identity");
});

it("rejects a custom endpoint recorded by another sandbox", () => {
it("rejects a custom endpoint recorded by another sandbox", async () => {
vi.spyOn(console, "error").mockImplementation(() => undefined);
const readRecordedProviderEndpoints = vi.fn(() => ["https://other.example.test/v1"]);

expect(() =>
await expect(
checkRebuildGatewayCredentialReuseOrBail("alpha", config(), false, vi.fn(), throwingBail, {
readGatewayProviderMetadata: () => exactGatewayProvider,
readGatewayProviderMetadata: async () => exactGatewayProvider,
readRecordedProviderEndpoints,
}),
).toThrow("recovered endpoint identity is missing or incompatible");
).rejects.toThrow("recovered endpoint identity is missing or incompatible");
expect(readRecordedProviderEndpoints).toHaveBeenCalledWith("compatible-endpoint", "alpha");
});
});
Loading
Loading