diff --git a/config/components/service-catalog/service.yaml b/config/components/service-catalog/service.yaml index 05443ad1..18f5c7ea 100644 --- a/config/components/service-catalog/service.yaml +++ b/config/components/service-catalog/service.yaml @@ -13,6 +13,6 @@ spec: producerProjectRef: name: datum-cloud enablementPolicy: - mode: GatedByProvider + mode: SelfService phase: Published serviceName: compute.datumapis.com diff --git a/docs/agent/llms-full.txt b/docs/agent/llms-full.txt index 197ff54b..feaa9ccc 100644 --- a/docs/agent/llms-full.txt +++ b/docs/agent/llms-full.txt @@ -206,8 +206,9 @@ about them explicitly rather than defaulting them quietly. Two things are outside your reach entirely. You cannot build or push a container image: it must already exist in a registry, fully qualified, and be built for the runtime Datum runs it on — `datumctl compute build` is the step -the customer runs. And you cannot enable Compute for a project or grant it -quota; both are Datum's to give. +the customer runs. These tools cannot enable Compute or change quota. The +customer can enable Compute with `datumctl services enable compute.datumapis.com`; +default quota is provisioned when the service becomes active. Writing is a sequence, not a call. `compute_workload_render` turns inputs into a Workload manifest and touches nothing. The rest is the platform's own change diff --git a/docs/agent/skills/workload-create.md b/docs/agent/skills/workload-create.md index cbfdadf7..c0d38a43 100644 --- a/docs/agent/skills/workload-create.md +++ b/docs/agent/skills/workload-create.md @@ -17,8 +17,9 @@ Two things you cannot do, however the request is phrased: - **You cannot build or push an image.** The image has to exist in a registry before any of this starts. -- **You cannot turn Compute on for a project, and you cannot grant it quota.** - Both are Datum's to grant. Say so and name the step the user takes. +- **You cannot turn Compute on for a project or change its quota with these tools.** + The user can enable Compute with `datumctl services enable compute.datumapis.com`. + Say so and name the step the user takes. The project is fixed by the request that reached you. There is no tool argument for it, so you cannot create a workload in a project other than the one the @@ -32,10 +33,10 @@ different answer: | Check | Tool | If it fails | |---|---|---| -| Compute is offered to the project | `locations_list` with service `compute` | Nothing can be placed. Datum's to enable — the user runs `datumctl services enable compute.datumapis.com`, and approval is a manual step on Datum's side. | +| Compute is offered to the project | `locations_list` with service `compute` | Nothing can be placed. The user can enable Compute with `datumctl services enable compute.datumapis.com`. | | Somewhere to run it | `locations_list` with service `compute` | The location names it returns are the only ones a placement may name. A location missing from the list is one compute is not offered in. An empty list means nothing is available to this project yet; that is Datum's, not something the user can add. | | A network | `resources_list` for kind `Network` in `networking.datumapis.com/v1alpha` | `default` by convention. If the one the workload names is missing, add a Network manifest of that name to the same `resources_plan` call — the plan orders it ahead of the Workload. Say so when you show the plan, because it is a second object being created. | -| Quota | `quota_get` with service `compute.datumapis.com` | Quota is granted by Datum and cannot be self-served. A project with none can still create a workload; its instances then sit at `QuotaGranted=False` with `QuotaNoBudget` and never start. | +| Quota | `quota_get` with service `compute.datumapis.com` | Default quota is provisioned when Compute is enabled. If none appears, wait for provisioning; an instance created without it sits at `QuotaGranted=False` with `QuotaNoBudget` and cannot start. | Do the quota arithmetic before you apply, not after. Replicas times the instance type's size from `compute_instance_types_list`, against what `quota_get` says is @@ -361,8 +362,8 @@ could not get a legitimate creation done: Not gaps, however awkward the turn: - **No image.** Building one was never in scope here. -- **No quota, or Compute not enabled.** Those are grants, and the tools - reporting them accurately is the tools working. +- **No quota, or Compute not enabled.** The user can enable Compute; default quota + follows activation. The tools reporting these states accurately is the tools working. - **A rejection that was right.** An unsupported instance type or an unattached volume is the plan doing its job — that is the answer, and it saved a broken workload. diff --git a/internal/cmd/compute/util/activation.go b/internal/cmd/compute/util/activation.go index 3b5cd3cc..b8d1255c 100644 --- a/internal/cmd/compute/util/activation.go +++ b/internal/cmd/compute/util/activation.go @@ -24,9 +24,8 @@ const ComputeServiceName = "compute.datumapis.com" // catalog. // // The SDK reads the enablement mode and description from the live Service -// instead of a hand-written copy. Without the mode, the SDK treats compute as -// self-service. The gate would then submit access requests without asking, -// including from CI, even though compute requires provider approval. +// instead of a hand-written copy, so commands follow the published +// self-service policy and can enable Compute on first use. func ResolveComputeService(ctx context.Context) (activation.ServiceInfo, error) { cfg, err := restConfig(func(apiHost string) string { return "https://" + apiHost }) if err != nil { diff --git a/internal/cmd/compute/util/activation_test.go b/internal/cmd/compute/util/activation_test.go index 34c6098f..a23bd069 100644 --- a/internal/cmd/compute/util/activation_test.go +++ b/internal/cmd/compute/util/activation_test.go @@ -12,9 +12,8 @@ import ( // TestComputeServiceResolvesFromPublishedManifest checks the gate's lookup // against the Service manifest compute publishes. If the name drifts from the -// manifest, every gated command fails. If the manifest loses its enablement -// policy, the gate treats compute as self-service and submits access requests -// without asking. +// manifest, every gated command fails. Keep the explicit self-service policy +// in sync with the service we publish. func TestComputeServiceResolvesFromPublishedManifest(t *testing.T) { raw, err := os.ReadFile("../../../../config/components/service-catalog/service.yaml") if err != nil { @@ -33,8 +32,11 @@ func TestComputeServiceResolvesFromPublishedManifest(t *testing.T) { if info.ObjectName != "compute" { t.Errorf("ObjectName = %q, want %q", info.ObjectName, "compute") } - if info.EnablementMode != servicesv1alpha1.EnablementModeGatedByProvider { - t.Errorf("EnablementMode = %q, want %q", info.EnablementMode, servicesv1alpha1.EnablementModeGatedByProvider) + if svc.Spec.EnablementPolicy == nil || svc.Spec.EnablementPolicy.Mode != servicesv1alpha1.EnablementModeSelfService { + t.Fatalf("published Compute service must explicitly enable self-service") + } + if info.EnablementMode != servicesv1alpha1.EnablementModeSelfService { + t.Errorf("EnablementMode = %q, want %q", info.EnablementMode, servicesv1alpha1.EnablementModeSelfService) } } diff --git a/ui/consumer/src/components/compute-enablement-banner.tsx b/ui/consumer/src/components/compute-enablement-banner.tsx index 5957efef..4cbfe86c 100644 --- a/ui/consumer/src/components/compute-enablement-banner.tsx +++ b/ui/consumer/src/components/compute-enablement-banner.tsx @@ -1,17 +1,16 @@ /** * Shown on the Workloads page in place of the CLI empty-state when the * project doesn't (yet) have an Active `compute` ServiceEntitlement — see - * `lib/api.ts`'s `useComputeEntitlement`/`useRequestComputeAccess`. The + * `lib/api.ts`'s `useComputeEntitlement`/`useEnableCompute`. The * "Enable Compute" button creates the same ServiceEntitlement object that - * `datumctl compute deploy`'s "Would you like to request access?" prompt - * does, so a request made here shows up identically to one made via the CLI. + * `datumctl compute deploy` creates on first use. * * The `compact` variant is the same states and wording, sized for the * project home page's Workloads column. */ import { Banner } from './cli-section'; import { ColumnEmpty } from '../cards/home-column-parts'; -import { useRequestComputeAccess, type EntitlementPhase } from '../lib/api'; +import { useEnableCompute, type EntitlementPhase } from '../lib/api'; import { Button } from '@datum-cloud/datum-ui/button'; import { Icon } from '@datum-cloud/datum-ui/icons'; import { toast } from '@datum-cloud/datum-ui/toast'; @@ -19,7 +18,7 @@ import { ClockIcon, ShieldAlertIcon, XCircleIcon } from 'lucide-react'; type BannerState = 'NotRequested' | 'PendingApproval' | 'Rejected'; -const COPY: Record = { +const COPY: Record = { NotRequested: { title: 'This project does not have Compute enabled', description: 'You need to enable Compute to create Workloads in this project.', @@ -27,17 +26,14 @@ const COPY: Record, - cta: 'Enable Compute', }, Rejected: { - title: 'Compute access was declined', - description: 'The request to enable Compute for this project was declined. You can request access again.', + title: 'Compute could not be enabled', + description: 'Run datumctl compute access request --renew to retry, or contact support.', icon: , - cta: 'Request access again', }, }; @@ -63,18 +59,18 @@ export function ComputeEnablementBanner({ phase: EntitlementPhase | null; compact?: boolean; }) { - const { mutate, isPending } = useRequestComputeAccess(projectId); + const { mutate, isPending } = useEnableCompute(projectId); const state = bannerState(phase); const copy = COPY[state]; const handleClick = () => { mutate(undefined, { - onSuccess: () => toast.success('Requested Compute access for this project'), - onError: () => toast.error('Failed to request Compute access'), + onSuccess: () => toast.success('Enabling Compute for this project'), + onError: () => toast.error('Failed to enable Compute'), }); }; - const action = state !== 'PendingApproval' && ( + const action = state === 'NotRequested' && (