diff --git a/apps/web/src/app/(connect)/app-connect/_components/connect-flow.tsx b/apps/web/src/app/(connect)/app-connect/_components/connect-flow.tsx index 6871d9ea..80af71c5 100644 --- a/apps/web/src/app/(connect)/app-connect/_components/connect-flow.tsx +++ b/apps/web/src/app/(connect)/app-connect/_components/connect-flow.tsx @@ -4,7 +4,6 @@ import type { ReactNode } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { Loader2 } from "lucide-react"; import { Button } from "@onecli/ui/components/button"; -import { IS_CLOUD } from "@/lib/env"; import { API_ORIGIN, getAuthToken, getProjectId } from "@/lib/api-fetch"; import { ConnectLayout } from "./connect-layout"; import { ConnectSuccess } from "./connect-success"; @@ -280,28 +279,6 @@ export const ConnectFlow = ({ Use an API key instead )} - {!IS_CLOUD && ( - <> -
-
- - or - -
-
-

- Skip setup with{" "} - - OneCLI Cloud - -

- - )}
); diff --git a/apps/web/src/app/(dashboard)/_components/get-started-dialog.tsx b/apps/web/src/app/(dashboard)/_components/get-started-dialog.tsx index e72a9911..307c0149 100644 --- a/apps/web/src/app/(dashboard)/_components/get-started-dialog.tsx +++ b/apps/web/src/app/(dashboard)/_components/get-started-dialog.tsx @@ -166,17 +166,16 @@ export const GetStartedDialog = ({

- Requires the OneCLI CLI. One-command install is - available with{" "} + Requires the OneCLI CLI — see the{" "} - OneCLI Cloud - - . + quickstart + {" "} + to install it.

)} @@ -215,17 +214,8 @@ export const GetStartedDialog = ({ ) : (
-

- Migration is available with{" "} - - OneCLI Cloud - - . +

+ Automated migration isn't available in this build.

)} diff --git a/apps/web/src/app/(dashboard)/connections/_components/app-config-form.tsx b/apps/web/src/app/(dashboard)/connections/_components/app-config-form.tsx index 667b56f7..4938b938 100644 --- a/apps/web/src/app/(dashboard)/connections/_components/app-config-form.tsx +++ b/apps/web/src/app/(dashboard)/connections/_components/app-config-form.tsx @@ -41,7 +41,6 @@ import { useDeleteAppConfig, useToggleAppConfig, } from "@/hooks/use-app-config"; -import { IS_CLOUD } from "@/lib/env"; import { RedirectUri } from "./redirect-uri"; export interface AppConfigFormHandle { @@ -301,23 +300,6 @@ export const AppConfigForm = ({ ? "Override platform defaults with your own." : (hint ?? `Required to connect ${appName}.`)}

- {!hasEnvDefaults && - !hasCredentials && - !enabled && - !IS_CLOUD && ( -

- Or connect instantly with{" "} - - OneCLI Cloud - {" "} - - no credentials needed. -

- )} diff --git a/apps/web/src/app/(dashboard)/connections/_components/apps-tab.tsx b/apps/web/src/app/(dashboard)/connections/_components/apps-tab.tsx index 7db4b7c3..9f2ee603 100644 --- a/apps/web/src/app/(dashboard)/connections/_components/apps-tab.tsx +++ b/apps/web/src/app/(dashboard)/connections/_components/apps-tab.tsx @@ -25,7 +25,7 @@ import { type AppCategory, } from "./app-categories"; import type { AppDefinition } from "@onecli/api/apps/types"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; import type { PageScope } from "@/lib/api"; import { queryKeys } from "@/lib/api/keys"; import { useConnections } from "@/hooks/use-connections"; @@ -40,8 +40,8 @@ import { useAppMessages, type AppConnectedEvent, } from "@/hooks/use-app-connected"; -import { getCurrentPlan } from "@/lib/user-plan"; import { ProAppDialog } from "@/lib/components/pro-app-dialog"; +import { UnavailableBadge } from "@/lib/components/unavailable-badge"; import { AppIcon } from "./app-icon"; import { ConnectAppDialog } from "./connect-app-dialog"; import { ConfigureCredentialsDialog } from "./configure-credentials-dialog"; @@ -121,10 +121,6 @@ export const AppsTab = ({ const configuredQuery = useConfiguredProviders(pageScope); const envDefaultsQuery = useEnvDefaultProviders(); const availableQuery = useAvailableApps(pageScope); - const planQuery = useQuery({ - queryKey: queryKeys.userPlan.all(), - queryFn: getCurrentPlan, - }); const connectionCounts = useMemo(() => { const counts = new Map(); @@ -143,12 +139,10 @@ export const AppsTab = ({ () => new Set(envDefaultsQuery.data ?? []), [envDefaultsQuery.data], ); - const plan = planQuery.data ?? null; const loading = connectionsQuery.isPending || configuredQuery.isPending || - envDefaultsQuery.isPending || - planQuery.isPending; + envDefaultsQuery.isPending; const handleConnected = useCallback( ({ provider, connectionId }: AppConnectedEvent) => { @@ -387,10 +381,7 @@ export const AppsTab = ({ ) : ( filteredApps.map((app) => { const count = connectionCounts.get(app.id) ?? 0; - const isLocked = - !app.available || - (app.teamOnly === true && - !["team", "scale", "enterprise"].includes(plan ?? "")); + const isLocked = !app.available; return ( {cloudOnly ? ( - - - - - - - Team - - + ) : (
{!hideDetails && ( diff --git a/apps/web/src/app/(dashboard)/connections/_components/configure-credentials-dialog.tsx b/apps/web/src/app/(dashboard)/connections/_components/configure-credentials-dialog.tsx index 9aed0cef..f910a6e1 100644 --- a/apps/web/src/app/(dashboard)/connections/_components/configure-credentials-dialog.tsx +++ b/apps/web/src/app/(dashboard)/connections/_components/configure-credentials-dialog.tsx @@ -15,7 +15,6 @@ import { SecretInput } from "@/components/secret-input"; import type { PageScope } from "@/lib/api"; import { useSaveAppConfig } from "@/hooks/use-app-config"; import type { OAuthConfigField } from "@onecli/api/apps/types"; -import { IS_CLOUD } from "@/lib/env"; import { AppIcon } from "./app-icon"; import { RedirectUri } from "./redirect-uri"; @@ -133,21 +132,6 @@ export const ConfigureCredentialsDialog = ({ > {saving ? "Saving..." : "Save & Connect"} - - {!IS_CLOUD && ( -

- Or use{" "} - - OneCLI Cloud - {" "} - for pre-configured connections. -

- )}
diff --git a/apps/web/src/lib/components/condition-builder.tsx b/apps/web/src/lib/components/condition-builder.tsx index 97064d12..1bb1d88f 100644 --- a/apps/web/src/lib/components/condition-builder.tsx +++ b/apps/web/src/lib/components/condition-builder.tsx @@ -10,16 +10,8 @@ export interface ConditionBuilderProps { export const ConditionBuilder = ({}: ConditionBuilderProps) => (

- Match conditions (body content, headers) are available on{" "} - - OneCLI Cloud - - . + Match conditions (body content, headers) are not yet available in this + build.

); diff --git a/apps/web/src/lib/components/pro-app-dialog.tsx b/apps/web/src/lib/components/pro-app-dialog.tsx index cf6aa7a4..07030b94 100644 --- a/apps/web/src/lib/components/pro-app-dialog.tsx +++ b/apps/web/src/lib/components/pro-app-dialog.tsx @@ -1,7 +1,5 @@ "use client"; -import { ExternalLink } from "lucide-react"; -import { Button } from "@onecli/ui/components/button"; import { Dialog, DialogContent, @@ -9,6 +7,7 @@ import { DialogTitle, } from "@onecli/ui/components/dialog"; import { AppIcon } from "@/app/(dashboard)/connections/_components/app-icon"; +import { UnavailableBadge } from "@/lib/components/unavailable-badge"; interface ProAppDialogProps { appName: string; @@ -19,6 +18,13 @@ interface ProAppDialogProps { onOpenChange: (open: boolean) => void; } +/** + * Shown when the user opens something this build does not implement: an + * `available: false` registry app (Connections list) or a capability without an + * OSS implementation (granular access). Informational only — the dialog's close + * button is the only action. Every EE edition aliases this module away + * (`next.config.js` → `@/ee/apps/pro-app-dialog`). + */ export const ProAppDialog = ({ appName, appIcon, @@ -44,72 +50,16 @@ export const ProAppDialog = ({ {appName} -
- - - - - - Team - +
+

{description}

- Available on OneCLI Cloud and on-prem enterprise plans. + Not yet available in this build.

- -
- - -
diff --git a/apps/web/src/lib/components/team-badge.tsx b/apps/web/src/lib/components/team-badge.tsx deleted file mode 100644 index f9d9e4df..00000000 --- a/apps/web/src/lib/components/team-badge.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * The house "Team" pill marking an app that needs a paid OneCLI plan — the - * same badge the Connections list (`apps-tab.tsx` AppRow) and `ProAppDialog` - * render inline. Extracted for the policy editor's cloud-only-app surfaces; - * the two existing inline copies are untouched (future cleanup). - */ -export const TeamBadge = () => ( - - - - - - - Team - - -); diff --git a/apps/web/src/lib/components/unavailable-badge.tsx b/apps/web/src/lib/components/unavailable-badge.tsx new file mode 100644 index 00000000..ca229ff7 --- /dev/null +++ b/apps/web/src/lib/components/unavailable-badge.tsx @@ -0,0 +1,11 @@ +/** + * The house pill marking an integration or capability this build does not + * implement (`available: false` registry entries, and the locked policy-editor + * surfaces). Rendered by the Connections list, the policy editor's app + * pickers, and `ProAppDialog`. + */ +export const UnavailableBadge = () => ( + + Unavailable + +); diff --git a/apps/web/src/lib/init/api.ts b/apps/web/src/lib/init/api.ts index 2512b8cc..0c9b01e8 100644 --- a/apps/web/src/lib/init/api.ts +++ b/apps/web/src/lib/init/api.ts @@ -1,6 +1,5 @@ import type { CreateApiAppOptions } from "@onecli/api"; import { ossNewProjectPolicySeeder } from "@onecli/api/services/policy-oss-cutover"; -import { ossPolicyValidator } from "@onecli/api/services/policy-oss-locks"; /** * The OSS edition's API wiring. Every EE edition ALIASES THIS FILE AWAY @@ -8,11 +7,12 @@ import { ossPolicyValidator } from "@onecli/api/services/policy-oss-locks"; * here is OSS-only by construction: * * - the new-project seeder gives fresh projects their published Default Rule — - * the per-project enforce signal — pinned to ALLOW since step 6; - * - the policy validator LOCKS granular resource scoping (a OneCLI Cloud - * capability the OSS gateway does not enforce) with a loud 422. + * the per-project enforce signal — pinned to ALLOW since step 6. + * + * No `policyValidator` is wired: the provider-hook default is permissive, so + * granular resource scoping and cloud-only app targets are accepted at the API + * layer. The gateway does not yet ENFORCE resource scoping — see Tier 3. */ export const eeOverrides: CreateApiAppOptions | undefined = { newOrgPolicySeeder: ossNewProjectPolicySeeder, - policyValidator: ossPolicyValidator, }; diff --git a/apps/web/src/lib/policy-editor/_components/app-select.tsx b/apps/web/src/lib/policy-editor/_components/app-select.tsx index 1dd255b2..a3e831b1 100644 --- a/apps/web/src/lib/policy-editor/_components/app-select.tsx +++ b/apps/web/src/lib/policy-editor/_components/app-select.tsx @@ -11,7 +11,7 @@ import { } from "@onecli/ui/components/popover"; import { getApp, getApps } from "@onecli/api/apps/registry"; import { AppIcon } from "@/app/(dashboard)/connections/_components/app-icon"; -import { TeamBadge } from "@/lib/components/team-badge"; +import { UnavailableBadge } from "@/lib/components/unavailable-badge"; /** * True when the registry knows the app but this edition can't connect it — @@ -96,7 +96,7 @@ export const AppSelect = ({ value, onChange, id, invalid }: AppSelectProps) => { size={18} /> {selectedApp.name} - {!selectedApp.available && } + {!selectedApp.available && } ) : ( Select an app… @@ -141,7 +141,7 @@ export const AppSelect = ({ value, onChange, id, invalid }: AppSelectProps) => { {a.name} - {!a.available && } + {!a.available && } {a.id === value && ( )} diff --git a/apps/web/src/lib/policy-editor/_components/app-target-fields.tsx b/apps/web/src/lib/policy-editor/_components/app-target-fields.tsx index 813b50de..94c1ffba 100644 --- a/apps/web/src/lib/policy-editor/_components/app-target-fields.tsx +++ b/apps/web/src/lib/policy-editor/_components/app-target-fields.tsx @@ -13,10 +13,11 @@ import { cn } from "@onecli/ui/lib/utils"; import { getApp } from "@onecli/api/apps/registry"; import { AppSelect } from "./app-select"; import { AppToolsPicker } from "./app-tools-picker"; -import { TeamBadge } from "@/lib/components/team-badge"; +import { UnavailableBadge } from "@/lib/components/unavailable-badge"; // Edition seam: EE aliases to the real granular resource editor; the OSS -// module is a locked "available on OneCLI Cloud" hint. Alias key on purpose — -// a relative import would bypass turbopack resolveAlias in EE builds. +// module is a locked "not available in this build" hint. Alias key on +// purpose — a relative import would bypass turbopack resolveAlias in EE +// builds. import { ResourceScopeFields } from "@/lib/policy-editor/resource-scope"; import type { Connection } from "@/lib/api"; @@ -144,18 +145,10 @@ export const AppTargetFields = ({ role="status" className="flex items-center gap-2.5 rounded-md border border-dashed px-3 py-2.5" > - +

- {providerName(value.provider)} connections are available on{" "} - - OneCLI Cloud - - . + {providerName(value.provider)} connections are not yet available in + this build.

) : ( diff --git a/apps/web/src/lib/policy-editor/identity-picker.tsx b/apps/web/src/lib/policy-editor/identity-picker.tsx index 85f7b932..2979506b 100644 --- a/apps/web/src/lib/policy-editor/identity-picker.tsx +++ b/apps/web/src/lib/policy-editor/identity-picker.tsx @@ -4,7 +4,7 @@ import type { ProjectionIdentity } from "@/lib/api"; /** * The OSS identity-picker seam (step 9.5). Directory identities (users, - * user-groups) are a OneCLI Cloud capability, and since attach-model step 6 + * user-groups) are not implemented in this build, and since attach-model step 6 * the only policy console left is the ORG one — which OSS does not mount at * all. So this stub can never render; it exists to keep the shared rule form * compiling in an OSS build. The EE editions alias this file to diff --git a/apps/web/src/lib/policy-editor/resource-scope.tsx b/apps/web/src/lib/policy-editor/resource-scope.tsx index e01916ca..d905e195 100644 --- a/apps/web/src/lib/policy-editor/resource-scope.tsx +++ b/apps/web/src/lib/policy-editor/resource-scope.tsx @@ -5,11 +5,11 @@ import type { Connection } from "@/lib/api"; /** * The OSS resource-scope seam (step 9.5): granular per-resource scoping * (GitHub repositories / Dropbox folders on a connection's injected - * credential) is a OneCLI Cloud capability — the OSS gateway has no guard to - * enforce it and the API locks it with a 422. Rendered only where the real - * editor would appear (a single specific connection on an Allow), as a locked - * capability hint. The EE editions alias this file to - * `@/ee/policy-editor/resource-scope` (the real fields). + * credential) is not implemented in this build — the gateway has no guard to + * enforce it (Tier 3). Rendered only where the real editor would appear (a + * single specific connection on an Allow), as a locked capability hint. The + * EE editions alias this file to `@/ee/policy-editor/resource-scope` (the + * real fields). */ export interface ResourceScopeFieldsProps { @@ -23,6 +23,6 @@ export const ResourceScopeFields: ( ) => React.JSX.Element = () => (

Resource scoping (limit this connection to specific repositories or folders) - is available on OneCLI Cloud. + is not yet available in this build.

); diff --git a/apps/web/src/lib/user-plan.tsx b/apps/web/src/lib/user-plan.tsx index 172ea050..c6997ec0 100644 --- a/apps/web/src/lib/user-plan.tsx +++ b/apps/web/src/lib/user-plan.tsx @@ -3,5 +3,10 @@ /** OSS default: no redirect needed. The EE editions override this via turbopack alias. */ export const checkDashboardRedirect = async (): Promise => null; -/** OSS default: no plan. The EE editions override this via turbopack alias. */ -export const getCurrentPlan = async (): Promise => null; +/** + * This build is fully entitled — mirrors what on-prem reports via + * ONPREM_ENTITLEMENT_ALIASES (`next.config.js`), so plan-gated apps and + * features are never shown as locked. The EE editions override this via + * turbopack alias. + */ +export const getCurrentPlan = async (): Promise => "enterprise"; diff --git a/packages/api/src/apps/connect-credentials.test.ts b/packages/api/src/apps/connect-credentials.test.ts index 3882bad4..830c7ffa 100644 --- a/packages/api/src/apps/connect-credentials.test.ts +++ b/packages/api/src/apps/connect-credentials.test.ts @@ -112,7 +112,7 @@ describe("resolveConnectCredentials", () => { }); expect(result).toEqual({ ok: false, - error: 'Provider "cloudy" is only available in OneCLI Cloud', + error: 'Provider "cloudy" is not yet available in this build', }); }); diff --git a/packages/api/src/apps/connect-credentials.ts b/packages/api/src/apps/connect-credentials.ts index 754698a2..a8b72926 100644 --- a/packages/api/src/apps/connect-credentials.ts +++ b/packages/api/src/apps/connect-credentials.ts @@ -70,7 +70,7 @@ export const resolveConnectCredentials = async ( if (activeMethod.type === "cloud_only") { return { ok: false, - error: `Provider "${provider}" is only available in OneCLI Cloud`, + error: `Provider "${provider}" is not yet available in this build`, }; } diff --git a/packages/api/src/lib/policy-flags.test.ts b/packages/api/src/lib/policy-flags.test.ts deleted file mode 100644 index 6ad9cb89..00000000 --- a/packages/api/src/lib/policy-flags.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { isOssEdition } from "./policy-flags"; - -// The OSS edition drives how the shared policy service phrases capability -// rejections (a OneCLI Cloud pointer there, byte-identical everywhere else), so -// the edition resolution itself is pinned: EDITION first, NEXT_PUBLIC_EDITION as -// the fallback, and an unset/unknown value parsing as OSS. -describe("isOssEdition", () => { - const originalEdition = process.env.EDITION; - const originalPublicEdition = process.env.NEXT_PUBLIC_EDITION; - - afterEach(() => { - if (originalEdition === undefined) delete process.env.EDITION; - else process.env.EDITION = originalEdition; - if (originalPublicEdition === undefined) - delete process.env.NEXT_PUBLIC_EDITION; - else process.env.NEXT_PUBLIC_EDITION = originalPublicEdition; - }); - - it.each([ - ["oss", true], - ["onprem-slim", false], - ["onprem-full", false], - ["cloud", false], - ["", true], // unset edition parses as oss - ])("edition %s → %s", (edition, expected) => { - delete process.env.NEXT_PUBLIC_EDITION; - process.env.EDITION = edition; - expect(isOssEdition()).toBe(expected); - }); - - it("falls back to NEXT_PUBLIC_EDITION when EDITION is unset", () => { - delete process.env.EDITION; - process.env.NEXT_PUBLIC_EDITION = "cloud"; - expect(isOssEdition()).toBe(false); - }); -}); diff --git a/packages/api/src/lib/policy-flags.ts b/packages/api/src/lib/policy-flags.ts deleted file mode 100644 index 138be7c0..00000000 --- a/packages/api/src/lib/policy-flags.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Policy runtime edition helpers. Pure and dependency-free (reads only - * `process.env` plus the pure edition parser), so it is safe to import from - * routes, middleware, or a standalone startup entry. - */ -import { parseEdition } from "./edition"; - -const runtimeEdition = () => - parseEdition(process.env.EDITION ?? process.env.NEXT_PUBLIC_EDITION).edition; - -/** Whether this runtime is the OSS edition — used by the shared policy - * service to phrase capability rejections as OneCLI Cloud pointers there - * (byte-identical messages everywhere else). */ -export const isOssEdition = (): boolean => runtimeEdition() === "oss"; diff --git a/packages/api/src/providers/hooks/policy-validator.ts b/packages/api/src/providers/hooks/policy-validator.ts index 7f3eb03b..b1e9731b 100644 --- a/packages/api/src/providers/hooks/policy-validator.ts +++ b/packages/api/src/providers/hooks/policy-validator.ts @@ -10,8 +10,7 @@ export interface PolicyValidator { /** * Edition gate over a rule's targets, run on create/update (never publish — * a pre-existing row must not brick a whole-scope publish). Absent = - * permissive (the default); the OSS edition wires an implementation that - * rejects app targets for cloud-only providers its gateway can't enforce. + * permissive (the default); no edition in this repo wires one. */ validateTargets?(targets: PolicyTargetInput[]): Promise; } diff --git a/packages/api/src/services/policy-oss-locks.test.ts b/packages/api/src/services/policy-oss-locks.test.ts deleted file mode 100644 index 71df400c..00000000 --- a/packages/api/src/services/policy-oss-locks.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { ossPolicyValidator } from "./policy-oss-locks"; -import { ServiceError } from "./errors"; -import type { PolicyTargetInput } from "../validations/policy"; - -// The OSS edition's policy locks. These run against the DEFAULT registries — -// exactly what an OSS process sees (no initEeApps): base apps available, the -// shared EE-stub list (aws-role, datadog, …) present with `available: false`. - -describe("ossPolicyValidator.validate (granular session policy)", () => { - it("rejects unconditionally with the cloud-only message", async () => { - await expect( - ossPolicyValidator.validate("org-1", "github", null, { - repositories: ["a/b"], - }), - ).rejects.toMatchObject({ - code: "UNPROCESSABLE", - message: - "Granular resource scoping (repositories/folders) is available on OneCLI Cloud.", - }); - }); -}); - -describe("ossPolicyValidator.validateTargets (cloud-only apps)", () => { - const run = (targets: PolicyTargetInput[]) => - ossPolicyValidator.validateTargets!(targets); - - it("rejects an app target for a cloud-only (EE-stub) provider, naming the app", async () => { - const err = await run([{ kind: "app", provider: "aws-role" }]).catch( - (e: unknown) => e, - ); - expect(err).toBeInstanceOf(ServiceError); - expect((err as ServiceError).code).toBe("UNPROCESSABLE"); - expect((err as ServiceError).message).toBe( - "AWS Role connections are available on OneCLI Cloud.", - ); - }); - - it("rejects when the cloud-only target is mixed among valid ones", async () => { - await expect( - run([ - { kind: "network", hostPattern: "api.example.com" }, - { kind: "app", provider: "datadog" }, - ]), - ).rejects.toMatchObject({ code: "UNPROCESSABLE" }); - }); - - it("accepts a base (connectable) app", async () => { - await expect( - run([{ kind: "app", provider: "github" }]), - ).resolves.toBeUndefined(); - }); - - it("accepts an UNKNOWN provider string (typos, and onprem-style excluded apps, stay non-fatal)", async () => { - await expect( - run([{ kind: "app", provider: "not-a-real-app" }]), - ).resolves.toBeUndefined(); - }); - - it("ignores non-app target kinds", async () => { - await expect( - run([ - { kind: "network", hostPattern: "*.x.com" }, - { kind: "secret", secretScope: "project" }, - { kind: "connection", connectionId: "conn-1" }, - ]), - ).resolves.toBeUndefined(); - }); - - it("accepts an empty target list", async () => { - await expect(run([])).resolves.toBeUndefined(); - }); -}); diff --git a/packages/api/src/services/policy-oss-locks.ts b/packages/api/src/services/policy-oss-locks.ts deleted file mode 100644 index c2fcceaf..00000000 --- a/packages/api/src/services/policy-oss-locks.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * The OSS edition's policy locks (step 9.5): shared implementations wired ONLY - * through the OSS init seam (`apps/web/src/lib/init/api.ts`, aliased away by - * every EE edition). The provider-hook DEFAULTS stay permissive — cloud's - * in-process web app relies on them before its init warms — so the locks are - * wired, not defaulted. - */ -import { ServiceError } from "./errors"; -import type { PolicyValidator } from "../providers"; -import { getApp } from "../apps/registry"; - -/** - * OSS rejects granular resource scoping outright. One seam covers both storage - * paths: `assertSessionPolicyValid` (policy-rule create/update/publish) and - * the legacy equipment `sessionPolicy` write both call - * `getPolicyValidator().validate(...)`. Without this lock OSS would - * accept-and-store `{repositories}`/`{folders}` that its gateway never - * enforces — false security, worse than absence. - * - * `validateTargets` (create/update only) rejects app targets naming a - * cloud-only provider — the registry's EE stubs (`available: false`), which - * the OSS gateway's base catalog can't resolve, so the rule would be dead. - * The editor locks the same key visually; this is the belt for the CLI/API - * path. App targets only: `assertTargetsValid` proves a connection target's - * OWNERSHIP, not connectability — but no OSS flow can mint an EE-provider - * connection in the first place (connect rejects `cloud_only` providers), so - * connection targets need no provider check. Unknown provider strings stay - * accepted (today's behavior). - */ -export const ossPolicyValidator: PolicyValidator = { - validate: async () => { - throw new ServiceError( - "UNPROCESSABLE", - "Granular resource scoping (repositories/folders) is available on OneCLI Cloud.", - ); - }, - validateTargets: async (targets) => { - for (const t of targets) { - if (t.kind !== "app") continue; - const app = getApp(t.provider); - if (app?.available === false) { - throw new ServiceError( - "UNPROCESSABLE", - `${app.name} connections are available on OneCLI Cloud.`, - ); - } - } - }, -}; diff --git a/packages/api/src/services/policy-service.ts b/packages/api/src/services/policy-service.ts index 5c1b2a07..a36c5aa8 100644 --- a/packages/api/src/services/policy-service.ts +++ b/packages/api/src/services/policy-service.ts @@ -1,6 +1,5 @@ import { db, Prisma } from "@onecli/db"; import { ServiceError } from "./errors"; -import { isOssEdition } from "../lib/policy-flags"; import { type ResourceScope } from "./resource-scope"; import { getPolicyValidator, getRuleActionGate } from "../providers"; import type { @@ -363,15 +362,12 @@ export const assertIdentitiesValid = async ( const userIds = idsOf("user"); const groupIds = idsOf("group"); - // Level restriction. The OSS edition phrases it as the capability lock it - // is there (directory identities are a OneCLI Cloud capability); the EE - // editions keep the scope-shaped message byte-identical. + // Level restriction — the same scope-shaped rule in every edition: a project + // rule targets agents, an org rule targets directory identities. if (base.scope === "project" && (userIds.length || groupIds.length)) { throw new ServiceError( "UNPROCESSABLE", - isOssEdition() - ? "Group and user identities are available on OneCLI Cloud." - : "A project rule can target a specific agent or all agents.", + "A project rule can target a specific agent or all agents.", ); } if (base.scope === "organization" && agentIds.length) { @@ -561,8 +557,8 @@ export const assertTargetsValid = async ( * with a connection target — then runs the wired policy validator per * connection target. EE deep-checks the shape against the provider (repos * exist on the installation, absolute Dropbox paths) and gates the team+ - * entitlement; OSS wires a validator that REJECTS session policies outright - * (granular scoping is a OneCLI Cloud capability — step 9.5). A no-op for + * entitlement; OSS wires no validator — the permissive default accepts + * session policies the OSS gateway does not yet enforce (Tier 3). A no-op for * behavioral / absent conditions. Same org fence as `assertTargetsValid`. * * Callers pass the MERGED (post-update) action/targets/conditions, so no PATCH