@kungfu-tech/buildchain 2.10.2 → 2.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1309,6 +1309,133 @@ function htmlLikeResponse(response, url = "") {
1309
1309
  return String(contentType).toLowerCase().includes("text/html") || /\/$|\.html(?:$|[?#])/.test(String(url || ""));
1310
1310
  }
1311
1311
 
1312
+ const managedNetworkRequiredEvidence = [
1313
+ "sync-static-artifact",
1314
+ "write-deployment-manifest",
1315
+ ];
1316
+
1317
+ function operationEvidenceStatus(operation, { plannedEvidence = false } = {}) {
1318
+ if (plannedEvidence && operation.status === undefined) {
1319
+ return true;
1320
+ }
1321
+ return operation.status === "applied" || operation.status === "planned";
1322
+ }
1323
+
1324
+ function managedNetworkHealthEvidence({ target, result, plan }) {
1325
+ const operationSource = Array.isArray(result?.operations) && result.operations.length > 0
1326
+ ? "apply-result"
1327
+ : "deploy-plan";
1328
+ const operations = operationSource === "apply-result"
1329
+ ? result.operations
1330
+ : (Array.isArray(plan?.steps) ? plan.steps : []);
1331
+ const surfaceOperations = operations.filter((operation) => operation.surface === target.surface);
1332
+ const presentActions = new Set(
1333
+ surfaceOperations
1334
+ .filter((operation) => operationEvidenceStatus(operation, { plannedEvidence: operationSource === "deploy-plan" }))
1335
+ .map((operation) => operation.action),
1336
+ );
1337
+ const missingActions = managedNetworkRequiredEvidence.filter((action) => !presentActions.has(action));
1338
+ const missingFields = [];
1339
+ if (!target.manifestKey) missingFields.push("manifestKey");
1340
+ if (!target.bucket) missingFields.push("bucket");
1341
+ if (!target.objectPrefix) missingFields.push("objectPrefix");
1342
+ return {
1343
+ source: operationSource,
1344
+ actions: [...presentActions].sort(),
1345
+ requiredActions: managedNetworkRequiredEvidence,
1346
+ missingActions,
1347
+ missingFields,
1348
+ status: missingActions.length === 0 && missingFields.length === 0 ? "pass" : "fail",
1349
+ };
1350
+ }
1351
+
1352
+ function managedNetworkTargetObjectKey(target) {
1353
+ const requestPath = String(target.requestPath || "/");
1354
+ let relative = requestPath.replace(/^\/+/, "");
1355
+ if (!relative) {
1356
+ relative = "index.html";
1357
+ } else if (requestPath.endsWith("/")) {
1358
+ relative = `${relative.replace(/\/+$/, "")}/index.html`;
1359
+ }
1360
+ return joinS3Key(target.objectPrefix, relative);
1361
+ }
1362
+
1363
+ function managedNetworkHeadObject({ target, kind, key, commandRunner }) {
1364
+ return runAdapterOperation({
1365
+ operation: {
1366
+ action: `health-head-${kind}`,
1367
+ surface: target.surface,
1368
+ command: "aws",
1369
+ args: ["s3api", "head-object", "--bucket", target.bucket, "--key", key],
1370
+ },
1371
+ dryRun: false,
1372
+ commandRunner,
1373
+ });
1374
+ }
1375
+
1376
+ function managedNetworkS3HealthCheck({ target, evidence, commandRunner }) {
1377
+ const objectKey = managedNetworkTargetObjectKey(target);
1378
+ const headResults = [
1379
+ managedNetworkHeadObject({ target, kind: "manifest", key: target.manifestKey, commandRunner }),
1380
+ managedNetworkHeadObject({ target, kind: "object", key: objectKey, commandRunner }),
1381
+ ];
1382
+ const failed = headResults.filter((operation) => operation.status === "failed");
1383
+ return {
1384
+ surface: target.surface,
1385
+ kind: target.kind,
1386
+ requestPath: target.requestPath,
1387
+ url: target.url,
1388
+ accessControl: "managed-network",
1389
+ healthStrategy: "s3-object",
1390
+ status: evidence.status === "pass" && failed.length === 0 ? "pass" : "fail",
1391
+ httpStatus: null,
1392
+ finalUrl: "",
1393
+ noindexHeader: false,
1394
+ manifestKey: target.manifestKey || "",
1395
+ bucket: target.bucket || "",
1396
+ objectPrefix: target.objectPrefix || "",
1397
+ objectKey,
1398
+ evidence,
1399
+ s3Checks: headResults.map((operation) => ({
1400
+ action: operation.action,
1401
+ bucket: target.bucket,
1402
+ key: operation.args?.[operation.args.indexOf("--key") + 1] || "",
1403
+ status: operation.status,
1404
+ exitCode: operation.exitCode,
1405
+ stderr: operation.stderr || "",
1406
+ })),
1407
+ message: evidence.status === "pass" && failed.length === 0
1408
+ ? "managed-network surface health verified from S3 manifest and object head checks; direct public fetch skipped"
1409
+ : `managed-network S3 object health failed: ${failed.map((operation) => `${operation.action}:${operation.stderr || operation.exitCode}`).join("; ") || "missing deployment evidence"}`,
1410
+ };
1411
+ }
1412
+
1413
+ function managedNetworkHealthCheck({ target, result, plan, commandRunner, verifyS3Objects = true }) {
1414
+ const evidence = managedNetworkHealthEvidence({ target, result, plan });
1415
+ if (verifyS3Objects && result?.status === "applied" && evidence.status === "pass") {
1416
+ return managedNetworkS3HealthCheck({ target, evidence, commandRunner });
1417
+ }
1418
+ return {
1419
+ surface: target.surface,
1420
+ kind: target.kind,
1421
+ requestPath: target.requestPath,
1422
+ url: target.url,
1423
+ accessControl: "managed-network",
1424
+ healthStrategy: "deployment-evidence",
1425
+ status: evidence.status,
1426
+ httpStatus: null,
1427
+ finalUrl: "",
1428
+ noindexHeader: false,
1429
+ manifestKey: target.manifestKey || "",
1430
+ bucket: target.bucket || "",
1431
+ objectPrefix: target.objectPrefix || "",
1432
+ evidence,
1433
+ message: evidence.status === "pass"
1434
+ ? "managed-network surface health verified from deployment manifest and S3 object sync evidence; direct public fetch skipped"
1435
+ : `managed-network surface health requires manifest and S3 object evidence; missing actions=${evidence.missingActions.join(",") || "none"}, missing fields=${evidence.missingFields.join(",") || "none"}`,
1436
+ };
1437
+ }
1438
+
1312
1439
  export async function checkWebSurfaceHealth({
1313
1440
  result = null,
1314
1441
  plan = null,
@@ -1316,6 +1443,9 @@ export async function checkWebSurfaceHealth({
1316
1443
  fetchImpl = fetch,
1317
1444
  checkedAt = new Date().toISOString(),
1318
1445
  allowedStatuses = [200],
1446
+ allowedManagedNetworkRunner = false,
1447
+ managedNetworkS3ObjectVerification = true,
1448
+ commandRunner = defaultCommandRunner,
1319
1449
  } = {}) {
1320
1450
  const loadedConfig = loadBuildchainConfig(cwd);
1321
1451
  const config = assertWebSurfaceConfig(loadedConfig);
@@ -1351,6 +1481,10 @@ export async function checkWebSurfaceHealth({
1351
1481
  required: smoke.required !== false,
1352
1482
  missing: Boolean(smoke.missing),
1353
1483
  message: smoke.message || "",
1484
+ accessControl: binding.accessControl || config.channels?.[channel]?.accessControl || "",
1485
+ manifestKey: binding.manifestKey || "",
1486
+ bucket: binding.bucket || "",
1487
+ objectPrefix: binding.objectPrefix || "",
1354
1488
  }));
1355
1489
  })
1356
1490
  : Object.entries(urls).map(([surface, url]) => ({
@@ -1359,6 +1493,7 @@ export async function checkWebSurfaceHealth({
1359
1493
  requestPath: "/",
1360
1494
  url,
1361
1495
  required: true,
1496
+ accessControl: config.channels?.[channel]?.accessControl || "",
1362
1497
  }));
1363
1498
 
1364
1499
  for (const target of smokeTargets) {
@@ -1377,6 +1512,16 @@ export async function checkWebSurfaceHealth({
1377
1512
  });
1378
1513
  continue;
1379
1514
  }
1515
+ if (target.accessControl === "managed-network" && !allowedManagedNetworkRunner) {
1516
+ checks.push(managedNetworkHealthCheck({
1517
+ target,
1518
+ result,
1519
+ plan,
1520
+ commandRunner,
1521
+ verifyS3Objects: managedNetworkS3ObjectVerification,
1522
+ }));
1523
+ continue;
1524
+ }
1380
1525
  try {
1381
1526
  const response = await fetchImpl(url, { redirect: "follow" });
1382
1527
  const expectedNoindex = Boolean(config.channels?.[channel]?.noindex);
@@ -1393,6 +1538,8 @@ export async function checkWebSurfaceHealth({
1393
1538
  kind: target.kind,
1394
1539
  requestPath: target.requestPath,
1395
1540
  url,
1541
+ accessControl: target.accessControl || "",
1542
+ healthStrategy: "http",
1396
1543
  status: statusOk && noindexOk ? "pass" : "fail",
1397
1544
  httpStatus: response.status,
1398
1545
  finalUrl: response.url || url,
@@ -231,7 +231,22 @@ export function webSurfaceCli() {
231
231
  const resultFile = readArg("result", process.env.BUILDCHAIN_WEB_SURFACE_APPLY_RESULT || "");
232
232
  const result = resultFile ? JSON.parse(fs.readFileSync(path.resolve(resultFile), "utf8")) : null;
233
233
  const plan = readJsonFileArg("plan");
234
- const health = checkWebSurfaceHealth({ cwd, result, plan });
234
+ const envAllowedManagedNetworkRunner = process.env.BUILDCHAIN_WEB_SURFACE_HEALTH_ALLOWED_RUNNER === "true" ||
235
+ process.env.BUILDCHAIN_WEB_SURFACE_HEALTH_ALLOWED_RUNNER === "1";
236
+ const health = checkWebSurfaceHealth({
237
+ cwd,
238
+ result,
239
+ plan,
240
+ allowedManagedNetworkRunner: readBooleanArg(
241
+ "allowed-managed-network-runner",
242
+ envAllowedManagedNetworkRunner,
243
+ ),
244
+ managedNetworkS3ObjectVerification: readBooleanArg(
245
+ "managed-network-s3-object-verification",
246
+ process.env.BUILDCHAIN_WEB_SURFACE_HEALTH_S3_OBJECTS !== "false" &&
247
+ process.env.BUILDCHAIN_WEB_SURFACE_HEALTH_S3_OBJECTS !== "0",
248
+ ),
249
+ });
235
250
  return Promise.resolve(health).then((resolved) => {
236
251
  writeJson(resolved, output);
237
252
  writeGitHubOutputs({