fix: 切换 HWLAB 投影实时权威
This commit is contained in:
@@ -1482,6 +1482,7 @@ export function nodeRuntimeCodeAgentRuntimeStatus(spec: HwlabRuntimeLaneSpec, na
|
||||
const apiKey = secretKeyStatus(spec, runtime.apiKeySecretName, runtime.apiKeySecretKey);
|
||||
const cloudApiEnv = nodeRuntimeCodeAgentCloudApiEnvStatus(spec, runtime);
|
||||
const cloudWebEnv = nodeRuntimeCodeAgentCloudWebEnvStatus(spec, runtime);
|
||||
const kafkaAuthority = nodeRuntimeKafkaAuthorityStatus(spec, runtime);
|
||||
const ready = runnerNamespace.exitCode === 0
|
||||
&& secretNamespace.exitCode === 0
|
||||
&& managerNamespace.exitCode === 0
|
||||
@@ -1538,12 +1539,70 @@ export function nodeRuntimeCodeAgentRuntimeStatus(spec: HwlabRuntimeLaneSpec, na
|
||||
apiKey,
|
||||
cloudApiEnv,
|
||||
cloudWebEnv,
|
||||
kafkaAuthority,
|
||||
repoUrlFrom: runtime.repoUrlFrom,
|
||||
providerIdFrom: runtime.providerIdFrom,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function nodeRuntimeKafkaAuthorityStatus(spec: HwlabRuntimeLaneSpec, runtime: NonNullable<HwlabRuntimeLaneSpec["codeAgentRuntime"]>): Record<string, unknown> {
|
||||
const kafka = runtime.kafkaEventBridge;
|
||||
if (kafka === undefined || kafka.enabled === false) return { required: false, authority: kafka?.authority ?? null, valuesPrinted: false };
|
||||
const healthUrl = `${spec.publicApiUrl.replace(/\/+$/u, "")}/health/ready`;
|
||||
const result = runCommand(["curl", "-k", "-sS", "--connect-timeout", "5", "--max-time", "12", healthUrl], repoRoot, { timeoutMs: 15_000 });
|
||||
let health: Record<string, unknown> = {};
|
||||
try {
|
||||
health = result.exitCode === 0 ? record(JSON.parse(result.stdout)) : {};
|
||||
} catch {
|
||||
health = {};
|
||||
}
|
||||
const projector = record(health.kafkaProjector);
|
||||
const components = Array.isArray(projector.components) ? projector.components.map(record) : [];
|
||||
const observations = [projector, ...components];
|
||||
const consumerLag = observations.map((item) => record(item.consumerLag).total).find((value) => typeof value === "number") ?? null;
|
||||
const backlogCount = observations.map((item) => item.backlogCount).find((value) => typeof value === "number") ?? null;
|
||||
const retryCount = observations.map((item) => item.retryCount).find((value) => typeof value === "number") ?? null;
|
||||
const failedInboxCount = observations.map((item) => item.failedInboxCount).find((value) => typeof value === "number") ?? null;
|
||||
const dlqCount = observations.map((item) => item.dlqCount).find((value) => typeof value === "number") ?? null;
|
||||
const thresholds = kafka.healthThresholds;
|
||||
const warningReasons = [
|
||||
typeof consumerLag === "number" && consumerLag > thresholds.consumerLagWarning ? "consumer-lag" : null,
|
||||
typeof backlogCount === "number" && backlogCount > thresholds.outboxBacklogWarning ? "outbox-backlog" : null,
|
||||
typeof retryCount === "number" && retryCount > thresholds.retryCountWarning ? "outbox-retry" : null,
|
||||
typeof failedInboxCount === "number" && failedInboxCount > thresholds.failedInboxWarning ? "failed-inbox" : null,
|
||||
typeof dlqCount === "number" && dlqCount > thresholds.dlqWarning ? "dlq" : null,
|
||||
].filter((value): value is string => value !== null);
|
||||
return {
|
||||
required: true,
|
||||
authority: kafka.authority,
|
||||
fixedGroups: {
|
||||
directPublish: kafka.directPublishConsumerGroupId,
|
||||
transactionalProjector: kafka.transactionalProjectorConsumerGroupId,
|
||||
liveSse: kafka.hwlabEventConsumerGroupId,
|
||||
},
|
||||
capabilities: kafka.features,
|
||||
liveReplay: {
|
||||
live: kafka.features.projectionRealtime || kafka.features.liveKafkaSse,
|
||||
replay: kafka.features.projectionRealtime || kafka.features.kafkaRefreshReplay,
|
||||
cursor: kafka.features.projectionRealtime ? "projection-outbox-seq" : kafka.features.kafkaRefreshReplay ? "kafka-refresh-replay" : "none",
|
||||
},
|
||||
healthObserved: Object.keys(projector).length > 0,
|
||||
healthStatus: projector.status ?? null,
|
||||
consumerLag,
|
||||
backlogCount,
|
||||
retryCount,
|
||||
failedInboxCount,
|
||||
dlqCount,
|
||||
thresholds,
|
||||
warning: warningReasons.length > 0,
|
||||
warningReasons,
|
||||
blocking: false,
|
||||
result: compactRuntimeCommand(result),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function nodeRuntimeCodeAgentManagerTarget(managerUrl: string, fallbackNamespace: string): { namespace: string; serviceName: string } {
|
||||
let parsed: URL | null = null;
|
||||
try {
|
||||
@@ -1666,13 +1725,47 @@ function nodeRuntimeCodeAgentCloudApiEnvStatus(spec: HwlabRuntimeLaneSpec, runti
|
||||
expectValue("HWLAB_KAFKA_TRANSACTIONAL_PROJECTOR_ENABLED", String(kafkaEventBridge.enabled && kafkaEventBridge.features.transactionalProjector));
|
||||
expectValue("HWLAB_KAFKA_PROJECTION_OUTBOX_RELAY_ENABLED", String(kafkaEventBridge.enabled && kafkaEventBridge.features.projectionOutboxRelay));
|
||||
expectValue("HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED", String(kafkaEventBridge.enabled && kafkaEventBridge.features.projectionRealtime));
|
||||
if (kafkaEventBridge.enabled && kafkaEventBridge.features.transactionalProjector) {
|
||||
if (kafkaEventBridge.transactionalProjector === undefined) throw new Error("codeAgentRuntime.kafkaEventBridge.transactionalProjector is required when transactional projector is enabled");
|
||||
expectValue("HWLAB_KAFKA_PROJECTOR_HEARTBEAT_INTERVAL_MS", String(kafkaEventBridge.transactionalProjector.heartbeatIntervalMs));
|
||||
} else {
|
||||
expectAbsent("HWLAB_KAFKA_PROJECTOR_HEARTBEAT_INTERVAL_MS");
|
||||
}
|
||||
const relayEnvNames = [
|
||||
"HWLAB_KAFKA_OUTBOX_RELAY_INTERVAL_MS",
|
||||
"HWLAB_KAFKA_OUTBOX_RELAY_BATCH_SIZE",
|
||||
"HWLAB_KAFKA_OUTBOX_RELAY_LEASE_MS",
|
||||
"HWLAB_KAFKA_OUTBOX_RELAY_SEND_TIMEOUT_MS",
|
||||
"HWLAB_KAFKA_OUTBOX_RELAY_RETRY_BACKOFF_MS",
|
||||
];
|
||||
if (kafkaEventBridge.enabled && kafkaEventBridge.features.projectionOutboxRelay) {
|
||||
const relay = kafkaEventBridge.projectionOutboxRelay;
|
||||
if (relay === undefined) throw new Error("codeAgentRuntime.kafkaEventBridge.projectionOutboxRelay is required when projection outbox relay is enabled");
|
||||
expectValue("HWLAB_KAFKA_OUTBOX_RELAY_INTERVAL_MS", String(relay.intervalMs));
|
||||
expectValue("HWLAB_KAFKA_OUTBOX_RELAY_BATCH_SIZE", String(relay.batchSize));
|
||||
expectValue("HWLAB_KAFKA_OUTBOX_RELAY_LEASE_MS", String(relay.leaseMs));
|
||||
expectValue("HWLAB_KAFKA_OUTBOX_RELAY_SEND_TIMEOUT_MS", String(relay.sendTimeoutMs));
|
||||
expectValue("HWLAB_KAFKA_OUTBOX_RELAY_RETRY_BACKOFF_MS", String(relay.retryBackoffMs));
|
||||
} else {
|
||||
for (const name of relayEnvNames) expectAbsent(name);
|
||||
}
|
||||
const projectionRealtimeEnvNames = ["HWLAB_WORKBENCH_OUTBOX_TAIL_BATCH_SIZE", "HWLAB_WORKBENCH_SSE_HEARTBEAT_MS", "HWLAB_WORKBENCH_SSE_DRAIN_TIMEOUT_MS"];
|
||||
if (kafkaEventBridge.enabled && kafkaEventBridge.features.projectionRealtime) {
|
||||
const realtime = kafkaEventBridge.projectionRealtime;
|
||||
if (realtime === undefined) throw new Error("codeAgentRuntime.kafkaEventBridge.projectionRealtime is required when projection realtime is enabled");
|
||||
expectValue("HWLAB_WORKBENCH_OUTBOX_TAIL_BATCH_SIZE", String(realtime.outboxTailBatchSize));
|
||||
expectValue("HWLAB_WORKBENCH_SSE_HEARTBEAT_MS", String(realtime.sseHeartbeatMs));
|
||||
expectValue("HWLAB_WORKBENCH_SSE_DRAIN_TIMEOUT_MS", String(realtime.sseDrainTimeoutMs));
|
||||
} else {
|
||||
for (const name of projectionRealtimeEnvNames) expectAbsent(name);
|
||||
}
|
||||
}
|
||||
return {
|
||||
ready: mismatches.length === 0,
|
||||
deploymentExists: true,
|
||||
deploymentName: "hwlab-cloud-api",
|
||||
containerName: "hwlab-cloud-api",
|
||||
checkedEnvCount: 9 + (kafkaShadowProducer === undefined ? 0 : 5) + (kafkaEventBridge === undefined ? 0 : 21),
|
||||
checkedEnvCount: 9 + (kafkaShadowProducer === undefined ? 0 : 5) + (kafkaEventBridge === undefined ? 0 : 30),
|
||||
envCount: envByName.size,
|
||||
mismatches,
|
||||
result: compactCodeAgentDeploymentProbeResult(envProbe),
|
||||
|
||||
Reference in New Issue
Block a user