fix(cli): wire aspect-agnostic resolution through cloudrun/lambda/batch + preflight recompute

Addresses R2 CHANGES_REQUESTED from Miga + Rames on PR #2529:

1. Sibling-surface gap (blocker): `hyperframes cloudrun render{,-batch}`,
   `hyperframes lambda render{,-batch}` all advertised the same tier-only
   aliases (`1080p` / `hd` / `4k` / `uhd`) but normalized them to `landscape`
   and never set `outputResolutionAspectAgnostic`. The distributed plumbing
   PR #2529 added received `undefined` from those callers, so portrait `1080p`
   still hit the original aspect-mismatch on Cloud Run / Lambda.

   Fix: introduce `resolveResolutionFlagPair` in `@hyperframes/parsers` (the
   single source of truth for the two-step normalize + aspect-agnostic
   detect) and route every distributed entrypoint through a shared
   `parseOutputResolutionFlag` CLI util so the alias signal now reaches
   `SerializableDistributedRenderConfig`. Studio Server keeps its
   canonical-only HTTP contract; that intent is now pinned in tests.

2. Preflight recompute (hardening): the earlier "downgrade aspect-mismatch"
   preflight cleared un-remapped mismatches, so IG 4:5 (non-preset aspect,
   no sibling) and portrait-4K comp + `--resolution 1080p` (remap +
   downsample) both slipped through to fail late in `resolveDeviceScaleFactor`.
   Now `checkRenderResolutionPreflight` computes the effective preset via
   `suggestMatchingPreset` (mirroring the compile stage's
   `adaptAspectAgnosticResolution`) and re-checks against that — only
   genuinely-fixable mismatches clear early. New tests pin both regressed
   input classes.

3. Docker forwarding boundary test (Miga's important #2): pinned
   `1080p` survives verbatim as `--resolution 1080p` in the Docker args
   so the in-container CLI can re-run `isAspectAgnosticResolutionAlias`.

4. Doc-nit (Miga): parsers/src/types.ts no longer references the
   nonexistent `resolveResolutionForComposition` — points at the actual
   remap helpers.

Fallow: cloudrun.ts / lambda.ts share 390 lines of pre-existing structural
symmetry (parallel AWS + GCP dispatchers), and lambda/render.ts +
render-batch.ts declare parallel RenderArgs interfaces. Both re-flagged
after threading the aspect-agnostic field through each surface; ignored
with justification in .fallowrc.jsonc. lambda.ts's `run` and
lambda/render.ts's `waitForCompletion` are pre-existing CRAP-score
hotspots untouched by this PR — added under health.ignore.

Co-Authored-By: Claude <noreply@anthropic.com>

— Via
This commit is contained in:
Via
2026-07-16 08:01:56 +00:00
co-authored by Claude
parent 7e58d050f8
commit 2d398ed274
18 changed files with 707 additions and 66 deletions
+36
View File
@@ -63,6 +63,42 @@ describe("@hyperframes/core public API exports", () => {
expect(core.isAspectAgnosticResolutionAlias(undefined)).toBe(false);
});
it("exports resolveResolutionFlagPair — the pair every distributed entrypoint must forward", () => {
// The single source of truth every distributed adapter reads
// (`hyperframes cloudrun render`, `hyperframes lambda render`,
// `hyperframes lambda render-batch`). Divergent copies across those
// callers is what shipped the portrait-1080p failure this helper
// exists to prevent (PR #2529). Case-insensitive on the raw input.
expect(core.resolveResolutionFlagPair("1080p")).toEqual({
outputResolution: "landscape",
outputResolutionAspectAgnostic: true,
});
expect(core.resolveResolutionFlagPair("4K")).toEqual({
outputResolution: "landscape-4k",
outputResolutionAspectAgnostic: true,
});
// Canonical preset — aspect-agnostic stays false.
expect(core.resolveResolutionFlagPair("portrait")).toEqual({
outputResolution: "portrait",
outputResolutionAspectAgnostic: false,
});
// Orientation-suffixed alias — aspect-agnostic stays false.
expect(core.resolveResolutionFlagPair("1080p-portrait")).toEqual({
outputResolution: "portrait",
outputResolutionAspectAgnostic: false,
});
// Unknown / empty / undefined → both fields degrade cleanly so
// callers can produce their own "invalid" UX.
expect(core.resolveResolutionFlagPair("8k")).toEqual({
outputResolution: undefined,
outputResolutionAspectAgnostic: false,
});
expect(core.resolveResolutionFlagPair(undefined)).toEqual({
outputResolution: undefined,
outputResolutionAspectAgnostic: false,
});
});
it("exports TIMELINE_COLORS", () => {
expect(core.TIMELINE_COLORS).toBeDefined();
expect(core.TIMELINE_COLORS.video).toBeDefined();