- 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