feat(FN-4792): complete Step 2 — add secret policy resolver and default

Fusion-Task-Id: FN-4792
Fusion-Task-Lineage: 8e29f9a0-4c9f-4322-9595-3c9c581dacb0
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 23:58:19 -07:00
committed by gsxdsm
parent aa2f588048
commit 0076b754eb
2 changed files with 49 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import type { GlobalSettings, SecretAccessPolicy } from "./types.js";
import { SECRET_ACCESS_POLICIES } from "./types.js";
/** Fallback policy when neither the secret row nor global settings specify one.
* Uses "prompt" to fail safe by requiring explicit approval before access. */
export const SECRET_ACCESS_POLICY_FALLBACK: SecretAccessPolicy = "prompt";
/** Inputs for secret-policy resolution in priority order:
* secret row policy -> global default (`secretsAccessPolicy`) -> fallback (`"prompt"`). */
export interface ResolveSecretAccessPolicyInput {
/** Row-level policy from the secret record. Null/undefined are treated as missing. */
secretPolicy?: SecretAccessPolicy | null;
/** Global settings snapshot. May be absent during early bootstrap before settings load. */
settings?: Pick<GlobalSettings, "secretsAccessPolicy">;
}
/** Effective secret access policy and where it was sourced from.
* - "auto": allow return of secret value (and downstream audit)
* - "prompt": require approval flow
* - "deny": reject without prompting */
export interface ResolveSecretAccessPolicyDecision {
policy: SecretAccessPolicy;
source: "secret" | "global-default" | "fallback";
}
/** Runtime guard for secret access policy strings.
* Accepts only `"auto" | "prompt" | "deny"` and rejects unknown legacy values. */
export function isSecretAccessPolicy(value: unknown): value is SecretAccessPolicy {
return typeof value === "string" && (SECRET_ACCESS_POLICIES as readonly string[]).includes(value);
}
/** Resolve effective secret access policy in this order:
* 1) row-level secret policy, 2) global `secretsAccessPolicy`, 3) fallback `"prompt"`.
* Unknown values are treated as missing and never throw. */
export function resolveSecretAccessPolicy(
input: ResolveSecretAccessPolicyInput,
): ResolveSecretAccessPolicyDecision {
if (isSecretAccessPolicy(input.secretPolicy)) {
return { policy: input.secretPolicy, source: "secret" };
}
const globalDefault = input.settings?.secretsAccessPolicy;
if (isSecretAccessPolicy(globalDefault)) {
return { policy: globalDefault, source: "global-default" };
}
return { policy: SECRET_ACCESS_POLICY_FALLBACK, source: "fallback" };
}

View File

@@ -98,6 +98,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
persistAgentThinkingLogEphemeral: false,
persistAgentThinkingLog: false,
agentMemoryInclusionMode: "full",
secretsAccessPolicy: undefined,
researchGlobalDefaults: {
searchProvider: undefined,
synthesisProvider: undefined,