fix(v02): remove stale endpoint contract freeze

This commit is contained in:
Codex Agent
2026-06-04 15:19:38 +08:00
parent 74af0c796b
commit 7fc6603c30
3 changed files with 103 additions and 9 deletions
+15 -2
View File
@@ -584,14 +584,25 @@ function laneDeployConfig(deployManifest, lane) {
return null;
}
function isHttpEndpoint(value) {
if (typeof value !== "string" || value.trim().length === 0) return false;
try {
const url = new URL(value);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
function assertV02Contracts(catalog, deployManifest) {
const lane = laneDeployConfig(deployManifest, "v02");
assert.ok(lane && typeof lane === "object", "deploy.lanes.v02 must exist for v02 artifact publish");
assert.equal(lane.namespace, "hwlab-v02", "deploy.lanes.v02.namespace must be hwlab-v02");
assert.equal(lane.endpoint, "http://74.48.78.17:19667", "deploy.lanes.v02.endpoint must be the v02 API endpoint");
assert.ok(isHttpEndpoint(lane.endpoint), "deploy.lanes.v02.endpoint must be a non-empty http(s) URL");
assert.equal(catalog.environment, "v02", "catalog.environment must be v02");
assert.equal(catalog.profile, "v02", "catalog.profile must be v02");
assert.equal(catalog.namespace, "hwlab-v02", "catalog.namespace must be hwlab-v02");
if (catalog.endpoint != null) assert.ok(isHttpEndpoint(catalog.endpoint), "catalog.endpoint must be a non-empty http(s) URL");
assert.deepEqual(catalog.allowedProfiles, ["v02"], "catalog.allowedProfiles must only allow v02");
assert.deepEqual(catalog.forbiddenProfiles, ["dev", "prod"], "catalog.forbiddenProfiles must forbid dev/prod");
const catalogIds = (catalog.services ?? []).map((service) => service.serviceId);
@@ -618,13 +629,15 @@ function artifactEnvironment(args) {
function artifactCatalogSkeleton({ args, deployManifest, commitId }) {
const tag = imageTagForCommit(args, commitId);
const laneEndpoint = args.lane === "v02" ? deployManifest?.lanes?.v02?.endpoint : "http://74.48.78.17:16667";
assert.ok(isHttpEndpoint(laneEndpoint), "deploy lane endpoint must be a non-empty http(s) URL");
return {
catalogVersion: "v1",
kind: "hwlab-artifact-catalog",
environment: artifactEnvironment(args),
profile: artifactEnvironment(args),
namespace: args.lane === "v02" ? "hwlab-v02" : "hwlab-dev",
endpoint: args.lane === "v02" ? "http://74.48.78.17:19667" : "http://74.48.78.17:16667",
endpoint: laneEndpoint,
commitId: tag,
artifactState: "contract-skeleton",
publish: {
+22 -7
View File
@@ -172,11 +172,24 @@ function artifactNamespace(args) {
return args.lane === "v02" ? "hwlab-v02" : "hwlab-dev";
}
function artifactEndpoint(args) {
return args.lane === "v02" ? "http://74.48.78.17:19667" : DEV_ENDPOINT;
function isHttpEndpoint(value) {
if (typeof value !== "string" || value.trim().length === 0) return false;
try {
const url = new URL(value);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
function artifactCatalogSkeleton({ args, target, registryPrefix = defaultRegistryPrefix }) {
function artifactEndpoint(args, deploy) {
if (args.lane !== "v02") return DEV_ENDPOINT;
const endpoint = deploy?.lanes?.v02?.endpoint;
assert.ok(isHttpEndpoint(endpoint), "deploy.lanes.v02.endpoint must be a non-empty http(s) URL");
return endpoint;
}
function artifactCatalogSkeleton({ args, deploy, target, registryPrefix = defaultRegistryPrefix }) {
const environment = artifactEnvironment(args);
const namespace = artifactNamespace(args);
return {
@@ -185,7 +198,7 @@ function artifactCatalogSkeleton({ args, target, registryPrefix = defaultRegistr
environment,
profile: environment,
namespace,
endpoint: artifactEndpoint(args),
endpoint: artifactEndpoint(args, deploy),
commitId: target.imageTag,
artifactState: "contract-skeleton",
publish: {
@@ -303,13 +316,13 @@ function assertV02DeployAndCatalog(deploy, catalog) {
const lane = deploy?.lanes?.v02;
assert.ok(lane && typeof lane === "object", "deploy.lanes.v02 must exist for v02 catalog refresh");
assert.equal(lane.namespace, "hwlab-v02", "deploy.lanes.v02.namespace must be hwlab-v02");
assert.equal(lane.endpoint, "http://74.48.78.17:19667", "deploy.lanes.v02.endpoint must be the v02 API endpoint");
assert.ok(isHttpEndpoint(lane.endpoint), "deploy.lanes.v02.endpoint must be a non-empty http(s) URL");
assert.deepEqual((deploy.services ?? []).map((service) => service.serviceId), SERVICE_IDS, "deploy services must cover frozen service IDs in order");
assert.equal(catalog.environment, "v02", "catalog environment must be v02");
assert.equal(catalog.profile, "v02", "catalog profile must be v02");
assert.equal(catalog.namespace, "hwlab-v02", "catalog namespace must be hwlab-v02");
assert.equal(catalog.endpoint, "http://74.48.78.17:19667", "catalog endpoint must be the v02 API endpoint");
if (catalog.endpoint != null) assert.ok(isHttpEndpoint(catalog.endpoint), "catalog endpoint must be a non-empty http(s) URL");
assert.deepEqual(catalog.allowedProfiles, ["v02"], "catalog allowedProfiles must only allow v02");
assert.deepEqual(catalog.forbiddenProfiles, ["dev", "prod"], "catalog forbiddenProfiles must forbid dev/prod");
assert.deepEqual((catalog.services ?? []).map((service) => service.serviceId), serviceIdsForLane("v02"), "catalog services must cover retained v0.2 service IDs in order");
@@ -425,6 +438,7 @@ function refreshDocuments({ deploy, catalog, target, publishRecords, serviceInve
const nextCatalogServices = [];
catalog.commitId = target.imageTag;
if (catalog.profile === "v02") catalog.endpoint = artifactEndpoint({ lane: "v02" }, deploy);
catalog.artifactState = published ? "published" : "contract-skeleton";
catalog.serviceInventory = serviceInventory;
catalog.publish = {
@@ -545,7 +559,7 @@ async function main() {
args.publishReportPath ? readJson(args.publishReportPath) : Promise.resolve(null)
]);
let catalog = await readJsonIfPresent(args.catalogPath, null);
if (!catalog && args.lane === "v02") catalog = artifactCatalogSkeleton({ args, target, registryPrefix: publishReport?.artifactPublish?.registryPrefix ?? defaultRegistryPrefix });
if (!catalog && args.lane === "v02") catalog = artifactCatalogSkeleton({ args, deploy, target, registryPrefix: publishReport?.artifactPublish?.registryPrefix ?? defaultRegistryPrefix });
catalog = normalizeCatalogForLane(catalog, args.lane);
assert.ok(catalog, `${args.catalogPath} is required for ${args.lane} catalog refresh`);
assertLaneDeployAndCatalog(args, deploy, catalog);
@@ -581,6 +595,7 @@ async function main() {
artifactCommitId: target.imageTag,
lane: args.lane,
catalogPath: args.catalogPath,
endpoint: catalog.endpoint ?? null,
imageTagMode: args.imageTagMode,
wrote: args.write ? [args.catalogPath] : [],
deployTruthPolicy: "deploy.json is human-authored runtime config; artifact promotion must not rewrite it",
+66
View File
@@ -353,6 +353,72 @@ test("v02 refresh accepts env-reuse device-pod image tag from environment hash",
assert.equal(devicePod.bootSh, "deploy/runtime/boot/hwlab-device-pod.sh");
});
test("v02 refresh follows the current deploy endpoint instead of the retired 19667 freeze", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-refresh-v02-https-endpoint-"));
const deployPath = path.join(tempDir, "deploy.json");
const catalogPath = path.join(tempDir, "artifact-catalog.v02.json");
await writeFile(deployPath, `${JSON.stringify({
environment: "dev",
namespace: "hwlab-dev",
endpoint: "http://74.48.78.17:16667",
profiles: { dev: { enabled: true }, prod: { enabled: false } },
lanes: {
v02: {
namespace: "hwlab-v02",
endpoint: "https://hwlab.74-48-78-17.nip.io"
}
},
services: SERVICE_IDS.map((serviceId) => ({ serviceId }))
}, null, 2)}\n`);
await writeFile(catalogPath, `${JSON.stringify({
catalogVersion: "v1",
kind: "hwlab-artifact-catalog",
environment: "v02",
profile: "v02",
namespace: "hwlab-v02",
endpoint: "http://74.48.78.17:19667",
commitId,
artifactState: "contract-skeleton",
publish: { registryPrefix: "127.0.0.1:5000/hwlab" },
allowedProfiles: ["v02"],
forbiddenProfiles: ["dev", "prod"],
services: V02_SERVICE_IDS.map((serviceId) => ({
serviceId,
profile: "v02",
namespace: "hwlab-v02",
commitId,
sourceCommitId: commitId,
image: `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`,
imageTag: commitId,
digest: "not_published"
}))
}, null, 2)}\n`);
const result = await execFileAsync(process.execPath, [
"scripts/refresh-artifact-catalog.mjs",
"--lane",
"v02",
"--image-tag-mode",
"full",
"--target-ref",
"HEAD",
"--deploy-json",
deployPath,
"--catalog-path",
catalogPath,
"--blocked",
"--no-write"
], {
cwd: repoRoot,
timeout: 15000,
maxBuffer: 10 * 1024 * 1024
});
const payload = JSON.parse(result.stdout);
assert.equal(payload.status, "blocked");
assert.equal(payload.endpoint, "https://hwlab.74-48-78-17.nip.io");
});
test("v02 refresh reports code-only env-reuse records as reused", async () => {
const commitId = await git(["rev-parse", "HEAD"]);
const previousCommitId = "1234567890abcdef1234567890abcdef12345678";