#!/usr/bin/env node 'use strict'; const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const path = require('node:path'); const yaml = require('js-yaml'); const { canonicalPluginPackagePromptOutputKeyringManifest, parsePluginPackagePromptOutputKeyringManifest, pluginPackagePromptOutputKeyringCatalogDigest, } = require('../packages/ql3-ai/dist/prompt-output/key-management/pluginPackagePromptOutputKeyringManifest.js'); const { K3sDockerLiveFixture, run, waitFor, } = require('./lib/ql3-k3s-docker-live.cjs'); const { imageDigest, imageTag, reviewedOperatorManifest, } = require('./ql3-cloudnativepg-live-contract.cjs'); const { FIXTURE, LIMITATIONS, validatePromptOutputKeyRetirementKubernetesLiveReport, } = require('./ql3-prompt-output-key-retirement-kubernetes-live-audit.cjs'); const ROOT = path.resolve(__dirname, '..'); const NAMESPACE = 'qinglong3-system'; const POSTGRES_CLUSTER = 'ql3-postgres'; const KEYRING_SECRET = 'ql3-prompt-output-keyring'; const KEYRING_DATA_KEY = 'keyring.json'; const RETIREMENT_JOB = 'ql3-prompt-output-key-retirement'; const RETIREMENT_SERVICE_ACCOUNT = RETIREMENT_JOB; const COMMAND_CONFIG_MAP = 'ql3-prompt-output-key-retirement-command'; const DENY_CANARY = 'ql3-retirement-deny-canary'; const DENY_CANARY_PORT = 9443; const ZERO_DIGEST = `sha256:${'0'.repeat(64)}`; const ADMIN_IMAGE_BASE = 'ql3-key-retirement-live-admin'; const CONTROL_IMAGE_BASE = 'ql3-key-retirement-live-migration'; const PRESERVE_FAILURE_ENV = 'QL3_PROMPT_OUTPUT_KEY_RETIREMENT_KUBERNETES_LIVE_PRESERVE_FAILURE'; const LOCK = JSON.parse( fs.readFileSync( path.join( ROOT, 'deploy/kubernetes/ql3-cluster/operators/cloudnative-pg/operator-lock.json', ), 'utf8', ), ); const OPERATOR_IMAGE = LOCK.operator.image; const POSTGRES_IMAGE = LOCK.operand.image; const OPERATOR_VERSION = LOCK.operator.version; const ROLE_NAMES = Object.freeze([ 'ql3_migration', 'ql3_ai_maintenance', 'ql3_ai_credential_manager', 'ql3_ai_credential_tester', 'ql3_runtime', 'ql3_admin', 'ql3_package_manager', 'ql3_package_executor', 'ql3_automation_manager', 'ql3_approval_manager', 'ql3_worker_credential_manager', 'ql3_worker_credential_executor', 'ql3_worker_ingress', ]); function randomSecret() { return crypto.randomBytes(32).toString('base64url'); } function roleSecretName(role) { return `ql3-postgres-${role.replace(/^ql3_/, '').replaceAll('_', '-')}-auth`; } function podReady(pod) { return Boolean( pod.metadata.deletionTimestamp === undefined && pod.status.conditions?.some( (condition) => condition.type === 'Ready' && condition.status === 'True', ), ); } function localManifest(rendered, imageName, localImage, expectedMatches = 1) { const placeholder = `${imageName}@${ZERO_DIGEST}`; const matches = rendered.split(placeholder).length - 1; assert.equal( matches, expectedMatches, `expected ${String(expectedMatches)} ${imageName} image placeholders`, ); return rendered.replaceAll(placeholder, localImage); } function manifestDocuments(rendered) { const documents = []; yaml.loadAll(rendered, (document) => { if (document) documents.push(document); }); return documents; } function findNamed(values, name) { const value = values.find((entry) => entry.name === name); assert.ok(value, `${name} is absent`); return value; } function applySecret(fixture, name, type, stringData) { fixture.apply({ apiVersion: 'v1', kind: 'Secret', metadata: { name, namespace: NAMESPACE }, type, stringData, }); } function postgresPods(fixture) { return fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'pods', '-l', `cnpg.io/cluster=${POSTGRES_CLUSTER}`, ]).items; } function currentPrimaryPod(fixture) { const name = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'cluster', POSTGRES_CLUSTER, ]).status?.currentPrimary; assert.ok(name, 'CloudNativePG current primary is unavailable'); return fixture.kubectlJson(['-n', NAMESPACE, 'get', 'pod', name]); } function psql(fixture, podName, sql) { return fixture.kubectl( [ '-n', NAMESPACE, 'exec', podName, '-c', 'postgres', '--', 'psql', '-U', 'postgres', '-d', 'qinglong', '--no-psqlrc', '--tuples-only', '--no-align', '--set', 'ON_ERROR_STOP=1', '--command', sql, ], { capture: true, quiet: true }, ).stdout; } async function waitForJob(fixture, name, timeoutMs = 120_000) { return ( await waitFor(`${name} terminal`, timeoutMs, () => { const job = fixture.kubectlJson(['-n', NAMESPACE, 'get', 'job', name]); const complete = job.status.conditions?.some( (condition) => condition.type === 'Complete' && condition.status === 'True', ); const failed = job.status.conditions?.some( (condition) => condition.type === 'Failed' && condition.status === 'True', ); return complete || failed ? { ready: true, value: { job, complete, failed } } : { ready: false, fact: JSON.stringify(job.status ?? {}) }; }) ).value; } async function terminalJobPod(fixture, name, timeoutMs = 120_000) { return ( await waitFor(`${name} terminal Pod`, timeoutMs, () => { const pods = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'pods', '-l', `batch.kubernetes.io/job-name=${name}`, ]).items; const main = pods[0]?.status.containerStatuses?.[0]?.state?.terminated; const init = pods[0]?.status.initContainerStatuses?.[0]?.state?.terminated; return pods.length === 1 && main && init ? { ready: true, value: pods[0] } : { ready: false, fact: `${pods.length} Pods observed` }; }) ).value; } async function terminalJobSnapshot(fixture, name, timeoutMs = 120_000) { const [pod, terminal] = await Promise.all([ terminalJobPod(fixture, name, timeoutMs), waitForJob(fixture, name, timeoutMs), ]); return Object.freeze({ pod, terminal }); } function aiFeatureMigrationJob(template, adminImage) { const job = structuredClone(template); job.metadata.name = 'ql3-ai-feature-migration'; delete job.metadata.resourceVersion; delete job.metadata.uid; job.spec.template.metadata.labels['app.kubernetes.io/name'] = 'ql3-ai-feature-migration'; job.spec.template.metadata.labels['app.kubernetes.io/component'] = 'ai-feature-migration'; const container = job.spec.template.spec.containers[0]; container.name = 'ai-feature-migration'; container.image = adminImage; container.command = [ 'node', '/opt/qinglong/node_modules/@qinglong/cluster-admin/dist/modelInvocationMigrationCli.js', ]; return job; } function configureRetirementResources(rendered, options) { const documents = manifestDocuments( localManifest( rendered, 'registry.example.com/qinglong/qinglong3-cluster-admin', options.adminImage, 2, ), ); const job = documents.find((document) => document.kind === 'Job'); const networkPolicy = documents.find( (document) => document.kind === 'NetworkPolicy', ); assert.ok(job && networkPolicy, 'retirement Job or NetworkPolicy is absent'); const init = findNamed( job.spec.template.spec.initContainers, 'network-policy-ready', ); findNamed(init.env, 'QL3_NETWORK_POLICY_DENY_CANARY_HOST').value = options.denyCanaryHost; findNamed(init.env, 'QL3_NETWORK_POLICY_DENY_CANARY_PORT').value = String( options.denyCanaryPort, ); networkPolicy.spec.egress.push( { to: [{ ipBlock: { cidr: `${options.kubernetesServiceIp}/32` } }], ports: [{ protocol: 'TCP', port: 443 }], }, { to: [{ ipBlock: { cidr: `${options.kubernetesServerIp}/32` } }], ports: [{ protocol: 'TCP', port: 6443 }], }, ); return documents; } function denyCanaryResources(adminImage) { const labels = { 'app.kubernetes.io/name': DENY_CANARY, 'app.kubernetes.io/component': 'live-deny-canary', }; return [ { apiVersion: 'apps/v1', kind: 'Deployment', metadata: { name: DENY_CANARY, namespace: NAMESPACE }, spec: { replicas: 1, selector: { matchLabels: labels }, template: { metadata: { labels }, spec: { automountServiceAccountToken: false, enableServiceLinks: false, containers: [ { name: 'canary', image: adminImage, imagePullPolicy: 'Never', command: [ 'node', '-e', "require('node:net').createServer((socket)=>socket.end()).listen(9443,'0.0.0.0')", ], ports: [{ name: 'canary', containerPort: DENY_CANARY_PORT }], securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: ['ALL'] }, }, resources: { requests: { cpu: '5m', memory: '16Mi' }, limits: { cpu: '100m', memory: '64Mi' }, }, }, ], securityContext: { runAsNonRoot: true, runAsUser: 10001, runAsGroup: 10001, seccompProfile: { type: 'RuntimeDefault' }, }, }, }, }, }, { apiVersion: 'v1', kind: 'Service', metadata: { name: DENY_CANARY, namespace: NAMESPACE }, spec: { selector: labels, ports: [ { name: 'canary', protocol: 'TCP', port: DENY_CANARY_PORT, targetPort: 'canary', }, ], }, }, ]; } function canaryControlJob(adminImage) { return { apiVersion: 'batch/v1', kind: 'Job', metadata: { name: 'ql3-retirement-canary-control', namespace: NAMESPACE }, spec: { backoffLimit: 0, activeDeadlineSeconds: 30, template: { spec: { automountServiceAccountToken: false, restartPolicy: 'Never', containers: [ { name: 'probe', image: adminImage, imagePullPolicy: 'Never', command: [ 'node', '-e', "const net=require('node:net');const fs=require('node:fs');const connect=()=>new Promise((resolve)=>{let settled=false;const socket=net.createConnection({host:'ql3-retirement-deny-canary.qinglong3-system.svc',port:9443});const finish=(value)=>{if(settled)return;settled=true;socket.destroy();resolve(value)};socket.setTimeout(500);socket.once('connect',()=>finish(true));socket.once('timeout',()=>finish(false));socket.once('error',()=>finish(false))});const sleep=(milliseconds)=>new Promise((resolve)=>setTimeout(resolve,milliseconds));(async()=>{const deadline=Date.now()+30000;while(Date.now(){fs.writeFileSync('/dev/termination-log','failed');process.exitCode=1})", ], terminationMessagePolicy: 'File', securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: ['ALL'] }, }, resources: { requests: { cpu: '5m', memory: '16Mi' }, limits: { cpu: '100m', memory: '64Mi' }, }, }, ], securityContext: { runAsNonRoot: true, runAsUser: 10001, runAsGroup: 10001, seccompProfile: { type: 'RuntimeDefault' }, }, }, }, }, }; } function canI(fixture, verb, resource, resourceName) { const args = [ 'auth', 'can-i', verb, resourceName ? `${resource}/${resourceName}` : resource, '--as', `system:serviceaccount:${NAMESPACE}:${RETIREMENT_SERVICE_ACCOUNT}`, '-n', NAMESPACE, ]; const result = fixture.kubectl(args, { allowFailure: true, capture: true, quiet: true, }); assert.deepEqual( [result.status, result.stdout], result.stdout === 'yes' ? [0, 'yes'] : [1, 'no'], `unexpected kubectl auth can-i response: status=${String( result.status, )} stderr=${result.stderr}`, ); return result.stdout; } function assertExactRbac(fixture) { for (const [verb, resource, name] of [ ['get', 'secrets', KEYRING_SECRET], ['update', 'secrets', KEYRING_SECRET], ]) { assert.equal(canI(fixture, verb, resource, name), 'yes'); } for (const [verb, resource, name] of [ ['list', 'secrets'], ['watch', 'secrets'], ['create', 'secrets'], ['delete', 'secrets', KEYRING_SECRET], ['patch', 'secrets', KEYRING_SECRET], ['get', 'secrets', `${KEYRING_SECRET}-other`], ['get', 'configmaps'], ['get', 'pods'], ]) { assert.equal(canI(fixture, verb, resource, name), 'no'); } return true; } async function runRetirementJob(fixture, template) { fixture.create(template); const { pod, terminal } = await terminalJobSnapshot( fixture, RETIREMENT_JOB, 5 * 60_000, ); const init = findNamed( pod.status.initContainerStatuses, 'network-policy-ready', ).state.terminated; const main = findNamed(pod.status.containerStatuses, 'retirement').state .terminated; assert.equal(terminal.complete, true, main.message); assert.equal(terminal.failed, false, main.message); assert.equal(init.exitCode, 0, init.message); assert.equal(main.exitCode, 0, main.message); assert.deepEqual(JSON.parse(init.message), { schemaVersion: 1, ready: true, code: 'POLICY_READY', }); const initSpec = findNamed(pod.spec.initContainers, 'network-policy-ready'); assert.equal(initSpec.volumeMounts, undefined); assert.equal( pod.spec.automountServiceAccountToken, false, 'Pod must not receive an automatic token', ); const projected = findNamed( pod.spec.volumes, 'kubernetes-api-token', ).projected; assert.equal(projected.sources[0].serviceAccountToken.expirationSeconds, 600); return Object.freeze({ tokenAbsentFromInit: initSpec.volumeMounts === undefined, denyCanaryEgressDenied: JSON.parse(init.message).code === 'POLICY_READY', }); } async function main() { if (process.env.QL3_PROMPT_OUTPUT_KEY_RETIREMENT_KUBERNETES_LIVE !== '1') { throw new Error( 'Refusing to mutate Docker/Kubernetes without ' + 'QL3_PROMPT_OUTPUT_KEY_RETIREMENT_KUBERNETES_LIVE=1', ); } const operatorManifestFile = process.env.QL3_CNPG_OPERATOR_MANIFEST_FILE; if (!operatorManifestFile) { throw new Error('QL3_CNPG_OPERATOR_MANIFEST_FILE is required'); } const reviewedManifest = reviewedOperatorManifest(operatorManifestFile); const fixture = new K3sDockerLiveFixture({ prefix: 'ql3-key-retire-live' }); const suffix = `${process.pid.toString(36)}-${crypto .randomBytes(3) .toString('hex')}`; const adminImage = `${ADMIN_IMAGE_BASE}:${suffix}`; const controlImage = `${CONTROL_IMAGE_BASE}:${suffix}`; let adminImageBuilt = false; let controlImageBuilt = false; let completed = false; try { const nodes = await fixture.start(); const architecture = fixture.inspectImage(fixture.k3sImage).Architecture; assert.ok(['amd64', 'arm64'].includes(architecture)); for (const [index, reviewedImage] of [ OPERATOR_IMAGE, POSTGRES_IMAGE, ].entries()) { if ( fixture.dockerRun(['image', 'inspect', reviewedImage], { capture: true, quiet: true, allowFailure: true, }).status !== 0 ) { run(fixture.docker, ['pull', reviewedImage]); } const inspected = fixture.inspectImage(reviewedImage); assert.ok( inspected.RepoDigests?.some((entry) => entry.endsWith(`@${imageDigest(reviewedImage)}`), ), ); const preloadTag = imageTag(reviewedImage); run(fixture.docker, ['tag', reviewedImage, preloadTag]); fixture.loadImage(preloadTag, `reviewed-${String(index)}.tar`); } fixture.kubectl(['apply', '--server-side', '-f', reviewedManifest]); fixture.kubectl([ '-n', 'cnpg-system', 'set', 'image', 'deployment/cnpg-controller-manager', `manager=${imageTag(OPERATOR_IMAGE)}`, ]); fixture.kubectl([ 'wait', '--for=condition=Established', 'crd/clusters.postgresql.cnpg.io', 'crd/databaseroles.postgresql.cnpg.io', 'crd/databases.postgresql.cnpg.io', '--timeout=5m', ]); fixture.kubectl([ '-n', 'cnpg-system', 'rollout', 'status', 'deployment/cnpg-controller-manager', '--timeout=5m', ]); fixture.kubectl([ 'apply', '-f', 'deploy/kubernetes/ql3-cluster/base/namespace.yaml', ]); fixture.kubectl([ '-n', NAMESPACE, 'apply', '-f', 'deploy/kubernetes/ql3-cluster/base/service-account.yaml', ]); for (const role of ROLE_NAMES) { applySecret(fixture, roleSecretName(role), 'kubernetes.io/basic-auth', { username: role, password: randomSecret(), }); } fixture.kubectl([ 'apply', '-k', 'deploy/kubernetes/ql3-cluster/operators/cloudnative-pg', ]); fixture.kubectl([ '-n', NAMESPACE, 'wait', '--for=condition=Ready', `cluster/${POSTGRES_CLUSTER}`, '--timeout=20m', ]); const readyDatabase = ( await waitFor('three CloudNativePG instances', 10 * 60_000, () => { const ready = postgresPods(fixture).filter(podReady); const distinctNodes = new Set(ready.map((pod) => pod.spec.nodeName)); return ready.length === 3 && distinctNodes.size === 3 ? { ready: true, value: ready } : { ready: false, fact: `${ready.length} Ready across ${distinctNodes.size} nodes`, }; }) ).value; run(fixture.docker, [ 'build', '--file', 'deploy/containers/ql3-cluster-admin/Dockerfile', '--tag', adminImage, '--build-arg', `SOURCE_REVISION=${process.env.GITHUB_SHA || 'live-contract'}`, '.', ]); adminImageBuilt = true; run(fixture.docker, [ 'build', '--file', 'deploy/containers/ql3-cluster-control/Dockerfile', '--tag', controlImage, '--build-arg', `SOURCE_REVISION=${process.env.GITHUB_SHA || 'live-contract'}`, '.', ]); controlImageBuilt = true; fixture.loadImage(adminImage, 'key-retirement-admin.tar'); fixture.loadImage(controlImage, 'key-retirement-migration.tar'); const migrationManifest = localManifest( fixture.kubectl( [ 'kustomize', 'deploy/kubernetes/ql3-cluster/operations/cloudnative-pg', ], { capture: true, quiet: true }, ).stdout, 'registry.example.com/qinglong/qinglong3-cluster-control', controlImage, ); const migrationJobTemplate = manifestDocuments(migrationManifest).find( (document) => document.kind === 'Job', ); assert.ok(migrationJobTemplate, 'migration Job template is unavailable'); fixture.kubectl(['create', '-f', '-'], { input: migrationManifest }); assert.equal( (await waitForJob(fixture, 'ql3-cluster-migration', 10 * 60_000)) .complete, true, ); fixture.create(aiFeatureMigrationJob(migrationJobTemplate, adminImage)); assert.equal( (await waitForJob(fixture, 'ql3-ai-feature-migration', 10 * 60_000)) .complete, true, ); const primary = currentPrimaryPod(fixture); const databaseFacts = JSON.parse( psql( fixture, primary.metadata.name, `SELECT json_build_object( 'postgresVersionNumber', current_setting('server_version_num')::integer, 'migrationCount', (SELECT count(*)::integer FROM "ql3"."schema_migrations"), 'aiMigrationCount', (SELECT count(*)::integer FROM "ql3_ai"."ai_schema_migrations") )::text;`, ), ); assert.equal(databaseFacts.migrationCount, 52); assert.equal(databaseFacts.aiMigrationCount, 16); for (const resource of denyCanaryResources(adminImage)) { fixture.apply(resource); } fixture.kubectl([ '-n', NAMESPACE, 'rollout', 'status', `deployment/${DENY_CANARY}`, '--timeout=3m', ]); fixture.create(canaryControlJob(adminImage)); const controlProbe = await waitForJob( fixture, 'ql3-retirement-canary-control', 60_000, ); assert.equal(controlProbe.complete, true); const controlPod = await waitFor('canary control Pod', 60_000, () => { const pods = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'pods', '-l', 'batch.kubernetes.io/job-name=ql3-retirement-canary-control', ]).items; const state = pods[0]?.status.containerStatuses?.[0]?.state?.terminated; return state ? { ready: true, value: pods[0] } : { ready: false, fact: `${pods.length} Pods observed` }; }); assert.equal( controlPod.value.status.containerStatuses[0].state.terminated.message, 'connected', ); const inactiveKeyId = 'cluster-key-old'; const activeKeyId = 'cluster-key-active'; const initialManifest = Object.freeze({ schema: 'qinglong/plugin-package-prompt-output-file-keyring@v1', generation: 1, activeKeyId, keys: Object.freeze({ [inactiveKeyId]: randomSecret(), [activeKeyId]: randomSecret(), }), retirements: Object.freeze({}), }); const initialBytes = canonicalPluginPackagePromptOutputKeyringManifest(initialManifest); fixture.create({ apiVersion: 'v1', kind: 'Secret', metadata: { name: KEYRING_SECRET, namespace: NAMESPACE, labels: { 'app.kubernetes.io/managed-by': 'qinglong3', 'qinglong.io/prompt-output-keyring': 'v1', }, annotations: { 'qinglong.io/prompt-output-keyring-generation': '1', 'qinglong.io/prompt-output-keyring-catalog-digest': pluginPackagePromptOutputKeyringCatalogDigest(initialManifest), }, }, type: 'Opaque', immutable: false, data: { [KEYRING_DATA_KEY]: initialBytes.toString('base64') }, }); initialBytes.fill(0); const initialSecret = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'secret', KEYRING_SECRET, ]); const initialResourceVersion = initialSecret.metadata.resourceVersion; const secretUid = initialSecret.metadata.uid; assert.match(secretUid, /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/); fixture.create({ apiVersion: 'v1', kind: 'ConfigMap', metadata: { name: COMMAND_CONFIG_MAP, namespace: NAMESPACE }, immutable: true, data: { 'command.json': JSON.stringify({ schemaVersion: 1, operation: 'cluster.prompt-output-key.retire', kubernetes: { namespace: NAMESPACE, secretName: KEYRING_SECRET, expectedSecretUid: secretUid, dataKey: KEYRING_DATA_KEY, }, request: { keyId: inactiveKeyId, retirementId: 'retirement-live-1', requestId: 'request-live-1', mutationId: 'mutation-live-1', }, }), }, }); const kubernetesServiceIp = fixture.kubectlJson([ '-n', 'default', 'get', 'service', 'kubernetes', ]).spec.clusterIP; const retirementResources = configureRetirementResources( fixture.kubectl( [ 'kustomize', 'deploy/kubernetes/ql3-cluster/operations/prompt-output-key-retirement/cloudnative-pg', ], { capture: true, quiet: true }, ).stdout, { adminImage, denyCanaryHost: `${DENY_CANARY}.${NAMESPACE}.svc`, denyCanaryPort: DENY_CANARY_PORT, kubernetesServiceIp, kubernetesServerIp: fixture.containerAddress(fixture.server), }, ); const retirementTemplate = retirementResources.find( (resource) => resource.kind === 'Job', ); assert.ok(retirementTemplate); for (const resource of retirementResources.filter( (candidate) => candidate.kind !== 'Job', )) { fixture.apply(resource); } const rbacExact = assertExactRbac(fixture); const first = await runRetirementJob(fixture, retirementTemplate); const afterFirst = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'secret', KEYRING_SECRET, ]); assert.equal(afterFirst.metadata.uid, secretUid); assert.notEqual( afterFirst.metadata.resourceVersion, initialResourceVersion, ); let afterFirstBytes = Buffer.from( afterFirst.data[KEYRING_DATA_KEY], 'base64', ); const afterFirstManifest = parsePluginPackagePromptOutputKeyringManifest(afterFirstBytes); afterFirstBytes.fill(0); afterFirstBytes = undefined; assert.equal(afterFirstManifest.generation, 2); assert.equal(afterFirstManifest.keys[inactiveKeyId], undefined); assert.equal(typeof afterFirstManifest.keys[activeKeyId], 'string'); assert.equal(Object.keys(afterFirstManifest.retirements).length, 1); fixture.kubectl( ['-n', NAMESPACE, 'delete', `job/${RETIREMENT_JOB}`, '--wait=true'], { capture: true, quiet: true }, ); const replay = await runRetirementJob(fixture, retirementTemplate); const afterReplay = fixture.kubectlJson([ '-n', NAMESPACE, 'get', 'secret', KEYRING_SECRET, ]); assert.equal( afterReplay.metadata.resourceVersion, afterFirst.metadata.resourceVersion, ); const ledgerFacts = JSON.parse( psql( fixture, currentPrimaryPod(fixture).metadata.name, `SELECT json_build_object( 'preparationCount', (SELECT count(*)::integer FROM "ql3_ai"."model_invocation_prompt_output_key_retirement_preparations" WHERE key_id = '${inactiveKeyId}'), 'completionCount', (SELECT count(*)::integer FROM "ql3_ai"."model_invocation_prompt_output_key_retirement_completions" WHERE key_id = '${inactiveKeyId}') )::text;`, ), ); assert.deepEqual(ledgerFacts, { preparationCount: 1, completionCount: 1, }); const kubernetesImage = fixture.inspectImage(fixture.k3sImage); const report = Object.freeze({ fixture: FIXTURE, observedAt: new Date().toISOString(), platform: Object.freeze({ distribution: 'k3s', kubernetesVersion: nodes[0].status.nodeInfo.kubeletVersion, architecture, kubernetesImageId: kubernetesImage.Id, cniName: 'flannel', controlPlaneNodes: 1, workerNodes: 2, }), database: Object.freeze({ operator: 'cloudnative-pg', operatorVersion: OPERATOR_VERSION, postgresVersionNumber: databaseFacts.postgresVersionNumber, instances: 3, readyInstances: readyDatabase.length, migrationCount: databaseFacts.migrationCount, aiMigrationCount: databaseFacts.aiMigrationCount, role: 'ql3_ai_maintenance', tlsVerified: true, }), operation: Object.freeze({ jobsRun: 2, status: 'completed', replayStatus: 'existing', generationAfter: afterFirstManifest.generation, retirementCount: Object.keys(afterFirstManifest.retirements).length, preparationCount: ledgerFacts.preparationCount, completionCount: ledgerFacts.completionCount, projectedTokenExpirationSeconds: 600, activeKeyRetained: typeof afterFirstManifest.keys[activeKeyId] === 'string', inactiveKeyRemoved: afterFirstManifest.keys[inactiveKeyId] === undefined, resourceVersionChangedOnce: initialResourceVersion !== afterFirst.metadata.resourceVersion && afterFirst.metadata.resourceVersion === afterReplay.metadata.resourceVersion, secretIdentityBound: afterReplay.metadata.uid === secretUid, tokenAbsentFromInit: first.tokenAbsentFromInit && replay.tokenAbsentFromInit, rbacExact, denyCanaryControlReachable: true, denyCanaryEgressDenied: first.denyCanaryEgressDenied && replay.denyCanaryEgressDenied, }), gates: Object.freeze({ contentFreeEvidence: true, durableReplay: true, exactRbac: true, passed: true, realCloudNativePg: true, realKubernetesApi: true, resourceVersionCas: true, samePodNetworkBarrier: true, shortLivedToken: true, }), limitations: LIMITATIONS, }); const validation = validatePromptOutputKeyRetirementKubernetesLiveReport(report); assert.deepEqual(validation.findings, []); process.stdout.write(`${JSON.stringify(report)}\n`); completed = true; } finally { if (!completed && process.env[PRESERVE_FAILURE_ENV] === '1') { process.stderr.write( `Preserved failed fixture containers: ${fixture.nodes.join(', ')}\n`, ); } else { await fixture.cleanup(); if (adminImageBuilt) { run(fixture.docker, ['image', 'rm', '-f', adminImage], { capture: true, quiet: true, allowFailure: true, }); } if (controlImageBuilt) { run(fixture.docker, ['image', 'rm', '-f', controlImage], { capture: true, quiet: true, allowFailure: true, }); } } } } if (require.main === module) { main().catch((error) => { process.stderr.write( `QL3 Prompt output key retirement Kubernetes live contract failed: ${ error instanceof Error ? error.stack || error.message : String(error) }\n`, ); process.exitCode = 1; }); } module.exports = { aiFeatureMigrationJob, canaryControlJob, configureRetirementResources, denyCanaryResources, terminalJobSnapshot, };