{
  "description": "GarageBucket is the Schema for the garagebuckets API",
  "properties": {
    "apiVersion": {
      "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
      "type": [
        "string",
        "null"
      ]
    },
    "kind": {
      "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
      "type": [
        "string",
        "null"
      ]
    },
    "metadata": {
      "type": [
        "object",
        "null"
      ]
    },
    "spec": {
      "additionalProperties": false,
      "description": "GarageBucketSpec defines the desired state of GarageBucket",
      "properties": {
        "bucketId": {
          "description": "BucketID pins this resource to a pre-existing Garage bucket ID.\nWhen set, the operator will never create a new bucket — it only manages\nsettings and key permissions for the identified bucket. Takes priority\nover GlobalAlias-based lookup. Useful for importing existing buckets and\nfor recovery after cluster incidents.",
          "type": [
            "string",
            "null"
          ]
        },
        "clusterRef": {
          "additionalProperties": false,
          "description": "ClusterRef references the GarageCluster this bucket belongs to",
          "properties": {
            "kubeConfigSecretRef": {
              "additionalProperties": false,
              "description": "KubeConfigSecretRef is reserved for a future remote Kubernetes client integration.\nIt is currently rejected by admission because the operator does not use it.",
              "properties": {
                "key": {
                  "description": "The key of the secret to select from.  Must be a valid secret key.",
                  "type": "string"
                },
                "name": {
                  "default": "",
                  "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "optional": {
                  "description": "Specify whether the Secret or its key must be defined",
                  "type": [
                    "boolean",
                    "null"
                  ]
                }
              },
              "required": [
                "key"
              ],
              "type": [
                "object",
                "null"
              ],
              "x-kubernetes-map-type": "atomic"
            },
            "name": {
              "description": "Name of the GarageCluster resource.",
              "type": "string"
            },
            "namespace": {
              "description": "Namespace of the GarageCluster. Defaults to the referencing resource's namespace.\nCross-namespace references require a GarageReferenceGrant where supported by\nthe owning resource. GarageNode and GarageAdminToken reject them.",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "name"
          ],
          "type": "object"
        },
        "deletionPolicy": {
          "default": "Delete",
          "description": "DeletionPolicy controls whether deleting this resource also deletes the\ncorresponding Garage bucket. The default is Delete.",
          "enum": [
            "Delete",
            "Retain"
          ],
          "type": [
            "string",
            "null"
          ]
        },
        "globalAlias": {
          "description": "GlobalAlias is the global alias for this bucket (optional)\nIf not set, the bucket name from metadata.name is used",
          "type": [
            "string",
            "null"
          ]
        },
        "keyPermissions": {
          "description": "KeyPermissions grants access to specific GarageKeys.\n\nNote: Permissions can be granted from either direction:\n- Here (GarageBucket.keyPermissions): Grant keys access to this bucket\n- On GarageKey (GarageKey.bucketPermissions): Grant the key access to buckets\n\nBoth approaches are equivalent and result in the same Garage API calls.\nUse whichever is more convenient for your workflow:\n- Bucket-centric: Define all key access on the bucket\n- Key-centric: Define all bucket access on the key\n\nIf the same permission is defined in both places, they are merged (not conflicting).",
          "items": {
            "additionalProperties": false,
            "description": "KeyPermission grants access to a key",
            "properties": {
              "keyRef": {
                "additionalProperties": false,
                "description": "KeyRef references the GarageKey by name (and optionally namespace).",
                "properties": {
                  "name": {
                    "description": "Name of the GarageKey.",
                    "type": "string"
                  },
                  "namespace": {
                    "description": "Namespace of the GarageKey. Defaults to the GarageBucket's namespace.\nCross-namespace references require a GarageReferenceGrant in the target namespace.",
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              },
              "owner": {
                "default": false,
                "description": "Owner allows bucket owner operations",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "read": {
                "default": false,
                "description": "Read allows reading objects",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "write": {
                "default": false,
                "description": "Write allows writing objects",
                "type": [
                  "boolean",
                  "null"
                ]
              }
            },
            "required": [
              "keyRef"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "lifecycle": {
          "additionalProperties": false,
          "description": "Lifecycle configures bucket lifecycle policies (object expiration,\nabort of incomplete multipart uploads).\n\nGarage exposes lifecycle only via the S3 API, not the admin API. The\noperator applies rules using an internal access key it manages per\nGarageCluster. Garage supports a strict subset of the AWS S3 lifecycle\nspec: only Expiration (days or date, no ExpiredObjectDeleteMarker) and\nAbortIncompleteMultipartUpload. Filters support prefix and object size\nbounds; tag filters and the deprecated rule-level Prefix are not\naccepted.\n\nGarage's lifecycle worker runs daily at midnight (UTC by default), so\nrule evaluation is asynchronous from reconciliation.",
          "properties": {
            "rules": {
              "description": "Rules to apply. The operator replaces the bucket's lifecycle\nconfiguration with this exact set on each reconcile.",
              "items": {
                "additionalProperties": false,
                "description": "LifecycleRule is a single lifecycle rule. At least one action\n(ExpirationDays, ExpirationDate, AbortIncompleteMultipartUploadDays)\nmust be set. ExpirationDays and ExpirationDate are mutually exclusive.",
                "properties": {
                  "abortIncompleteMultipartUploadDays": {
                    "description": "AbortIncompleteMultipartUploadDays aborts multipart uploads that have\nbeen pending for at least this many days.",
                    "format": "int32",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "expirationDate": {
                    "description": "ExpirationDate expires current objects on or after this UTC date.",
                    "format": "date-time",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "expirationDays": {
                    "description": "ExpirationDays expires current objects this many days after creation.",
                    "format": "int32",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "filter": {
                    "additionalProperties": false,
                    "description": "Filter narrows the rule to a subset of objects. If unset, the rule\napplies to every object in the bucket.",
                    "properties": {
                      "objectSizeGreaterThan": {
                        "description": "ObjectSizeGreaterThan matches objects strictly larger than this many\nbytes.",
                        "format": "int64",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "objectSizeLessThan": {
                        "description": "ObjectSizeLessThan matches objects strictly smaller than this many\nbytes.",
                        "format": "int64",
                        "minimum": 1,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "prefix": {
                        "description": "Prefix matches object keys starting with this string.",
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": [
                      "object",
                      "null"
                    ]
                  },
                  "id": {
                    "description": "ID is the rule identifier. Must be unique within the bucket.",
                    "minLength": 1,
                    "type": "string"
                  },
                  "status": {
                    "default": "Enabled",
                    "description": "Status enables or disables this rule. Disabled rules are sent to\nGarage but skipped by the lifecycle worker.",
                    "enum": [
                      "Enabled",
                      "Disabled"
                    ],
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id"
                ],
                "type": "object"
              },
              "type": [
                "array",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "localAliases": {
          "description": "LocalAliases are per-key local aliases for this bucket",
          "items": {
            "additionalProperties": false,
            "description": "LocalAlias is a bucket alias that is only visible to a specific key.\nUnlike global aliases (which any key can use), a local alias is scoped to one key —\nuseful when different teams share the same Garage cluster but use different bucket names.\nThe alias is accessible via S3 as a bucket name when authenticated with that key.",
            "properties": {
              "alias": {
                "description": "Alias is the bucket name this key will use to access the bucket.\nMust be unique within the key's alias namespace.",
                "type": "string"
              },
              "keyRef": {
                "description": "KeyRef is the name of the GarageKey in the same namespace that owns this alias.",
                "type": "string"
              }
            },
            "required": [
              "alias",
              "keyRef"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "quotas": {
          "additionalProperties": false,
          "description": "Quotas configures bucket quotas",
          "properties": {
            "maxObjects": {
              "description": "MaxObjects is the maximum number of objects",
              "format": "int64",
              "type": [
                "integer",
                "null"
              ]
            },
            "maxSize": {
              "description": "MaxSize is the maximum bucket size in bytes",
              "oneOf": [
                {
                  "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
                  "type": "string"
                },
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "x-kubernetes-int-or-string": true
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "website": {
          "additionalProperties": false,
          "description": "Website configures static website hosting for this bucket.\nNote: Only indexDocument and errorDocument are supported via the Admin API.\nFor advanced features (routing rules, redirectAll), use S3 PutBucketWebsite API directly.",
          "properties": {
            "enabled": {
              "description": "Enabled enables static website hosting.",
              "type": [
                "boolean",
                "null"
              ]
            },
            "errorDocument": {
              "description": "ErrorDocument is the error document to serve for 404s",
              "type": [
                "string",
                "null"
              ]
            },
            "indexDocument": {
              "default": "index.html",
              "description": "IndexDocument is the default index document (default: index.html)",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "websiteExposure": {
          "additionalProperties": false,
          "description": "WebsiteExposure optionally exposes a website-enabled bucket through\nKubernetes HTTP routing: an Ingress or a Gateway API HTTPRoute, created\nin this bucket's namespace and pointing at the referenced cluster's web\nAPI Service. At most one of Ingress and Gateway may be set. Requires\nspec.website.enabled. See the websiteExposure documentation for the\nhostname semantics (Garage resolves the bucket from the Host header).",
          "properties": {
            "backendRef": {
              "additionalProperties": false,
              "description": "BackendRef overrides the Service the exposure routes to. When unset,\nthe operator targets the referenced cluster's web API Service\n(\u003ccluster\u003e-gateway for unified clusters, \u003ccluster\u003e otherwise). For an\nIngress the referent must be a core/v1 Service in the bucket's\nnamespace (Ingress backends cannot cross namespaces). For an\nHTTPRoute any referent valid for spec.rules[].backendRefs is\naccepted, including cross-namespace ones (which additionally need a\ngateway API ReferenceGrant).",
              "properties": {
                "group": {
                  "description": "Group of the referent. Defaults to \"\" (the core API group).",
                  "maxLength": 253,
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "kind": {
                  "description": "Kind of the referent. Defaults to Service.",
                  "maxLength": 253,
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "name": {
                  "description": "Name of the referent.",
                  "maxLength": 253,
                  "minLength": 1,
                  "type": "string"
                },
                "namespace": {
                  "description": "Namespace of the referent. Defaults to the bucket's namespace (the\nexposure resource's namespace). For an Ingress it must stay the\nbucket's namespace: Ingress backends cannot cross namespaces.",
                  "maxLength": 63,
                  "minLength": 1,
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "name"
              ],
              "type": [
                "object",
                "null"
              ]
            },
            "gateway": {
              "additionalProperties": false,
              "description": "Gateway configures the generated Gateway API HTTPRoute. Mutually\nexclusive with Ingress. Requires the Gateway API CRDs to be installed;\nwithout them the operator reports a condition and does not fail the\nbucket.",
              "properties": {
                "annotations": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Annotations to add to the HTTPRoute.",
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "labels": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Labels to add to the HTTPRoute (for example external-dns or\nargo-rollouts annotations). Operator-managed labels take precedence\non conflict.",
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "parentRefs": {
                  "description": "ParentRefs are passed through verbatim to the HTTPRoute's\nspec.parentRefs (Gateway names, optional sectionName, and optional\ncross-namespace references). At least one is required.",
                  "items": {
                    "additionalProperties": false,
                    "description": "ParentReference identifies an API object (usually a Gateway) that can be considered\na parent of this resource (usually a route). There are two kinds of parent resources\nwith \"Core\" support:\n\n* Gateway (Gateway conformance profile)\n* Service (Mesh conformance profile, ClusterIP Services only)\n\nThis API may be extended in the future to support additional kinds of parent\nresources.\n\nThe API object must be valid in the cluster; the Group and Kind must\nbe registered in the cluster for this reference to be valid.",
                    "properties": {
                      "group": {
                        "default": "gateway.networking.k8s.io",
                        "description": "Group is the group of the referent.\nWhen unspecified, \"gateway.networking.k8s.io\" is inferred.\nTo set the core API group (such as for a \"Service\" kind referent),\nGroup must be explicitly set to \"\" (empty string).\n\nSupport: Core",
                        "maxLength": 253,
                        "pattern": "^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "kind": {
                        "default": "Gateway",
                        "description": "Kind is kind of the referent.\n\nThere are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile)\n* Service (Mesh conformance profile, ClusterIP Services only)\n\nSupport for other resources is Implementation-Specific.",
                        "maxLength": 63,
                        "minLength": 1,
                        "pattern": "^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "name": {
                        "description": "Name is the name of the referent.\n\nSupport: Core",
                        "maxLength": 253,
                        "minLength": 1,
                        "type": "string"
                      },
                      "namespace": {
                        "description": "Namespace is the namespace of the referent. When unspecified, this refers\nto the local namespace of the Route.\n\nNote that there are specific rules for ParentRefs which cross namespace\nboundaries. Cross-namespace references are only valid if they are explicitly\nallowed by something in the namespace they are referring to. For example:\nGateway has the AllowedRoutes field, and ReferenceGrant provides a\ngeneric way to enable any other kind of cross-namespace reference.\n\n\u003cgateway:experimental:description\u003e\nParentRefs from a Route to a Service in the same namespace are \"producer\"\nroutes, which apply default routing rules to inbound connections from\nany namespace to the Service.\n\nParentRefs from a Route to a Service in a different namespace are\n\"consumer\" routes, and these routing rules are only applied to outbound\nconnections originating from the same namespace as the Route, for which\nthe intended destination of the connections are a Service targeted as a\nParentRef of the Route.\n\u003c/gateway:experimental:description\u003e\n\nSupport: Core",
                        "maxLength": 63,
                        "minLength": 1,
                        "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "port": {
                        "description": "Port is the network port this Route targets. It can be interpreted\ndifferently based on the type of parent resource.\n\nWhen the parent resource is a Gateway, this targets all listeners\nlistening on the specified port that also support this kind of Route(and\nselect this Route). It's not recommended to set `Port` unless the\nnetworking behaviors specified in a Route must apply to a specific port\nas opposed to a listener(s) whose port(s) may be changed. When both Port\nand SectionName are specified, the name and port of the selected listener\nmust match both specified values.\n\n\u003cgateway:experimental:description\u003e\nWhen the parent resource is a Service, this targets a specific port in the\nService spec. When both Port (experimental) and SectionName are specified,\nthe name and port of the selected port must match both specified values.\n\u003c/gateway:experimental:description\u003e\n\nImplementations MAY choose to support other parent resources.\nImplementations supporting other types of parent resources MUST clearly\ndocument how/if Port is interpreted.\n\nFor the purpose of status, an attachment is considered successful as\nlong as the parent resource accepts it partially. For example, Gateway\nlisteners can restrict which Routes can attach to them by Route kind,\nnamespace, or hostname. If 1 of 2 Gateway listeners accept attachment\nfrom the referencing Route, the Route MUST be considered successfully\nattached. If no Gateway listeners accept attachment from this Route,\nthe Route MUST be considered detached from the Gateway.\n\nSupport: Extended",
                        "format": "int32",
                        "maximum": 65535,
                        "minimum": 1,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "sectionName": {
                        "description": "SectionName is the name of a section within the target resource. In the\nfollowing resources, SectionName is interpreted as the following:\n\n* Gateway: Listener name. When both Port (experimental) and SectionName\nare specified, the name and port of the selected listener must match\nboth specified values.\n* Service: Port name. When both Port (experimental) and SectionName\nare specified, the name and port of the selected listener must match\nboth specified values.\n\nImplementations MAY choose to support attaching Routes to other resources.\nIf that is the case, they MUST clearly document how SectionName is\ninterpreted.\n\nWhen unspecified (empty string), this will reference the entire resource.\nFor the purpose of status, an attachment is considered successful if at\nleast one section in the parent resource accepts it. For example, Gateway\nlisteners can restrict which Routes can attach to them by Route kind,\nnamespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\nthe referencing Route, the Route MUST be considered successfully\nattached. If no Gateway listeners accept attachment from this Route, the\nRoute MUST be considered detached from the Gateway.\n\nSupport: Core",
                        "maxLength": 253,
                        "minLength": 1,
                        "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "name"
                    ],
                    "type": "object"
                  },
                  "minItems": 1,
                  "type": "array"
                }
              },
              "required": [
                "parentRefs"
              ],
              "type": [
                "object",
                "null"
              ]
            },
            "hostnames": {
              "description": "Hostnames are the external hostnames the exposure routes on. When\nempty, the operator uses the single canonical hostname\n\u003cglobalAlias\u003e\u003cwebApi.rootDomain\u003e. Wildcards and duplicates are not\nsupported (duplicates are rejected by the validating webhook; the CRD\nschema cannot express uniqueItems).\n\nFor an HTTPRoute any hostname is accepted: a hostname that is neither\ncanonical nor the global alias gets a URLRewrite filter rewriting the\nHost header back to the canonical host, so Garage still resolves it to\nthis bucket. For an Ingress, only the canonical hostname and the\nglobal alias are accepted — an Ingress cannot rewrite the Host header,\nso any other hostname is refused on the WebsiteExposed condition.",
              "items": {
                "type": "string"
              },
              "maxItems": 16,
              "type": [
                "array",
                "null"
              ]
            },
            "ingress": {
              "additionalProperties": false,
              "description": "Ingress configures the generated Kubernetes Ingress. Mutually\nexclusive with Gateway. Only valid when the bucket and the referenced\ncluster share a namespace.",
              "properties": {
                "annotations": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Annotations to add to the Ingress (for example the TLS or\nproxy-protocol annotations your ingress controller expects).",
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "ingressClassName": {
                  "description": "IngressClassName is the ingress class the Ingress must match\n(spec.ingressClassName). When empty, the Ingress is left without a\nclass so the cluster's default ingress controller picks it up.",
                  "maxLength": 253,
                  "minLength": 1,
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "labels": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Labels to add to the Ingress. Operator-managed labels take precedence\non conflict.",
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "tlsSecretName": {
                  "description": "TLSSecretName is the name of a TLS Secret in the bucket's namespace\n(where the generated Ingress lives), attached to the Ingress\n(spec.tls). The Secret must already exist; the operator does not\nprovision certificates.",
                  "maxLength": 253,
                  "minLength": 1,
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "type": [
                "object",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        }
      },
      "required": [
        "clusterRef"
      ],
      "type": "object"
    },
    "status": {
      "additionalProperties": false,
      "description": "GarageBucketStatus defines the observed state of GarageBucket",
      "properties": {
        "bucketId": {
          "description": "BucketID is the internal Garage bucket ID",
          "type": [
            "string",
            "null"
          ]
        },
        "conditions": {
          "description": "Conditions represent the current state",
          "items": {
            "additionalProperties": false,
            "description": "Condition contains details for one aspect of the current state of this API Resource.",
            "properties": {
              "lastTransitionTime": {
                "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.",
                "format": "date-time",
                "type": "string"
              },
              "message": {
                "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.",
                "maxLength": 32768,
                "type": "string"
              },
              "observedGeneration": {
                "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.",
                "format": "int64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "reason": {
                "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.",
                "maxLength": 1024,
                "minLength": 1,
                "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$",
                "type": "string"
              },
              "status": {
                "description": "status of the condition, one of True, False, Unknown.",
                "enum": [
                  "True",
                  "False",
                  "Unknown"
                ],
                "type": "string"
              },
              "type": {
                "description": "type of condition in CamelCase or in foo.example.com/CamelCase.",
                "maxLength": 316,
                "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$",
                "type": "string"
              }
            },
            "required": [
              "lastTransitionTime",
              "message",
              "reason",
              "status",
              "type"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ],
          "x-kubernetes-list-map-keys": [
            "type"
          ],
          "x-kubernetes-list-type": "map"
        },
        "createdAt": {
          "description": "CreatedAt is when the bucket was created in Garage",
          "format": "date-time",
          "type": [
            "string",
            "null"
          ]
        },
        "globalAlias": {
          "description": "GlobalAlias is the assigned global alias",
          "type": [
            "string",
            "null"
          ]
        },
        "incompleteUploadBytes": {
          "description": "IncompleteUploadBytes is the total bytes in incomplete multipart uploads",
          "format": "int64",
          "type": [
            "integer",
            "null"
          ]
        },
        "incompleteUploadParts": {
          "description": "IncompleteUploadParts is the count of parts in incomplete multipart uploads",
          "format": "int64",
          "type": [
            "integer",
            "null"
          ]
        },
        "incompleteUploads": {
          "description": "IncompleteUploads is the count of incomplete multipart uploads",
          "format": "int64",
          "type": [
            "integer",
            "null"
          ]
        },
        "keys": {
          "description": "Keys contains keys with access to this bucket",
          "items": {
            "additionalProperties": false,
            "description": "BucketKeyStatus shows key access status",
            "properties": {
              "keyId": {
                "description": "KeyID is the access key ID",
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "description": "Name is the key name",
                "type": [
                  "string",
                  "null"
                ]
              },
              "permissions": {
                "additionalProperties": false,
                "description": "Permissions granted to this key",
                "properties": {
                  "owner": {
                    "description": "Owner permission",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "read": {
                    "description": "Read permission",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "write": {
                    "description": "Write permission",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                },
                "type": [
                  "object",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "lifecycleRules": {
          "description": "LifecycleRules summarises lifecycle rules currently applied to the\nbucket in Garage. Spec is the source of truth for rule contents; this\nlist reports id and enabled state only.",
          "items": {
            "additionalProperties": false,
            "description": "LifecycleRuleStatus reports the id and enabled state of a lifecycle rule\ncurrently applied to the bucket.",
            "properties": {
              "id": {
                "description": "ID of the rule.",
                "type": "string"
              },
              "status": {
                "description": "Status is Enabled or Disabled.",
                "enum": [
                  "Enabled",
                  "Disabled"
                ],
                "type": "string"
              }
            },
            "required": [
              "id",
              "status"
            ],
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "localAliases": {
          "description": "LocalAliases tracks per-key local aliases for this bucket",
          "items": {
            "additionalProperties": false,
            "description": "LocalAliasStatus shows the status of a local alias for this bucket",
            "properties": {
              "alias": {
                "description": "Alias is the local alias name",
                "type": [
                  "string",
                  "null"
                ]
              },
              "keyId": {
                "description": "KeyID is the access key ID that owns this alias",
                "type": [
                  "string",
                  "null"
                ]
              },
              "keyName": {
                "description": "KeyName is the friendly name of the key",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "managedGlobalAlias": {
          "description": "ManagedGlobalAlias is the global alias reserved or successfully managed from\nspec.globalAlias (or the bucket name when spec.globalAlias is empty).\nIt is separate from GlobalAlias, which reports observed Garage state, so\naliases created outside the operator are never removed accidentally.",
          "type": [
            "string",
            "null"
          ]
        },
        "managedKeyGrants": {
          "description": "ManagedKeyGrants lists access key IDs with reserved or active operator\nownership from this bucket's spec.keyPermissions. IDs are recorded before\nthe first remote mutation and removed only after exact convergence, allowing\ncrash-safe revocation when a declaration is dropped without disturbing\ngrants managed through GarageKey or by hand.",
          "items": {
            "type": "string"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "managedLocalAliases": {
          "description": "ManagedLocalAliases lists the per-key aliases reserved or successfully\nmanaged from spec.localAliases. IDs are recorded before the first remote\nadd so an interrupted add can still be removed safely.",
          "items": {
            "additionalProperties": false,
            "description": "LocalAliasStatus shows the status of a local alias for this bucket",
            "properties": {
              "alias": {
                "description": "Alias is the local alias name",
                "type": [
                  "string",
                  "null"
                ]
              },
              "keyId": {
                "description": "KeyID is the access key ID that owns this alias",
                "type": [
                  "string",
                  "null"
                ]
              },
              "keyName": {
                "description": "KeyName is the friendly name of the key",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "observedGeneration": {
          "description": "ObservedGeneration is the last observed generation",
          "format": "int64",
          "type": [
            "integer",
            "null"
          ]
        },
        "pendingGlobalAlias": {
          "description": "PendingGlobalAlias reserves a replacement before its first remote add.\nManagedGlobalAlias retains the old alias until the replacement succeeds,\nso a failed rename never leaves the bucket without its prior alias.",
          "type": [
            "string",
            "null"
          ]
        },
        "phase": {
          "description": "Phase represents the current phase",
          "enum": [
            "Pending",
            "Creating",
            "Ready",
            "Deleting",
            "Failed",
            "Unknown"
          ],
          "type": [
            "string",
            "null"
          ]
        },
        "quotaUsage": {
          "additionalProperties": false,
          "description": "QuotaUsage shows current quota consumption",
          "properties": {
            "objectCount": {
              "description": "ObjectCount is the current object count",
              "format": "int64",
              "type": [
                "integer",
                "null"
              ]
            },
            "objectLimit": {
              "description": "ObjectLimit is the configured object limit (0 = unlimited)",
              "format": "int64",
              "type": [
                "integer",
                "null"
              ]
            },
            "objectPercent": {
              "description": "ObjectPercent is the percentage of object quota used",
              "format": "int32",
              "type": [
                "integer",
                "null"
              ]
            },
            "sizeBytes": {
              "description": "SizeBytes is the current size in bytes",
              "format": "int64",
              "type": [
                "integer",
                "null"
              ]
            },
            "sizeLimit": {
              "description": "SizeLimit is the configured size limit in bytes (0 = unlimited)",
              "format": "int64",
              "type": [
                "integer",
                "null"
              ]
            },
            "sizePercent": {
              "description": "SizePercent is the percentage of size quota used",
              "format": "int32",
              "type": [
                "integer",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "size": {
          "description": "Size is the current bucket size",
          "type": [
            "string",
            "null"
          ]
        },
        "websiteConfig": {
          "additionalProperties": false,
          "description": "WebsiteConfig shows the current website configuration details",
          "properties": {
            "errorDocument": {
              "description": "ErrorDocument is the configured error document",
              "type": [
                "string",
                "null"
              ]
            },
            "indexDocument": {
              "description": "IndexDocument is the configured index document",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "websiteEnabled": {
          "description": "WebsiteEnabled indicates if website hosting is currently enabled",
          "type": [
            "boolean",
            "null"
          ]
        },
        "websiteExposure": {
          "additionalProperties": false,
          "description": "WebsiteExposure reports the operator-generated HTTP routing resource\n(Ingress or HTTPRoute) when spec.websiteExposure is set.",
          "properties": {
            "hostnames": {
              "description": "Hostnames are the hostnames the generated resource routes on.",
              "items": {
                "type": "string"
              },
              "type": [
                "array",
                "null"
              ]
            },
            "name": {
              "description": "Name is the name of the generated resource, in the bucket's namespace.",
              "type": [
                "string",
                "null"
              ]
            },
            "parents": {
              "description": "Parents mirrors the route's status.parents for an HTTPRoute: the\nper-parent Accepted/ResolvedRefs/Ready conditions the Gateway\ncontrollers publish. Empty for an Ingress, which has no per-parent\nreadiness model.",
              "items": {
                "additionalProperties": false,
                "description": "WebsiteParentStatus is the per-parent readiness of a generated HTTPRoute,\nmirroring gateway.networking.k8s.io/v1 RouteParentStatus conditions.",
                "properties": {
                  "accepted": {
                    "description": "Accepted is true when the parent accepted the route\n(status.parents[].conditions[Accepted]=True).",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "message": {
                    "description": "Message carries the parent's condition message when not ready.",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "parent": {
                    "description": "Parent is the parent Gateway (or other parent) as namespace/name.",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "ready": {
                    "description": "Ready is true when the parent reports the route Ready\n(status.parents[].conditions[Ready]=True).",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "resolvedRefs": {
                    "description": "ResolvedRefs is true when the route's backend references resolved on\nthat parent (status.parents[].conditions[ResolvedRefs]=True).",
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                },
                "type": "object"
              },
              "type": [
                "array",
                "null"
              ]
            },
            "type": {
              "description": "Type is the kind of routing resource the operator manages for this\nbucket: Ingress or HTTPRoute.",
              "enum": [
                "Ingress",
                "HTTPRoute"
              ],
              "type": [
                "string",
                "null"
              ]
            }
          },
          "type": [
            "object",
            "null"
          ]
        },
        "websiteUrl": {
          "description": "WebsiteURL is the computed website URL (if website hosting is enabled)",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "type": [
        "object",
        "null"
      ]
    }
  },
  "required": [
    "spec"
  ],
  "type": "object"
}