-
Notifications
You must be signed in to change notification settings - Fork 0
[WRONG BRANCH] fix(codex): fence entitlement credential refreshes #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import type { AdmissionLease } from "../lib/admission"; | ||
| import type { OcxConfig } from "../types"; | ||
| import { MAIN_CODEX_ACCOUNT_ID } from "./main-account"; | ||
| import { | ||
| resolveCodexModelEntitlements, | ||
| type CodexModelEntitlementResolveOptions, | ||
| type CodexModelEntitlementSnapshot, | ||
| } from "./model-entitlements"; | ||
| import { tryAcquireNativeMainProfileClaim } from "./native-main-admission"; | ||
| import { withNativeMainSharedClaim } from "./native-main-claim"; | ||
| import { resolveNativeProfileContext } from "./native-profile-store"; | ||
| import { NativeProfileError } from "./native-profile-types"; | ||
|
|
||
| interface ModelEntitlementAdmissionDeps { | ||
| readonly acquireNativeMain?: () => AdmissionLease | null; | ||
| readonly resolve?: typeof resolveCodexModelEntitlements; | ||
| readonly withSharedClaim?: <T>(operation: () => Promise<T>) => Promise<T>; | ||
| } | ||
|
|
||
| function excludeNativeMain( | ||
| options: CodexModelEntitlementResolveOptions, | ||
| ): CodexModelEntitlementResolveOptions { | ||
| return { | ||
| ...options, | ||
| excludeAccountIds: new Set([ | ||
| ...(options.excludeAccountIds ?? []), | ||
| MAIN_CODEX_ACCOUNT_ID, | ||
| ]), | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Resolve background/data-plane entitlements inside both native-main fences. | ||
| * | ||
| * Pool discovery remains available when startup recovery or a profile drain | ||
| * owns the physical credential. When main is admitted, the process-local lease | ||
| * and cross-process shared claim cover its complete read/possible refresh. | ||
| */ | ||
| export async function resolveAdmittedCodexModelEntitlements( | ||
| config: Pick<OcxConfig, "codexAccounts">, | ||
| options: CodexModelEntitlementResolveOptions = {}, | ||
| deps: ModelEntitlementAdmissionDeps = {}, | ||
| ): Promise<CodexModelEntitlementSnapshot> { | ||
| const resolve = deps.resolve ?? resolveCodexModelEntitlements; | ||
| const lease = (deps.acquireNativeMain ?? tryAcquireNativeMainProfileClaim)(); | ||
| if (!lease) return resolve(config, excludeNativeMain(options)); | ||
|
|
||
| try { | ||
| const operation = () => resolve(config, options); | ||
| const withSharedClaim = deps.withSharedClaim | ||
| ?? (<T>(work: () => Promise<T>) => withNativeMainSharedClaim(resolveNativeProfileContext(), work)); | ||
| try { | ||
| return await withSharedClaim(operation); | ||
| } catch (error) { | ||
| // A foreign exclusive holder or an unsupported claim filesystem makes | ||
| // main unavailable; it must not suppress independent Pool discovery. | ||
| if (!(error instanceof NativeProfileError)) throw error; | ||
| return await resolve(config, excludeNativeMain(options)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the cross-process claim is busy or unavailable, this fallback performs the entire Pool-only resolution before the Useful? React with 👍 / 👎. |
||
| } | ||
| } finally { | ||
| lease.release(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { describe, expect, test } from "bun:test"; | ||
|
|
||
| import { MAIN_CODEX_ACCOUNT_ID } from "../src/codex/main-account"; | ||
| import { resolveAdmittedCodexModelEntitlements } from "../src/codex/model-entitlement-admission"; | ||
| import type { CodexModelEntitlementResolveOptions } from "../src/codex/model-entitlements"; | ||
| import { NativeProfileError } from "../src/codex/native-profile-types"; | ||
|
|
||
| const emptySnapshot = { | ||
| modelsByAccount: new Map<string, ReadonlySet<string>>(), | ||
| confirmedAccountIds: new Set<string>(), | ||
| credentialIdentities: new Map<string, string>(), | ||
| }; | ||
|
|
||
| describe("Codex model entitlement admission", () => { | ||
| test("excludes native main before credential discovery when lifecycle admission is blocked", async () => { | ||
| let received: CodexModelEntitlementResolveOptions | undefined; | ||
|
|
||
| await resolveAdmittedCodexModelEntitlements({ codexAccounts: [] }, {}, { | ||
| acquireNativeMain: () => null, | ||
| resolve: async (_config, options) => { | ||
| received = options; | ||
| return emptySnapshot; | ||
| }, | ||
| }); | ||
|
|
||
| expect(received?.excludeAccountIds?.has(MAIN_CODEX_ACCOUNT_ID)).toBe(true); | ||
| }); | ||
|
|
||
| test("holds lifecycle and shared claims through credential discovery", async () => { | ||
| const events: string[] = []; | ||
| let released = false; | ||
|
|
||
| await resolveAdmittedCodexModelEntitlements({ codexAccounts: [] }, {}, { | ||
| acquireNativeMain: () => ({ release: () => { | ||
| released = true; | ||
| events.push("lifecycle-release"); | ||
| } }), | ||
| withSharedClaim: async operation => { | ||
| events.push("shared-enter"); | ||
| const result = await operation(); | ||
| events.push("shared-release"); | ||
| return result; | ||
| }, | ||
| resolve: async () => { | ||
| expect(released).toBe(false); | ||
| events.push("credential-discovery"); | ||
| return emptySnapshot; | ||
| }, | ||
| }); | ||
|
|
||
| expect(events).toEqual([ | ||
| "shared-enter", | ||
| "credential-discovery", | ||
| "shared-release", | ||
| "lifecycle-release", | ||
| ]); | ||
| }); | ||
|
|
||
| test("falls back to Pool-only discovery when the shared claim is unavailable", async () => { | ||
| const exclusions: boolean[] = []; | ||
|
|
||
| await resolveAdmittedCodexModelEntitlements({ codexAccounts: [] }, {}, { | ||
| acquireNativeMain: () => ({ release: () => undefined }), | ||
| withSharedClaim: async () => { | ||
| throw new NativeProfileError("NATIVE_MAIN_CLAIM_BUSY", "busy", 503, true); | ||
| }, | ||
| resolve: async (_config, options) => { | ||
| exclusions.push(options.excludeAccountIds?.has(MAIN_CODEX_ACCOUNT_ID) === true); | ||
| return emptySnapshot; | ||
| }, | ||
| }); | ||
|
|
||
| expect(exclusions).toEqual([true]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When startup ownership acquisition or journal recovery is still pending,
startNativeMainStartupLifecyclereports native-main traffic as blocked, but the startup flow insrc/cli/index.tsproceeds fromstartServertosyncCodexOnStartIfEnabledwithout awaiting that lifecycle. This branch therefore letssyncCatalogModelscommit a Pool-only entitlement snapshot, removing main-only account-gated models from the on-disk Codex catalog; when recovery later succeeds, nothing automatically reconverges that catalog, so the models remain absent until a separate sync occurs. Treat this as a non-committable/retryable snapshot for catalog writers, or schedule convergence when the startup gate opens.Useful? React with 👍 / 👎.