import { b as openDualScopeDb } from './dual-scope-db-ATrwq0LH.js';
import './paths-2M_UxD_H.js';
import 'node:fs';
import 'node:path';
import 'node:crypto';
import { b as serviceConnections, a as agentServiceGrants } from './services-Dlf3Gefq.js';
import 'node:child_process';
import 'node:fs/promises';
import 'node:events';
import 'node:module';
import 'node:url';
import './indexes-aYlULWG3.js';
import './utils-BvHPJOVk.js';
import 'node:os';
import 'node:diagnostics_channel';
import 'fs';
import 'events';
import 'util';
import 'path';
import 'assert';
import 'worker_threads';
import 'module';
import 'url';
import 'buffer';
import 'node:process';
import 'node:async_hooks';
import './unique-constraint-BM-Apx1H.js';

//#region ../core/dist/store/service-trust-gate.js
/**
* Service-vault trust gate — **policy-before-decrypt** per-agent access control.
*
* EP-UNIVERSAL-SERVICE-VAULT (epic T11765 · saga SG-VAULT-CORE T10409 · M2 W1a ·
* task T11937 · AC4). Ports onecli `connect.rs` selective-mode: an agent may use a
* service connection ONLY when (a) a row exists in `agent_service_grants` linking
* the agent to the connection, AND (b) the grant's `session_policy` evaluates to
* an allow. This module evaluates that decision from already-loaded grant data —
* it performs ZERO crypto. The store
* ({@link import('./service-connections-accessor.js')}) calls `decryptGlobal`
* ONLY after this gate returns `allowed: true`, so a denied/blocked agent NEVER
* triggers a decrypt (the policy-before-decrypt invariant, proven by unit test).
*
* ## Why the gate is decrypt-free
*
* The gate takes the grant rows as INPUT and returns a pure decision. It cannot
* decrypt even if it wanted to — it imports no crypto. The decryption chokepoint
* lives one layer up (the accessor), guarded by this decision. That separation is
* exactly what makes "a denied agent never decrypts" structurally true and
* spy-assertable: the spy is placed on `decryptGlobal`, and a deny path returns
* before the accessor reaches the decrypt call.
*
* ## Policy shape ({@link SessionPolicy})
*
* Mirrors onecli's `PolicyAction` (block / rate-limit / manual-approval) collapsed
* to a per-grant JSON:
*
*   - `mode: 'allow' | 'block'` — `block` is a hard deny (no decrypt).
*   - `rateLimit?` — advisory rate cap; the gate surfaces it but does not itself
*     count requests (the caller enforces). Presence alone does not deny.
*   - `manualApproval?: true` — the connection requires an out-of-band human
*     approval that has not been granted for this session → deny (no decrypt)
*     until the caller supplies `approved: true` in the evaluation context.
*
* A bare grant (`{"mode":"allow"}`, the column default) allows.
*
* @module store/service-trust-gate
* @task T11937
* @epic T11765
* @saga T10409
* @see ./service-connections-accessor.ts — the accessor that decrypts ONLY on allow
* @see ../../../onecli — `apps/gateway/src/connect.rs` selective-mode (the ported model)
*/
/**
* Parse a raw `session_policy` JSON string into a {@link SessionPolicy}, failing
* CLOSED (to `block`) on any malformed input.
*
* SECURITY: a corrupt/unparseable policy must NEVER silently widen access — it
* resolves to `{ mode: 'block' }` so the gate denies and no decrypt occurs.
*
* @param raw - The `agent_service_grants.session_policy` column value.
* @returns A well-formed {@link SessionPolicy}; `block` on parse failure.
* @task T11937
*/
function parseSessionPolicy(raw) {
	let parsed;
	try {
		parsed = JSON.parse(raw);
	} catch {
		return { mode: "block" };
	}
	if (typeof parsed !== "object" || parsed === null) return { mode: "block" };
	const obj = parsed;
	const policy = { mode: obj["mode"] === "block" ? "block" : obj["mode"] === "allow" ? "allow" : "block" };
	if (obj["manualApproval"] === true) policy.manualApproval = true;
	const rl = obj["rateLimit"];
	if (typeof rl === "object" && rl !== null) {
		const rlObj = rl;
		const maxRequests = rlObj["maxRequests"];
		const window = rlObj["window"];
		if (typeof maxRequests === "number" && maxRequests > 0 && (window === "minute" || window === "hour" || window === "day")) policy.rateLimit = {
			maxRequests,
			window
		};
	}
	return policy;
}
//#endregion
//#region ../core/dist/store/service-connections-accessor.js
/**
* Service-vault accessor — store-level CRUD + policy-before-decrypt egress.
*
* EP-UNIVERSAL-SERVICE-VAULT (epic T11765 · saga SG-VAULT-CORE T10409 · M2 W1a ·
* task T11937 · AC3). The store layer for the universal SERVICE-credential vault
* (`service_connections` / `service_configs` / `agent_service_grants`).
*
* ## DB Open Guard (Gate 3 — strict)
*
* EVERY open goes through {@link openDualScopeDb}`('global')` (or its path-aware
* sibling {@link openDualScopeDbAtPath} for an injected test DB) — there is ZERO
* raw `new DatabaseSync(`. The typed Drizzle handle drives all CRUD; the accessor
* never constructs a native handle itself.
*
* ## Crypto reuse (DRY — no new crypto · T11710)
*
* `credentials_enc` holds the {@link encryptGlobal} ciphertext of the
* `{access_token, refresh_token}` JSON, keyed `id = service:${provider}:${label}`.
* The accessor calls {@link encryptGlobal}/{@link decryptGlobal} from
* `crypto/credentials.ts` — it adds no crypto of its own. The crypto functions are
* injectable ({@link ServiceVaultDeps}) so a test can spy on `decryptGlobal` and
* PROVE it is never invoked on a denied access (policy-before-decrypt, AC4).
*
* ## Policy-before-decrypt egress (AC4)
*
* {@link resolveSealedConnection} evaluates the {@link evaluateServiceAccess}
* trust gate FIRST (a pure decision from the agent's grant rows). It calls
* `decryptGlobal` ONLY when the gate returns `allowed: true`. A denied agent
* returns `null` BEFORE the decrypt call — the decrypt closure never runs. On
* allow, the plaintext is not surfaced inline: it is wrapped in a
* {@link makeSealedCredential} handle whose `resolveToken` thunk performs the
* decrypt at the wire (the same egress the LLM credential pool uses).
*
* ## OAuth seam (T11939)
*
* The OAuth build/exchange/refresh dance is the FOLLOW-UP (T11939). This accessor
* leaves the clean seam: {@link connectService} accepts an already-obtained token
* blob and writes its `encryptGlobal` ciphertext into `credentials_enc`; the OAuth
* flow simply calls `connectService` (or `updateConnectionCredentials`) with the
* tokens it negotiates. No OAuth logic lives here.
*
* @module store/service-connections-accessor
* @task T11937
* @epic T11765
* @saga T10409
* @see ./schema/cleo-global/services.ts — the three tables
* @see ./service-trust-gate.ts — the policy-before-decrypt decision
* @see ../crypto/credentials.ts — `encryptGlobal` / `decryptGlobal` (reused, not re-implemented)
* @see ../llm/sealed-credential.ts — `makeSealedCredential` (the shared egress handle)
*/
/** Resolve the global Drizzle handle — injected, else canonical open. */
async function resolveDb(deps) {
	return (await openDualScopeDb("global")).db;
}
/** Map a raw connection row to a non-secret {@link ServiceConnectionView}. */
function toView(row) {
	let scopes = [];
	let metadata = {};
	try {
		const parsed = JSON.parse(row.scopes);
		if (Array.isArray(parsed)) scopes = parsed.filter((s) => typeof s === "string");
	} catch {}
	try {
		const parsed = JSON.parse(row.metadata);
		if (typeof parsed === "object" && parsed !== null) metadata = parsed;
	} catch {}
	return {
		id: row.id,
		provider: row.provider,
		label: row.label,
		status: row.status,
		scopes,
		expiresAt: row.expiresAt,
		metadata,
		connectedAt: row.connectedAt,
		updatedAt: row.updatedAt,
		hasCredentials: row.credentialsEnc !== null && row.credentialsEnc !== ""
	};
}
/**
* LIST connections as non-secret views (NEVER decrypts), optionally by provider.
*
* @param provider - When set, restrict to one provider; otherwise all.
* @task T11937
*/
async function listConnections(provider, deps) {
	const db = await resolveDb();
	return (await db.select().from(serviceConnections) ).map(toView);
}
/**
* LIST every agent grant as a NON-SECRET {@link AgentGrantView}, joined to its
* connection's `provider`/`label` for display. NEVER decrypts and never carries
* a token — the dashboard read path (T11943).
*
* The join is performed in-memory (one `agent_service_grants` scan + one
* `service_connections` scan, both already redacted) so the accessor stays a
* pure read with no second round-trip per grant. A grant whose connection was
* deleted out-of-band resolves `provider`/`label` to `null` (defensive — the
* cascade should prevent this, but the view must not throw).
*
* @param agentId - When set, restrict to one agent's grants; otherwise all.
* @task T11943
*/
async function listAllGrants(agentId, deps) {
	const db = await resolveDb();
	const grantRows = await db.select().from(agentServiceGrants) ;
	const connRows = await db.select().from(serviceConnections);
	const identityById = /* @__PURE__ */ new Map();
	for (const c of connRows) identityById.set(c.id, {
		provider: c.provider,
		label: c.label
	});
	return grantRows.map((g) => {
		const identity = identityById.get(g.serviceConnectionId) ?? null;
		const policy = parseSessionPolicy(g.sessionPolicy);
		return {
			agentId: g.agentId,
			serviceConnectionId: g.serviceConnectionId,
			provider: identity?.provider ?? null,
			label: identity?.label ?? null,
			mode: policy.mode,
			manualApproval: policy.manualApproval ?? false,
			createdAt: g.createdAt,
			updatedAt: g.updatedAt
		};
	});
}

//#region src/routes/settings/vault/+page.server.ts
/**
* `/settings/vault` — server load for the read-only service-vault dashboard
* (T11943 · M2-W6 · E-UNIVERSAL-SERVICE-VAULT).
*
* Surfaces the global service vault — `service_connections` + per-agent
* `agent_service_grants` — through the CORE read facades
* ({@link listConnections} / {@link listAllGrants}). Both return REDACTED,
* non-secret views: provider / label / status / scopes / expiry /
* hasCredentials and, for grants, the agent + policy mode. NO ciphertext, NO
* plaintext, NO `tokenPreview`-able material ever reaches this load — the
* decrypted token never leaves core (AC1).
*
* Studio holds NO second store and opens NO DB directly (AC3): this load calls
* the same core accessors the `cleo service list|status` CLI verbs use, so the
* dashboard and the CLI agree on one source of truth. Connect / revoke are NOT
* exposed here (read-first M2) — they route through the CLI (`cleo service
* connect|revoke`), linked from the page.
*
* @task T11943
* @epic T11765 — E-UNIVERSAL-SERVICE-VAULT
* @saga T10409
*/
/** Project a core {@link ServiceConnectionView} onto the redacted dashboard row. */
function toConnectionRow(v) {
	return {
		provider: v.provider,
		label: v.label,
		status: v.status,
		scopes: [...v.scopes],
		expiresAt: v.expiresAt,
		hasCredentials: v.hasCredentials,
		connectedAt: v.connectedAt
	};
}
/** Project a core {@link AgentGrantView} onto the redacted dashboard row. */
function toGrantRow(v) {
	return {
		agentId: v.agentId,
		provider: v.provider,
		label: v.label,
		mode: v.mode,
		manualApproval: v.manualApproval
	};
}
var load = async () => {
	try {
		const [connections, grants] = await Promise.all([listConnections(), listAllGrants()]);
		return {
			connections: connections.map(toConnectionRow),
			grants: grants.map(toGrantRow)
		};
	} catch (e) {
		return {
			connections: [],
			grants: [],
			error: e instanceof Error ? e.message : "Failed to read the service vault."
		};
	}
};

var _page_server_ts = /*#__PURE__*/Object.freeze({
	__proto__: null,
	load: load
});

const index = 21;
let component_cache;
const component = async () => component_cache ??= (await import('./_page.svelte-CHzXuw2Q.js')).default;
const server_id = "src/routes/settings/vault/+page.server.ts";
const imports = ["_app/immutable/nodes/21.CpAN71H8.js","_app/immutable/chunks/DyGQ_VXC.js","_app/immutable/chunks/DrKWWDpL.js","_app/immutable/chunks/CQCxd1p9.js","_app/immutable/chunks/CEqOYim7.js","_app/immutable/chunks/CqF5Lomf.js"];
const stylesheets = ["_app/immutable/assets/Tabs.CU9_rsWl.css","_app/immutable/assets/ui.CH-9N9-0.css","_app/immutable/assets/21.nneFKQjw.css"];
const fonts = [];

export { component, fonts, imports, index, _page_server_ts as server, server_id, stylesheets };
//# sourceMappingURL=21-DiM3K2IK.js.map
