fix: expose publish preflight control channels
This commit is contained in:
@@ -124,7 +124,7 @@ The CI user-service artifact task must follow these rules:
|
|||||||
- For D601 direct services, `findjob` and `pipeline` have reviewed dev/prod D601 Compose artifact consumers, `met-nonlinear` is dry-run only until the long-running service image contract matches the published artifact, and `k3sctl-adapter` is supervisor-only because it is the native k3s control bridge outside the k3s failure domain.
|
- For D601 direct services, `findjob` and `pipeline` have reviewed dev/prod D601 Compose artifact consumers, `met-nonlinear` is dry-run only until the long-running service image contract matches the published artifact, and `k3sctl-adapter` is supervisor-only because it is the native k3s control bridge outside the k3s failure domain.
|
||||||
- ClaudeQQ source comes from `https://gitee.com/lyon1998/agent_skills`; the producer exports the `claudeqq/` subtree and overlays the UniDesk Dockerfile plus API adapter from `src/components/microservices/claudeqq/` before building. Runtime topology and deploy intent still live in manifests and `deploy.json`, not in `CI.json`.
|
- ClaudeQQ source comes from `https://gitee.com/lyon1998/agent_skills`; the producer exports the `claudeqq/` subtree and overlays the UniDesk Dockerfile plus API adapter from `src/components/microservices/claudeqq/` before building. Runtime topology and deploy intent still live in manifests and `deploy.json`, not in `CI.json`.
|
||||||
|
|
||||||
The same command also has a read-only preflight mode: `bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha> --dry-run`. That mode may be called from the main server or through remote frontend passthrough, and it must return `runnerDisposition`, `missingChannels`, `channels`, `registry`, `artifactSummary`, `boundary` and `next` without creating a PipelineRun or pushing an image. If backend-core, database or provider channels are missing, the result must be structured `infra-blocked`, not a bare container lookup failure.
|
The same command also has a read-only preflight mode: `bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha> --dry-run`. That mode may be called from the main server or through remote frontend passthrough, and it must return `runnerDisposition`, `missingChannels`, `missingControlChannels`, `channels`, `controlChannels`, `registry`, `artifactSummary`, `controlledPublish`, `boundary` and `next` without creating a PipelineRun or pushing an image. `missingChannels` is the detailed probe list, while `missingControlChannels` is the runner-facing domain list using only `backend-core`, `database`, `provider` and `registry`. `controlledPublish` must point at the real producer boundary: D601, namespace `unidesk-ci`, PipelineRun `unidesk-user-service-artifact-publish`, and the non-dry-run `ci publish-user-service` command shape. If backend-core, database, provider or registry channels are missing, the result must be structured `infra-blocked`, not a bare container lookup failure.
|
||||||
|
|
||||||
Publish a Baidu Netdisk artifact:
|
Publish a Baidu Netdisk artifact:
|
||||||
|
|
||||||
|
|||||||
@@ -188,4 +188,4 @@ backend-core and D601 `code-queue` remain restricted to dev image validation in
|
|||||||
|
|
||||||
This precheck uses lightweight parsing and dry-run evidence only. It intentionally does not run full `check`, e2e, Playwright, or other broad browser/runtime test suites on the master server because those are outside the precheck scope and may exceed master-server resource limits. `backend-core` and D601 `code-queue` production validation are also out of scope; backend-core dev rollout can be attempted only through the existing D601 dev path, and a provider-offline result is an infrastructure blocker rather than permission to validate production.
|
This precheck uses lightweight parsing and dry-run evidence only. It intentionally does not run full `check`, e2e, Playwright, or other broad browser/runtime test suites on the master server because those are outside the precheck scope and may exceed master-server resource limits. `backend-core` and D601 `code-queue` production validation are also out of scope; backend-core dev rollout can be attempted only through the existing D601 dev path, and a provider-offline result is an infrastructure blocker rather than permission to validate production.
|
||||||
|
|
||||||
The structured read-only preflight entrypoints are `artifact-registry status|health` and `ci publish-user-service --dry-run`. Remote runners may call them through the frontend passthrough path, and the result must classify missing backend-core, database or provider channels as `runnerDisposition=infra-blocked` with explicit missing channel names. Those cases are infrastructure blockers, not business failures and not a license to retry a real publish.
|
The structured read-only preflight entrypoints are `artifact-registry status|health` and `ci publish-user-service --dry-run`. Remote runners may call them through the frontend passthrough path, and the result must classify missing backend-core, database, provider or registry channels as `runnerDisposition=infra-blocked`. The detailed probe list remains in `missingChannels`; the stable runner-facing domain list is `missingControlChannels` with only `backend-core`, `database`, `provider` and `registry`. Those cases are infrastructure blockers, not business failures and not a license to retry a real publish. A non-dry-run publish may be attempted only where `controlledPublish` points: D601 CI, namespace `unidesk-ci`, PipelineRun `unidesk-user-service-artifact-publish`.
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ The default release flow for a user-service change is:
|
|||||||
- Commit-pinned image tags are the deployment truth; mutable `latest` tags are not.
|
- Commit-pinned image tags are the deployment truth; mutable `latest` tags are not.
|
||||||
- Root `CI.json` is an artifact catalog only. It lists CI producer inputs such as `serviceId`, artifact kind, source repository, repo-relative Dockerfile, image repository naming, upstream image digest/mirror metadata and the required artifact summary fields; it must not carry runtime topology or replace `deploy.json`.
|
- Root `CI.json` is an artifact catalog only. It lists CI producer inputs such as `serviceId`, artifact kind, source repository, repo-relative Dockerfile, image repository naming, upstream image digest/mirror metadata and the required artifact summary fields; it must not carry runtime topology or replace `deploy.json`.
|
||||||
- The standard CI artifact producer is `bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha>`. It accepts only a pushed Git commit and a service id registered in `CI.json`, reads `source.repo` and `source.dockerfile` from that catalog, rejects ad hoc `--repo` overrides, and reports `serviceId`, `sourceCommit`, `sourceRepo`, `dockerfile`, `imageRef`, `tag`, `digest` and `digestRef`.
|
- The standard CI artifact producer is `bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha>`. It accepts only a pushed Git commit and a service id registered in `CI.json`, reads `source.repo` and `source.dockerfile` from that catalog, rejects ad hoc `--repo` overrides, and reports `serviceId`, `sourceCommit`, `sourceRepo`, `dockerfile`, `imageRef`, `tag`, `digest` and `digestRef`.
|
||||||
|
- The producer dry-run preflight is `bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha> --dry-run`. It is read-only and reports detailed `missingChannels` plus stable `missingControlChannels` for `backend-core`, `database`, `provider` and `registry`; any missing control channel is `runnerDisposition=infra-blocked`. The `controlledPublish` field names D601 `unidesk-ci` as the only controlled environment for the subsequent real publish.
|
||||||
- The CI artifact producer is not a deploy executor. It must not mutate the production namespace, restart production services, or update `deploy.json`.
|
- The CI artifact producer is not a deploy executor. It must not mutate the production namespace, restart production services, or update `deploy.json`.
|
||||||
- `CI.json` may list `blocked` source-build entries when the source input is known but the publish/CD boundary is not yet reviewed. It may also list `upstream-image` entries for image-only services such as File Browser; those entries pin upstream digest and mirror intent but must not be treated as Dockerfile builds.
|
- `CI.json` may list `blocked` source-build entries when the source input is known but the publish/CD boundary is not yet reviewed. It may also list `upstream-image` entries for image-only services such as File Browser; those entries pin upstream digest and mirror intent but must not be treated as Dockerfile builds.
|
||||||
- Every production release must finish with a manual acceptance step after the automated checks pass.
|
- Every production release must finish with a manual acceptance step after the automated checks pass.
|
||||||
|
|||||||
@@ -58,22 +58,34 @@ async function main(): Promise<void> {
|
|||||||
const record = asRecord(result, "preflight");
|
const record = asRecord(result, "preflight");
|
||||||
const source = asRecord(record.source, "source");
|
const source = asRecord(record.source, "source");
|
||||||
const channels = Array.isArray(record.channels) ? record.channels.map((item) => asRecord(item, "channel")) : [];
|
const channels = Array.isArray(record.channels) ? record.channels.map((item) => asRecord(item, "channel")) : [];
|
||||||
|
const controlChannels = Array.isArray(record.controlChannels) ? record.controlChannels.map((item) => asRecord(item, "control channel")) : [];
|
||||||
const registry = asRecord(record.registry, "registry");
|
const registry = asRecord(record.registry, "registry");
|
||||||
|
const controlledPublish = asRecord(record.controlledPublish, "controlledPublish");
|
||||||
const backendCore = asRecord(channels.find((item) => item.channel === "backend-core-api")?.detail, "backendCore detail");
|
const backendCore = asRecord(channels.find((item) => item.channel === "backend-core-api")?.detail, "backendCore detail");
|
||||||
const backendCoreTransport = asRecord(backendCore.detail, "backendCore transport payload");
|
const backendCoreTransport = asRecord(backendCore.detail, "backendCore transport payload");
|
||||||
const backendCoreBody = asRecord(backendCoreTransport.body, "backendCore body payload");
|
const backendCoreBody = asRecord(backendCoreTransport.body, "backendCore body payload");
|
||||||
const providerDispatch = asRecord(channels.find((item) => item.channel === "provider-dispatch")?.detail, "providerDispatch detail");
|
const providerDispatch = asRecord(channels.find((item) => item.channel === "provider-dispatch")?.detail, "providerDispatch detail");
|
||||||
const missingChannels = Array.isArray(record.missingChannels) ? record.missingChannels as string[] : [];
|
const missingChannels = Array.isArray(record.missingChannels) ? record.missingChannels as string[] : [];
|
||||||
|
const missingControlChannels = Array.isArray(record.missingControlChannels) ? record.missingControlChannels as string[] : [];
|
||||||
|
|
||||||
assertCondition(record.ok === false, "infra-blocked preflight should fail", record);
|
assertCondition(record.ok === false, "infra-blocked preflight should fail", record);
|
||||||
assertCondition(record.mode === "dry-run-preflight", "dry-run preflight mode should be reported", record);
|
assertCondition(record.mode === "dry-run-preflight", "dry-run preflight mode should be reported", record);
|
||||||
assertCondition(record.runnerDisposition === "infra-blocked", "runnerDisposition should be infra-blocked", record);
|
assertCondition(record.runnerDisposition === "infra-blocked", "runnerDisposition should be infra-blocked", record);
|
||||||
assertCondition(Array.isArray(record.missingChannels), "missingChannels should be an array", record);
|
assertCondition(Array.isArray(record.missingChannels), "missingChannels should be an array", record);
|
||||||
|
assertCondition(Array.isArray(record.missingControlChannels), "missingControlChannels should be an array", record);
|
||||||
assertCondition(missingChannels.includes("backend-core-api"), "backend-core-api should be missing", record);
|
assertCondition(missingChannels.includes("backend-core-api"), "backend-core-api should be missing", record);
|
||||||
assertCondition(missingChannels.includes("database"), "database should be missing", record);
|
assertCondition(missingChannels.includes("database"), "database should be missing", record);
|
||||||
assertCondition(missingChannels.includes("provider-dispatch"), "provider-dispatch should be missing", record);
|
assertCondition(missingChannels.includes("provider-dispatch"), "provider-dispatch should be missing", record);
|
||||||
assertCondition(missingChannels.includes("provider-host-ssh"), "provider-host-ssh should be missing", record);
|
assertCondition(missingChannels.includes("provider-host-ssh"), "provider-host-ssh should be missing", record);
|
||||||
assertCondition(missingChannels.includes("artifact-registry"), "artifact-registry should be missing", record);
|
assertCondition(missingChannels.includes("artifact-registry"), "artifact-registry should be missing", record);
|
||||||
|
assertCondition(missingControlChannels.join(",") === "backend-core,database,provider,registry", "missingControlChannels should name runner-facing domains", record);
|
||||||
|
assertCondition(controlChannels.length === 4, "controlChannels should report four runner-facing domains", controlChannels);
|
||||||
|
assertCondition(controlChannels.every((item) => item.ok === false), "infra-blocked transport should fail every control channel", controlChannels);
|
||||||
|
assertCondition(
|
||||||
|
(controlChannels.find((item) => item.channel === "provider")?.probes as unknown[]).join(",") === "provider-dispatch,provider-host-ssh",
|
||||||
|
"provider control channel should map provider dispatch and host ssh probes",
|
||||||
|
controlChannels,
|
||||||
|
);
|
||||||
assertCondition(!JSON.stringify(record).includes("No such container: unidesk-database"), "raw container error should not leak", record);
|
assertCondition(!JSON.stringify(record).includes("No such container: unidesk-database"), "raw container error should not leak", record);
|
||||||
assertCondition(backendCoreBody.failureKind === "target-stack-not-running", "backend-core detail should classify target-stack-not-running", backendCoreBody);
|
assertCondition(backendCoreBody.failureKind === "target-stack-not-running", "backend-core detail should classify target-stack-not-running", backendCoreBody);
|
||||||
assertCondition(providerDispatch.status === "infra-blocked", "provider dispatch should be infra-blocked", providerDispatch);
|
assertCondition(providerDispatch.status === "infra-blocked", "provider dispatch should be infra-blocked", providerDispatch);
|
||||||
@@ -82,6 +94,10 @@ async function main(): Promise<void> {
|
|||||||
assertCondition(source.mode === "planned-only", "source should remain planned-only", source);
|
assertCondition(source.mode === "planned-only", "source should remain planned-only", source);
|
||||||
assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source repo should use CI catalog ssh form", source);
|
assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source repo should use CI catalog ssh form", source);
|
||||||
assertCondition(asRecord(record.artifactSummary, "artifactSummary").imageRef === `127.0.0.1:5000/unidesk/frontend:${commit}`, "artifact ref should remain commit-pinned", record.artifactSummary);
|
assertCondition(asRecord(record.artifactSummary, "artifactSummary").imageRef === `127.0.0.1:5000/unidesk/frontend:${commit}`, "artifact ref should remain commit-pinned", record.artifactSummary);
|
||||||
|
assertCondition(controlledPublish.environment === "D601", "controlledPublish should name the controlled environment", controlledPublish);
|
||||||
|
assertCondition(controlledPublish.namespace === "unidesk-ci", "controlledPublish should name the Tekton namespace", controlledPublish);
|
||||||
|
assertCondition(controlledPublish.pipeline === "unidesk-user-service-artifact-publish", "controlledPublish should name the user-service pipeline", controlledPublish);
|
||||||
|
assertCondition(String(controlledPublish.command ?? "").includes("--wait-ms 1200000"), "controlledPublish should provide the real publish command shape", controlledPublish);
|
||||||
assertCondition(String(record.boundary ?? "").includes("read-only"), "boundary should state preflight is read-only", record);
|
assertCondition(String(record.boundary ?? "").includes("read-only"), "boundary should state preflight is read-only", record);
|
||||||
|
|
||||||
process.stdout.write(`${JSON.stringify({
|
process.stdout.write(`${JSON.stringify({
|
||||||
@@ -90,8 +106,11 @@ async function main(): Promise<void> {
|
|||||||
"dry-run preflight returns infra-blocked when backend-core/database/provider channels are absent",
|
"dry-run preflight returns infra-blocked when backend-core/database/provider channels are absent",
|
||||||
"missing channel list names the absent channels",
|
"missing channel list names the absent channels",
|
||||||
"artifact summary remains commit-pinned and read-only",
|
"artifact summary remains commit-pinned and read-only",
|
||||||
|
"missingControlChannels maps detailed probes to backend-core/database/provider/registry",
|
||||||
|
"controlledPublish identifies D601 unidesk-ci as the only place for the real publish",
|
||||||
],
|
],
|
||||||
missingChannels: record.missingChannels,
|
missingChannels: record.missingChannels,
|
||||||
|
missingControlChannels: record.missingControlChannels,
|
||||||
registry,
|
registry,
|
||||||
}, null, 2)}\n`);
|
}, null, 2)}\n`);
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-8
@@ -87,13 +87,24 @@ interface DispatchResult {
|
|||||||
raw: unknown;
|
raw: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PublishPreflightControlChannel = "backend-core" | "database" | "provider" | "registry";
|
||||||
|
type PublishPreflightDetailedChannel = "backend-core-api" | "provider-dispatch" | "provider-host-ssh" | "database" | "artifact-registry";
|
||||||
|
|
||||||
interface PublishPreflightChannelProbe {
|
interface PublishPreflightChannelProbe {
|
||||||
channel: "backend-core-api" | "provider-dispatch" | "provider-host-ssh" | "database" | "artifact-registry";
|
channel: PublishPreflightDetailedChannel;
|
||||||
|
controlChannel: PublishPreflightControlChannel;
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
requiredFor: string;
|
requiredFor: string;
|
||||||
detail: unknown;
|
detail: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PublishPreflightControlChannelProbe {
|
||||||
|
channel: PublishPreflightControlChannel;
|
||||||
|
ok: boolean;
|
||||||
|
requiredFor: string[];
|
||||||
|
probes: PublishPreflightDetailedChannel[];
|
||||||
|
}
|
||||||
|
|
||||||
interface PublishPreflight {
|
interface PublishPreflight {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
runnerDisposition: "ready" | "infra-blocked";
|
runnerDisposition: "ready" | "infra-blocked";
|
||||||
@@ -102,6 +113,8 @@ interface PublishPreflight {
|
|||||||
providerId: string;
|
providerId: string;
|
||||||
supportedArtifactPublish: boolean;
|
supportedArtifactPublish: boolean;
|
||||||
missingChannels: string[];
|
missingChannels: string[];
|
||||||
|
missingControlChannels: PublishPreflightControlChannel[];
|
||||||
|
controlChannels: PublishPreflightControlChannelProbe[];
|
||||||
channels: PublishPreflightChannelProbe[];
|
channels: PublishPreflightChannelProbe[];
|
||||||
registry: unknown;
|
registry: unknown;
|
||||||
next: string[];
|
next: string[];
|
||||||
@@ -362,12 +375,27 @@ function responseOk(response: unknown): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function channelProbe(
|
function channelProbe(
|
||||||
channel: PublishPreflightChannelProbe["channel"],
|
channel: PublishPreflightDetailedChannel,
|
||||||
|
controlChannel: PublishPreflightControlChannel,
|
||||||
ok: boolean,
|
ok: boolean,
|
||||||
requiredFor: string,
|
requiredFor: string,
|
||||||
detail: unknown,
|
detail: unknown,
|
||||||
): PublishPreflightChannelProbe {
|
): PublishPreflightChannelProbe {
|
||||||
return { channel, ok, requiredFor, detail };
|
return { channel, controlChannel, ok, requiredFor, detail };
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishPreflightControlChannelOrder: PublishPreflightControlChannel[] = ["backend-core", "database", "provider", "registry"];
|
||||||
|
|
||||||
|
function summarizePublishControlChannels(channels: PublishPreflightChannelProbe[]): PublishPreflightControlChannelProbe[] {
|
||||||
|
return publishPreflightControlChannelOrder.map((channel) => {
|
||||||
|
const probes = channels.filter((item) => item.controlChannel === channel);
|
||||||
|
return {
|
||||||
|
channel,
|
||||||
|
ok: probes.length > 0 && probes.every((item) => item.ok),
|
||||||
|
requiredFor: Array.from(new Set(probes.map((item) => item.requiredFor))),
|
||||||
|
probes: probes.map((item) => item.channel),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function backendCoreUnavailable(value: unknown): boolean {
|
function backendCoreUnavailable(value: unknown): boolean {
|
||||||
@@ -1292,7 +1320,7 @@ async function publishUserServicePreflight(
|
|||||||
const overview = await transport.coreFetch("/api/overview", { maxResponseBytes: 500_000 });
|
const overview = await transport.coreFetch("/api/overview", { maxResponseBytes: 500_000 });
|
||||||
const overviewBody = coreBody(overview);
|
const overviewBody = coreBody(overview);
|
||||||
const backendCoreOk = responseOk(overview) && overviewBody?.dbReady === true;
|
const backendCoreOk = responseOk(overview) && overviewBody?.dbReady === true;
|
||||||
channels.push(channelProbe("backend-core-api", backendCoreOk, "dispatch API, provider catalog, task polling, and database-backed CI state", {
|
channels.push(channelProbe("backend-core-api", "backend-core", backendCoreOk, "dispatch API, provider catalog, task polling, and database-backed CI state", {
|
||||||
ok: responseOk(overview),
|
ok: responseOk(overview),
|
||||||
dbReady: overviewBody?.dbReady ?? null,
|
dbReady: overviewBody?.dbReady ?? null,
|
||||||
runnerDisposition: asRecord(overview)?.runnerDisposition ?? null,
|
runnerDisposition: asRecord(overview)?.runnerDisposition ?? null,
|
||||||
@@ -1302,7 +1330,7 @@ async function publishUserServicePreflight(
|
|||||||
body: overviewBody,
|
body: overviewBody,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
channels.push(channelProbe("database", backendCoreOk, "backend-core task dispatch, provider state, Tekton task polling, and source identity lookup", {
|
channels.push(channelProbe("database", "database", backendCoreOk, "backend-core task dispatch, provider state, Tekton task polling, and source identity lookup", {
|
||||||
dbReady: overviewBody?.dbReady ?? false,
|
dbReady: overviewBody?.dbReady ?? false,
|
||||||
observedThrough: "backend-core /api/overview",
|
observedThrough: "backend-core /api/overview",
|
||||||
}));
|
}));
|
||||||
@@ -1316,14 +1344,14 @@ async function publishUserServicePreflight(
|
|||||||
"test -S /var/run/docker.sock || test -S /run/docker.sock || true",
|
"test -S /var/run/docker.sock || test -S /run/docker.sock || true",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
const sshProbe = await transport.dispatchHostSsh(probeScript, 30_000, 15_000);
|
const sshProbe = await transport.dispatchHostSsh(probeScript, 30_000, 15_000);
|
||||||
channels.push(channelProbe("provider-dispatch", sshProbe.taskId !== null || sshProbe.ok, "backend-core /api/dispatch can create D601 host.ssh tasks", {
|
channels.push(channelProbe("provider-dispatch", "provider", sshProbe.taskId !== null || sshProbe.ok, "backend-core /api/dispatch can create D601 host.ssh tasks", {
|
||||||
taskId: sshProbe.taskId,
|
taskId: sshProbe.taskId,
|
||||||
status: sshProbe.status,
|
status: sshProbe.status,
|
||||||
ok: sshProbe.taskId !== null || sshProbe.ok,
|
ok: sshProbe.taskId !== null || sshProbe.ok,
|
||||||
exitCode: sshProbe.exitCode,
|
exitCode: sshProbe.exitCode,
|
||||||
stderrTail: sshProbe.stderr.slice(-1200),
|
stderrTail: sshProbe.stderr.slice(-1200),
|
||||||
}));
|
}));
|
||||||
channels.push(channelProbe("provider-host-ssh", sshProbe.ok, "D601 source export, registry checks, kubectl/Tekton submission, and artifact summary reads", {
|
channels.push(channelProbe("provider-host-ssh", "provider", sshProbe.ok, "D601 source export, registry checks, kubectl/Tekton submission, and artifact summary reads", {
|
||||||
taskId: sshProbe.taskId,
|
taskId: sshProbe.taskId,
|
||||||
status: sshProbe.status,
|
status: sshProbe.status,
|
||||||
exitCode: sshProbe.exitCode,
|
exitCode: sshProbe.exitCode,
|
||||||
@@ -1339,9 +1367,11 @@ async function publishUserServicePreflight(
|
|||||||
const registry = artifactRegistryReadonlyResultFromCommand(registryProbe, registryCommand);
|
const registry = artifactRegistryReadonlyResultFromCommand(registryProbe, registryCommand);
|
||||||
const registryRecord = asRecord(registry);
|
const registryRecord = asRecord(registry);
|
||||||
const registryOk = registryRecord?.ok === true || registryRecord?.runtimeApiHealthy === true;
|
const registryOk = registryRecord?.ok === true || registryRecord?.runtimeApiHealthy === true;
|
||||||
channels.push(channelProbe("artifact-registry", registryOk, "commit-pinned image push and later CD manifest checks", registry));
|
channels.push(channelProbe("artifact-registry", "registry", registryOk, "commit-pinned image push and later CD manifest checks", registry));
|
||||||
|
|
||||||
const missingChannels = channels.filter((item) => !item.ok).map((item) => item.channel);
|
const missingChannels = channels.filter((item) => !item.ok).map((item) => item.channel);
|
||||||
|
const controlChannels = summarizePublishControlChannels(channels);
|
||||||
|
const missingControlChannels = controlChannels.filter((item) => !item.ok).map((item) => item.channel);
|
||||||
const ready = missingChannels.length === 0;
|
const ready = missingChannels.length === 0;
|
||||||
return {
|
return {
|
||||||
ok: ready,
|
ok: ready,
|
||||||
@@ -1351,6 +1381,8 @@ async function publishUserServicePreflight(
|
|||||||
providerId,
|
providerId,
|
||||||
supportedArtifactPublish: true,
|
supportedArtifactPublish: true,
|
||||||
missingChannels,
|
missingChannels,
|
||||||
|
missingControlChannels,
|
||||||
|
controlChannels,
|
||||||
channels,
|
channels,
|
||||||
registry,
|
registry,
|
||||||
next: ready
|
next: ready
|
||||||
@@ -1359,6 +1391,7 @@ async function publishUserServicePreflight(
|
|||||||
`later CD must consume ${plannedArtifact.imageRef}; CI itself must not deploy production`,
|
`later CD must consume ${plannedArtifact.imageRef}; CI itself must not deploy production`,
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
|
`Restore missing control channel(s): ${missingControlChannels.join(", ") || "unknown"}.`,
|
||||||
"Run from the main-server CLI or use remote frontend transport against a healthy frontend/backend-core path.",
|
"Run from the main-server CLI or use remote frontend transport against a healthy frontend/backend-core path.",
|
||||||
"Restore backend-core/database/provider-gateway/Host SSH connectivity before retrying artifact publication.",
|
"Restore backend-core/database/provider-gateway/Host SSH connectivity before retrying artifact publication.",
|
||||||
"Use bun scripts/cli.ts artifact-registry health --provider-id D601 to recheck registry reachability after the control bridge is restored.",
|
"Use bun scripts/cli.ts artifact-registry health --provider-id D601 to recheck registry reachability after the control bridge is restored.",
|
||||||
@@ -1536,6 +1569,8 @@ async function publishUserServiceArtifact(config: UniDeskConfig, options: CiPubl
|
|||||||
serviceId: options.serviceId,
|
serviceId: options.serviceId,
|
||||||
supportedArtifactPublish: preflight.supportedArtifactPublish,
|
supportedArtifactPublish: preflight.supportedArtifactPublish,
|
||||||
missingChannels: preflight.missingChannels,
|
missingChannels: preflight.missingChannels,
|
||||||
|
missingControlChannels: preflight.missingControlChannels,
|
||||||
|
controlChannels: preflight.controlChannels,
|
||||||
channels: preflight.channels,
|
channels: preflight.channels,
|
||||||
registry: preflight.registry,
|
registry: preflight.registry,
|
||||||
sourceHostPath: options.sourceHostPath,
|
sourceHostPath: options.sourceHostPath,
|
||||||
@@ -1555,6 +1590,13 @@ async function publishUserServiceArtifact(config: UniDeskConfig, options: CiPubl
|
|||||||
},
|
},
|
||||||
artifact: plannedArtifact.imageRef,
|
artifact: plannedArtifact.imageRef,
|
||||||
artifactSummary: plannedArtifact,
|
artifactSummary: plannedArtifact,
|
||||||
|
controlledPublish: {
|
||||||
|
environment: "D601",
|
||||||
|
namespace: "unidesk-ci",
|
||||||
|
pipeline: "unidesk-user-service-artifact-publish",
|
||||||
|
command: `bun scripts/cli.ts ci publish-user-service --service ${options.serviceId} --commit ${options.commit} --wait-ms 1200000`,
|
||||||
|
requiresReadyControlChannels: publishPreflightControlChannelOrder,
|
||||||
|
},
|
||||||
boundary: preflight.boundary,
|
boundary: preflight.boundary,
|
||||||
next: preflight.next,
|
next: preflight.next,
|
||||||
};
|
};
|
||||||
@@ -1652,6 +1694,8 @@ export async function runCiPublishUserServiceDryRunPreflight(
|
|||||||
serviceId: options.serviceId,
|
serviceId: options.serviceId,
|
||||||
supportedArtifactPublish: preflight.supportedArtifactPublish,
|
supportedArtifactPublish: preflight.supportedArtifactPublish,
|
||||||
missingChannels: preflight.missingChannels,
|
missingChannels: preflight.missingChannels,
|
||||||
|
missingControlChannels: preflight.missingControlChannels,
|
||||||
|
controlChannels: preflight.controlChannels,
|
||||||
channels: preflight.channels,
|
channels: preflight.channels,
|
||||||
registry: preflight.registry,
|
registry: preflight.registry,
|
||||||
sourceHostPath: options.sourceHostPath,
|
sourceHostPath: options.sourceHostPath,
|
||||||
@@ -1671,6 +1715,13 @@ export async function runCiPublishUserServiceDryRunPreflight(
|
|||||||
},
|
},
|
||||||
artifact: plannedArtifact.imageRef,
|
artifact: plannedArtifact.imageRef,
|
||||||
artifactSummary: plannedArtifact,
|
artifactSummary: plannedArtifact,
|
||||||
|
controlledPublish: {
|
||||||
|
environment: "D601",
|
||||||
|
namespace: "unidesk-ci",
|
||||||
|
pipeline: "unidesk-user-service-artifact-publish",
|
||||||
|
command: `bun scripts/cli.ts ci publish-user-service --service ${options.serviceId} --commit ${options.commit} --wait-ms 1200000`,
|
||||||
|
requiresReadyControlChannels: publishPreflightControlChannelOrder,
|
||||||
|
},
|
||||||
boundary: preflight.boundary,
|
boundary: preflight.boundary,
|
||||||
next: preflight.next,
|
next: preflight.next,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user