* Read the untarred planDir's `plan.json` and assert its `planHash` * matches what the Step Functions event claims. Throws on mismatch with * a typed `PLAN_HASH_MISMATCH` error name so the state machine's typed * non-retryable list routes it correctly. * * This is defense-in-depth — the produce
(planDir: string, expected: string)
| 545 | * the Chrome-launch + per-frame capture cost on a misrouted chunk. |
| 546 | */ |
| 547 | function verifyPlanHash(planDir: string, expected: string): void { |
| 548 | const planJsonPath = join(planDir, "plan.json"); |
| 549 | let parsed: { planHash?: unknown }; |
| 550 | try { |
| 551 | parsed = JSON.parse(readFileSync(planJsonPath, "utf-8")) as { planHash?: unknown }; |
| 552 | } catch (err) { |
| 553 | const msg = err instanceof Error ? err.message : String(err); |
| 554 | const error = new Error(`PLAN_HASH_MISMATCH: failed to read ${planJsonPath}: ${msg}`); |
| 555 | error.name = "PLAN_HASH_MISMATCH"; |
| 556 | throw error; |
| 557 | } |
| 558 | const actual = parsed.planHash; |
| 559 | if (typeof actual !== "string" || actual !== expected) { |
| 560 | const error = new Error( |
| 561 | `PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match plan.json planHash=${String(actual)}`, |
| 562 | ); |
| 563 | error.name = "PLAN_HASH_MISMATCH"; |
| 564 | throw error; |
| 565 | } |
| 566 | } |