CMS editor image: build-time reads, sharp in standalone images, arm64 pairing, image smoke test - #331
Merged
Merged
Conversation
Sanitized epic spec for the six-PR CMS editor image epic, cut from the adopter build-failure/runtime-500/architecture-mismatch investigation: 1. build-time reads come from the working tree (fix/build-reads-working-tree) 2. sharp loads lazily (fix/sharp-lazy-load) 3. withCanopy traces libvips into standalone output (fix/sharp-standalone-tracing) 4. CanopyCmsService always passes a resolved architecture, arm64 default (fix/cms-service-architecture) 5. CI image smoke test (ci/standalone-image-smoke) 6. adopter-answer docs (docs/cms-image-adopter-answers) Cross-links added from the four existing task files this epic addresses.
CanopyCmsService passed `props.architecture` straight through, so by default the function got none. DockerImageFunction binds its image code with that architecture, and for `DockerImageCode.fromImageAsset` that is what sets the Docker build platform: with none, CDK set no platform and Docker built for whatever machine ran `cdk deploy`. An Apple Silicon deploy produced an arm64 image for an x86_64 function, which deploys clean and fails at invoke. - Resolve `architecture` to ARM_64 when unset and always pass it, matching the arm64 worker and transform Lambda. No new API. - Scaffold stack template and example stack: drop `platform` and its import; `architecture` is the single source. The example also gains the `NEXT_PUBLIC_CANOPY_MODE: 'prod'` build arg the template already had. - Generated deploy workflow (template and example): run on `ubuntu-24.04-arm`, so the arm64 image builds natively instead of needing QEMU on an x86 runner. - Tests: arm64 default; asset-manifest assertions that `fromImageAsset` without `platform` synthesizes linux/arm64 by default and linux/amd64 with X86_64, and that an explicit platform still wins; the scaffold synth asserts the image and its Lambda agree on arm64. - docs/deploying-to-aws.md: a "Where the image is built" section.
…s platform ARCHITECTURE.md gains a Key Design Decisions entry for why CanopyCmsService always resolves an architecture. DEVELOPING.md notes that a Docker build's platform lives in the asset manifest, not the template, and points at the Dockerfile-only fixture that lets a synth exercise it without Docker.
A claim-check pass over the whole branch, after a code-only review round found no code defects. - Name the error an architecture mismatch actually produces at invoke, Runtime.InvalidEntrypoint, in every copy, and cite it once in the deploy guide (with execve(2) for why a wrong-architecture binary cannot run). - Deploy guide: sharp and libvips come from the package install, not a dedicated Dockerfile step; cite the arm64 runner's private-repo date and Docker's docs for QEMU emulation and its cost; note that the workflow's own dependency install now runs on arm64 Linux; say CDK, not CanopyCmsService, derives the platform. - Workflow comments: an x86 runner's build would run under QEMU, which Docker's docs warn can be much slower, rather than asserting it is slow. - Test helper doc: the asset manifest records the platform the image is built for, per the schema, rather than a CLI flag nothing here observes. - Example stack: its workflow is deploy-cms.yml, not a generated one. - File the pre-existing drift between examples/aws-deployment and the init-deploy templates as a P3 future task.
fix(cdk): always pass the resolved Lambda architecture, arm64 by default
…work transform.ts imported sharp statically, and it sits under canopycms/server and, via api/assets.ts, canopycms/http and canopycms-next. Turbopack emits a top-level await of the external, so importing that graph dlopens libvips: an adopter's standalone editor image with the libvips .so missing returned 500 on every route, 404s included. The static import also defeated pipeline.ts's deliberate fail-open dynamic import. - assets/sharp-loader.ts: loadSharp(), the package's only runtime load of sharp. Memoized, including a rejection (a failed dlopen cannot heal in-process), logging one error per process. - transform.ts: type-only sharp import. applyTransform keeps the 400 for an unsupported format first, then awaits loadSharp() outside its try, so a load failure throws (500) and is never the 422 for undecodable input. - pipeline.ts: uses loadSharp(); still fails open. - eslint: @typescript-eslint/no-restricted-imports rejects a static value import of sharp under packages/canopycms/src outside tests. Tests: server.sharp-unavailable (importing ./server and ./http with sharp unloadable), transform.sharp-unavailable, sharp-loader. Each was break-and-rerun against the pre-fix transform.ts and four single-point breaks. Docs: DEVELOPING.md, ARCHITECTURE.md, CODEBASE_GUIDE.md, assets/AGENTS.md. Files admin-status-image-processing-availability.md (P3, deferred).
CI typecheck failed with TS2550: the package targets ES2021, whose lib types Error without a cause property. The local typecheck ran before the cause-chain helper was added.
… too Two LOW findings from review round 1: - A first loadSharp() call that nobody awaits (a warm-up) left a rejected promise with no handler, which Node treats as fatal by default. Attach a no-op catch to the memoized promise; every caller still gets the rejection. New test; removing the catch makes vitest report an unhandled rejection. - The static-sharp-import lint rule now also covers packages/canopycms-next/src, which sits in the same adopter server graph.
A claim-check pass over the whole diff, each claim run or opened: - eslint.config.mjs said the typescript-eslint rule variant was chosen for allowTypeImports. Core no-restricted-imports (ESLint 9.39) supports it too and flags the same forms, so that reason is gone. The comment also said the canopycms-next scope stops a dependency being added (it stops an import), and the rule message pointed canopycms-next at a module it cannot import. - The Turbopack claim is now scoped to sharp being external, matching what the adopter image's server chunk shows: an async module awaiting the external's load. - DEVELOPING.md: no NON-TEST module imports sharp statically; tests do. - ARCHITECTURE.md: finalize does resize (a throwaway decode check); it stores no resized variants. - assets/AGENTS.md, CODEBASE_GUIDE.md: a static import is a lint error outside tests, and it was transform.ts's import that reached every importer of canopycms/server and canopycms/http, not any import under src/. - The admin-status task and its index row now list the per-request handler error among the signals. - Two stale 'PR 7' forward references to the transform Lambda are gone.
fix(assets): load sharp lazily so a missing libvips fails only image work
- deploying-to-aws.md: document <ClerkProvider publishableKey> read from a run-time variable, with `dynamic = 'force-dynamic'` in the editor layout and no clerkMiddleware, as a supported shape (not yet run live). Present the NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY build arg as the clerkMiddleware path. Restate the Security Model's Clerk note: CanopyCMS's Lambda auth needs only CLERK_JWT_KEY; clerkMiddleware is what needs the secret. - Operating mode: set NEXT_PUBLIC_CANOPY_MODE=prod as a constant build value, expect the override warning, never derive config.mode from either variable. Remove the false "hides the pull-request UI" claim; no client check does that. - middleware templates: the Clerk middleware is optional, and what it costs. - dual-build fixture: the /edit layout reads its key per request. The `dynamic` export moves from the 'use client' page, where Next ignored it and prerendered /edit at build. The test builds without the key, serves with it, and asserts the served /edit carries it. - Backlog: clerk-middleware-runtime-key-unverified, deploy-test-lambda-plaintext-clerk-secret and pr229-review-followups §1 updated to match.
claim-check pass on PR 325: - Security Model: the middleware shape was deploy-tested in 2026-07; only the shape without it is unproven. - deploy doc: the ignored `dynamic` export was measured on a 'use client' page (the fixture's), not on the scaffold's page. - Generated canopy.ts comment (templates.ts, mirrored in example1): a missing CLERK_JWT_KEY throws at the first authenticated request; CLERK_SECRET_KEY is read only where Clerk's backend API is called. - assertAuthPluginAllowedForMode's message names CLERK_JWT_KEY, not CLERK_SECRET_KEY. - README, ARCHITECTURE.md, adopter-migration.md: a CMS server needs the secret only if it also runs clerkMiddleware. - Backlog: file editor-operatingmode-option-unused (P3).
PR 4 copied cms-stack.ts.template's build-args comment into examples/aws-deployment, including "and the prod feature flags". This branch corrected that claim in the template (no client-side check varies by mode except the edit page's auth selection); match it here.
docs: one CMS image for every Clerk tier; what adopters need for NEXT_PUBLIC_CANOPY_MODE
A CMS editor image built from Dockerfile.cms.template failed during `next build` page-data collection with "Cannot initialize local simulated remote: base branch '<name>' does not exist locally". isBuildMode() changed WHO a build-time read runs as, not WHERE: for deployedAs 'server' every read still provisioned a branch clone, which needs the configured base branch to exist in the builder's synthesized snapshot repo. The same root cause made a local `next build` silently read git-committed state from .canopy-dev. Every build-time read now comes from the working tree, in every mode, as the static path already did: - build-mode.ts: readsFromCheckout(config) = isDeployedStatic || isBuildMode decides WHERE; the existing checks keep deciding WHO. - loadOrCreateBranchContext short-circuits on it (covering context.ts, content-reader.ts and ai/resolve-branch.ts); content-reader, resolve-branch and services.ts's HEAD detection use the same predicate. - Dockerfile.cms.template: the builder loses its git install and snapshot commit and sets CANOPY_BUILD_MODE=true; the runner keeps git. - CI: example1's `git checkout -B main` step is removed, so its build on a detached HEAD is the live proof. - Comments and docs that described the old behaviour are corrected; the provisioning lock stays, with its rationale reworded. - Backlog: resolves dev-mode-build-reads-branch-clone-not-working-tree; files build-canopy-scripts-outside-next-build and dev-content-watcher-relative-sourceroot.
Builds no longer provision workspaces, so the codebase guide, the concurrency doc and the content-write-lock task no longer cite build workers as provisioning contenders. The codebase guide also documents readsFromCheckout and the new build-mode-reads integration test.
Branch identity no longer detects git HEAD during a build, the dev content-divergence section covers every build rather than only static ones, README notes that editor saves reach a build only after sync pull, and ARCHITECTURE gains a short design note on why builds read the checkout. The open build-canopy task now also names README's AI-content sentence that depends on its decision.
A real Next 16.1.7 pnpm scaffold built through init + init-deploy aws + docker build hit two scaffold gaps unrelated to this branch: the generated infrastructure/ breaks the app's next build type-check, and the Dockerfile's pnpm path never copies pnpm-workspace.yaml, so pnpm 11 allowBuilds approvals are lost in the builder.
- init.test.ts: the stage split consumed the runner's own FROM line, so a template edit to FROM builder AS runner -- which would carry the builder's CANOPY_BUILD_MODE=true into the deployed CMS -- stayed green. Assert the runner's base image is not the builder. - branch-workspace.test.ts: drop a .canopy-meta absence assertion from the new build-mode twin that no implementation could fail.
- NEXT_PHASE is not yet set when next build loads next.config (the earlier 'while next.config is evaluated' overreached); cite the Next versions checked, 15.5.21 and 16.1.7. - Dockerfile.cms sets CANOPY_BUILD_MODE in its builder stage ahead of the build command, not for the whole stage. - 'builds never provision' narrowed to build-time content reads, in every copy. - mode-env.ts cites where the prod-only checks come from; the integration test header states the matrix it covers; the init-deploy gaps task quotes the CLI's actual wording and the verified es5-ext chain.
The isBuildMode JSDoc named only dist/build/index.js; Next 15.5.21 and 16.1.7 carry the same assignment in dist/esm/build/index.js too.
…NOPY_MODE Build-time reads now come from the working tree in either mode, so a build that resolves prod no longer reads anything differently; what it meets is the prod-mode checks the dev literal keeps out of it, as the Operating mode section says.
fix: build-time reads come from the working tree
sharp loads libvips with dlopen through its native binding's rpath, so nothing requires the shared library and import-following tracers never see it. Next's JS tracer special-cases only sharp 0.34's lib/index.js entry, and a Next 16.1.7 Turbopack standalone build traced libvips's package.json and the rpath symlink but not lib/libvips-cpp.so.*, so the server failed every sharp load with ERR_DLOPEN_FAILED (vercel/next.js#97973). New sharp-tracing.ts finds each installed @img/sharp-libvips-* package's real lib/ directory, reached from the canopycms the project resolves and from any sharp the project resolves directly, looking beside sharp and beside each native binding. It walks the node_modules hierarchy the way a bundler does: not require.resolve, which exports maps defeat, and not require.resolve.paths, which adds global folders. It mirrors Next's tracing root inference, refuses directories outside that root (Turbopack fails the build on a glob that climbs above it) and paths containing glob metacharacters, and never throws. withCanopy merges the result into outputFileTracingIncludes['/**'] for any build except a static export, under experimental before Next 15, keeping the adopter's own includes. A standalone build that finds nothing, or runs from a directory with no next.config, warns with a manual snippet.
…heck docs/deploying-to-aws.md gets the manual outputFileTracingIncludes snippet for configs that do not use withCanopy. README, ARCHITECTURE and CODEBASE_GUIDE describe the new behaviour. New P3 upstream-next-sharp-tracing-recheck.md says how to tell when Next traces the library itself, and what to delete then.
…ows bindings Two defects found by review round 1, sharing one cause. Next 15 and 16 still accept the legacy experimental spellings, and copy any that is present over the top-level key: - experimental.outputFileTracingRoot (and, on Next 15 only, experimental.turbo.root) was not read. withCanopy computed a wider root than Next used, so a monorepo include could climb above Next's root, which fails a Turbopack build. - experimental.outputFileTracingIncludes was not read. Next copied it over the top-level include withCanopy wrote, silently dropping libvips again. withCanopy now reads and writes each option where Next reads it. Also from the same round: - An installed native binding that lists no libvips package contributes its own lib/. sharp 0.35's Windows bindings ship libvips-42.dll and libvips-cpp-8.18.3.dll there and list none, so a Windows standalone build previously got no include and a warning. - isFile and isDirectory answer false on any failed stat, so hasNextConfig can no longer throw out of the adopter's next.config. - New fixture: a project directory reached through a symlink. It pins realpathSync(projectDir) on platforms whose tmpdir is not a symlink.
Review round 2 found the round-1 fix's `in` checks counted a legacy
experimental key set to undefined or null as present. Next's
assignDefaults drops such values before copying legacy keys to the top
level, so withCanopy could:
- write the libvips include under experimental, where Next never reads it,
and drop the adopter's own top-level includes from the result;
- treat a null value as malformed and skip the include with no warning;
- ignore a real top-level outputFileTracingRoot.
Now every value is checked with isSet, and every read goes through
readProperty, so `experimental: true` in an untyped config cannot throw.
Also from the same round:
- On Next 15, any root key on turbopack wins over experimental.turbo.root,
even an empty one, matching Next's `{ ...turbo, ...turbopack }`.
- On Next 13 and 14 the tracing root passed to the lookup is
`experimental.outputFileTracingRoot || projectDir`. Those versions infer
no root from lockfiles (Next 14.2.25 build/index.ts), so a lockfile-
inferred root could let an include climb out of the directory Next copies
from.
… an unknown version Correction to 4761a20. Its message says Next 13 and 14 infer no tracing root from lockfiles. That is wrong: assignDefaults in their server/config.ts sets experimental.outputFileTracingRoot to findRootDir(dir), the directory of the CLOSEST lockfile (checked in Next 13.5.7 and 14.2.25). The `|| projectDir` fallback that commit added made withCanopy refuse a valid include in any Next 13/14 workspace whose lockfile sits above the app. The root lookup now takes lockfileRoot 'closest' (Next 13/14, with Next 14.2.25's lockfile names, which predate bun.lock) or 'outermost' (Next 15 and later, the default). withCanopy passes the one matching the installed Next. Also, per JP's decision on the Next 13/14 key: when the installed Next version cannot be read, the include still falls back to the top-level key, but a standalone build now says so and tells Next 13/14 users to move it under experimental. When nothing could be traced either, the nothing-found warning carries the same note. No version warning fires when a legacy experimental include decides the key, because every Next version reads that one.
…nd unguarded The review of 91f4bda ran five mutations that no test caught. Each now has a test, and each mutation now fails exactly that test: - the unreadable-version warning fires only once per module instance; - an unreadable Next version uses the outermost-lockfile root, as Next 15 and later do; - lockfileRoot 'closest' recognises bun.lockb, which Next 14 looks for; - lockfileRoot 'closest' falls back to the project directory when no lockfile exists; - the outermost walk continues past a nearer lockfile to an outer bun.lock, which Next 14's list does not know.
Every claim in the change, its comments, docs and backlog files was run, opened or mutated against the code, CI logs and the local evidence. Wrong copies were fixed everywhere they appeared, including: - DEVELOPING.md's smoke section: the path filter, the 14 checks (two titles, request-time vs build-time reads), --keep, --work-dir and the container log; - the tsconfig skip cases, which now include an `extends` tsconfig with no exclude of its own (README, deploying-to-aws, CODEBASE_GUIDE, backlog); - withCanopy's turbopack default, which adds no key for an unreadable Next version (README, JSDoc, citations into Next 16.1.7 and 15.5.21); - leg timings, the Node floor, the runner stage's copies, and claims no run measured, which were deleted or given a citation. Two warnings in init.ts said less than the code does. "Not plain JSON" also covers an `exclude` that is not a list of strings, and the kept .dockerignore warning now names infrastructure alongside .env* and vendor/.
CI: build, boot and check the generated CMS image (CMS image epic PR 5) Adds the `standalone-image` CI job and scripts/smoke/standalone-image.mjs, which run the image `canopycms init-deploy aws` generates against real requests. Includes three scaffold fixes the job's first builds found: withCanopy's answer to Next 16's default-Turbopack guard, init-deploy's tsconfig/.dockerignore exclusion of infrastructure/, and the pnpm COPY carrying pnpm-workspace.yaml. Commit history note: b4d7eb1 is titled "docs(tasks): …" but also contains the original withCanopy Turbopack fix, the CLI scaffold fixes, the CI job and script, and their docs. A pre-commit hook rejected the four commits intended to precede it, and their staged files were swept into it. The later commits are each what their subject says; 3cf2007 refines the Turbopack fix.
PR 5 excluded infrastructure/ from the app's tsconfig.json and the image context so `next build` would stop failing on aws-cdk-lib, which left the CDK app with no type-check at all: cdk.json runs it through tsx, which strips types without checking them. A misspelled CanopyCmsService prop was dropped silently and the deploy used the prop's default. - `init-deploy aws` scaffolds infrastructure/tsconfig.json (cdk-tsconfig.json.template): bundler resolution, which accepts the extensionless imports tsx does whatever the package "type"; strict; noEmit; include limited to bin/ and lib/. - deploy-cms.yml.template runs `npx tsc --noEmit -p infrastructure` after the dependency check and before AWS credentials, failing first with a named error when typescript is missing (otherwise npx downloads npm's deprecated `tsc` package). - scaffold-synth.test.ts reads that command out of the generated workflow and runs it: it passes on the scaffold, lists only the CDK app and canopycms.config.ts, and fails with TS2561 on a misspelled memorySize. - examples/aws-deployment gets the same tsconfig and step.
…igration entry
- README and deploying-to-aws.md: the scaffolded infrastructure/tsconfig.json
and the workflow's `tsc --noEmit -p infrastructure` step, including the
local `cdk deploy` sequence; README says withCanopy() should be the
outermost config wrapper, since a plugin wrapped around it adds its
webpack after withCanopy() chose to add `turbopack: {}`.
- adopter-migration.md: one entry for adopters who scaffolded before the CMS
image epic or hand-copied Dockerfile.cms.template: no git or snapshot repo
in the builder, CANOPY_BUILD_MODE, the pnpm-workspace.yaml COPY, the
infrastructure/ exclusions and the new type-check, the ARM_64 default and
omitting `platform`, and withCanopy's tracing and turbopack additions.
- DEVELOPING.md and CODEBASE_GUIDE.md: the new scaffold-synth tests and the
new template.
- future-tasks: items 2 and 3 of cms-image-pr5-review-followups.md resolved
(1 and 4 stay open); pr229-review-followups' row now says the PR-UI claim
was corrected and the runtime mode check remains open.
Review round 1: tsx runs the CDK app with the project's tsconfig.json and honours its `paths` and `jsx`, but the scaffolded infrastructure/tsconfig.json stood alone. An `@/` import in canopycms.config.ts (create-next-app's alias) synthesized and then failed the deploy's type-check with TS2307; so did a `.ts`-extension import. infrastructure/tsconfig.json now extends ../tsconfig.json and overrides only what the CDK app needs: bundler resolution, allowImportingTsExtensions, types: ["node"], skipLibCheck, noEmit. Two overrides are load-bearing: - `exclude: []`, because the inherited exclude resolves against the project directory and names infrastructure/, which leaves no inputs (TS18003); - `incremental: false`, or the app's inherited `incremental: true` writes infrastructure/tsconfig.tsbuildinfo. With no project tsconfig.json the type-check now fails, so init-deploy's existing "No tsconfig.json found" warning says so. The scaffold-synth type-check tests get their own scaffold with a create-next-app-shaped tsconfig.json, and a test that an `@/` alias import resolves. Removing `extends`, `exclude: []` or `incremental: false` from the template each fails them.
Review round 2 found the round-1 fix inheriting too much. tsx resolves the
project's `paths` in every file, but compiles files the project's
tsconfig.json excludes -- infrastructure/, since PR 5 -- without its
compiler options. Extending that tsconfig therefore failed the generated
stack on code tsx runs:
- verbatimModuleSyntax: TS1484 on `import { Stack, StackProps }`, while the
synth passes;
- exactOptionalPropertyTypes: TS2375/TS2379 in app.ts and cms-stack.ts;
- noPropertyAccessFromIndexSignature: TS4111 on each `process.env.X`;
- composite: TS6379 against the `incremental: false` override.
A sweep of 33 project-tsconfig options over a real scaffold found no other
option that fails a project file under the template (the rest either pass or
fail only the workspace's own canopycms src/, which adopters get as .d.ts
behind skipLibCheck; isolatedDeclarations flags only the adopter's
canopycms.config.ts, which `next build` checks under the same option). The
template resets those four, leaving the adopter's other options inherited.
The deploy workflow now also triggers on tsconfig.json: infrastructure/
tsconfig.json extends it, so a commit changing only it can fail the
type-check, and would otherwise surface on the next unrelated deploy.
A new scaffold-synth test sets all four in the app's tsconfig.json and
expects the type-check to pass; removing any one reset fails it with that
option's error.
…ounds The claims pass over the whole PR. The docs, the backlog and several comments still described infrastructure/tsconfig.json as the standalone file the first commit wrote; it now extends the project's tsconfig.json and resets four options. - README, deploying-to-aws.md, the adopter-migration entry, the examples README and the follow-ups file say it extends the project's tsconfig.json, so the type-check fails until the project has one, and that the workflow also runs on a change to tsconfig.json. The follow-ups resolution now lists what the template actually overrides and resets, and cites PR #332. - The template comment says the resets reach canopycms.config.ts as well, which `next build` still checks with the project's own options (verified: tsx applies the project's verbatimModuleSyntax to canopycms.config.ts and not to a file under infrastructure/). Its `exclude` comment says that `init-deploy aws` is what adds infrastructure/ to the inherited list. - init-deploy's missing-tsconfig warning says "as init-deploy writes it", since a kept infrastructure/tsconfig.json may not extend anything. - A test comment no longer calls `incremental` inherited, which the template turns off; another says tsx applies none of the four options to infrastructure/, not that none changes how tsx runs anything. - The migration entry says a hand-copied Dockerfile.cms builds as it did, rather than that nothing requires the new lines. Commit 3d7434e's message says tsx honours the project tsconfig's `paths` and `jsx` for the CDK app. That holds for `paths` only; 6d6812c records why.
Two things PR #332 deliberately did not do, so they don't read as done: CI runs the scaffolded CDK app's type-check only against workspace src/, never the published .d.ts adopters install (checked once by hand); and a project tsconfig.json with a typeRoots that leaves out node_modules/@types, or a narrower rootDir, fails the check loudly on code tsx runs.
fix(cli): type-check the scaffolded CDK app again
Corrects the claims the three claim-check verifiers found wrong or unverifiable across the epic's diff. Comments, JSDoc, docs, test comments, smoke-check names and error strings only; no program or test logic changes. - Turbopack vs webpack: withCanopy's libvips include fixes Turbopack standalone builds, while a pnpm webpack build (seen on Next 15.5.21) bundles sharp. Every copy now says so: the withCanopy JSDoc, sharp-tracing, the smoke script, ci.yml, README, ARCHITECTURE, CODEBASE_GUIDE, DEVELOPING, and the deploy and migration guides. - Stale rationales: "a prod-mode build opens an EFS branch workspace" (cms-service, scaffold-synth test, generated canopycms.config) and "the app need not install aws-cdk-lib" (init, dockerignore, init tests). - Architecture mismatch: name Runtime.InvalidEntrypoint only for the direction the cited source covers, and drop "deploys clean". - Citations: Next 16.1.7 line numbers and renamed functions, Clerk 6.39.5 lines, the optional pnpm-workspace.yaml COPY glob (measured on Docker Engine 29.6.2, BuildKit v0.31.2), and CI leg durations with run IDs. - Smaller fixes: init flags in CODEBASE_GUIDE, init-deploy's overwrite prompt, withCanopy's warning conditions and Turbopack guard, stale "this PR" references, the `npx tsc` note, the pnpm/action-setup version, and smoke-check names that claimed more than they assert. - Backlog: the epic doc records #332 and its follow-up task; new P3 sharp-loader-rejection-test-strength.md; item 5 (the sitemap check) in cms-image-pr5-review-followups.md.
docs: claims pass over the CMS image epic
docs: mechanical re-check of the claims pass (#335)
Brings in the worker-credential epic that landed on int-202609-a since this branch was cut at 8b854c1: #322, #321, #329, #334, #330, #333, #338, #339, #340, #337. Conflicts, each resolved by keeping both sides: - examples/aws-deployment/infrastructure/lib/cms-stack.ts: both sides had added the NEXT_PUBLIC_CANOPY_MODE build-arg comment to the example. Took this branch's wording, which the claims pass corrected against the client code and which the merged template already carries. - packages/canopycms-cdk/src/scaffold-synth.test.ts: kept this branch's linux/arm64 image-platform test and int-202609-a's four JSON-field and GitHub App tests. The App test re-synths into the shared cdk.out and says every other test reads state captured in beforeAll, but the arm64 test read the asset manifests from disk at test time. Moved that read into beforeAll (imagePlatforms) so the claim holds whatever order the tests run in. - CODEBASE_GUIDE.md: CLI commands table keeps this branch's init-deploy aws row and adds int-202609-a's init-github-app row; test-files list keeps int-202609-a's permission drift guard bullet and this branch's extended scaffold synth bullet. Table re-aligned by prettier. - .claude/future-tasks/index.md: every row from both sides in P2, P3 and Resolved, int-202609-a's (newer) rows first. Also: the two sides filed the same examples/aws-deployment drift on 2026-09-12 (example-aws-deployment-drift-from-template.md and example-deploy-workflow-drifted.md there, examples-aws-deployment-drift.md here). All three kept, each now links the other two.
int-202609-a (the JSON-field and App tests) and int-202609-cms-image (the image-asset platform tests) each added `import path from 'node:path'` to cms-deploy.test.ts. Git merged the two lines cleanly, and tsc rejects them with TS2300.
The same examples/aws-deployment drift was filed three times on 2026-09-12, twice on int-202609-a and once on int-202609-cms-image, and the base merge brought all three together. example-aws-deployment-drift-from-template.md, the P1 filing, is now canonical and carries every distinct fact from the other two, re-checked against the merged tree: five template/example pairs and their current state, the workflow and stack drift, the guards that exist and what each pins, and the three fix options with the seven placeholders. example-deploy-workflow-drifted.md and examples-aws-deployment-drift.md move to resolved/ as duplicates. Do-next #16 now points at the canonical file, its P3 row is gone, and the epic spec links the resolved copy.
- scaffold-synth.test.ts: the imagePlatforms comment and the GitHub App test's comment now state the invariant precisely: no other test reads cdk.out after beforeAll. Before, the App test's comment said every other assertion reads beforeAll state, but the prod-mode and workflow tests read scaffold files from disk. - example-aws-deployment-drift-from-template.md: PR #323 edited more of the example than runs-on and build args, and it added NEXT_PUBLIC_CANOPY_MODE independently of #322. Names the test that pins it, calls the cross-copy checks per-feature pins rather than textual ones (the asset-support check instantiates AssetSupport), and adds that the example's tsconfig.json extends a ../tsconfig.json that examples/aws-deployment/ does not have. - index.md: Do-next #16 and the P1 row use the same wording for the cross-copy checks. - cms-image-pr5-review-followups.md: item 6, from the base merge's code review. The file-level beforeAll in scaffold-synth.test.ts also runs, and can fail, for the type-check describe, which has a scaffold of its own.
Merge int-202609-a into int-202609-cms-image
jpslav
added a commit
that referenced
this pull request
Sep 14, 2026
…aims docs: post-merge claims pass over the CMS image epic (#331)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the CMS editor image epic (
int-202609-cms-image) intoint-202609-a. An adopter's first image built fromDockerfile.cms.templatefailed atnext build, then answered 500 on every route, and nothing kept the image platform and Lambda architecture in step. Spec, decisions and as-built notes:.claude/future-tasks/cms-image-build-epic.md. Each PR below had its own review rounds and claim-check.Merged
readsFromCheckout(config)(isDeployedStatic || isBuildMode) short-circuitsloadOrCreateBranchContext; content-reader,ai/resolve-branchand services' HEAD detection use it. The Dockerfile builder drops git and the snapshot commit and setsCANOPY_BUILD_MODE=true; example1's CIgit checkout -B mainstep is gone.loadSharp()(assets/sharp-loader.ts) is the only runtime load: a transform rejects (500) when sharp can't load, finalize fails open, and lint rejects a static value import of sharp outside tests.withCanopytraces sharp's libvips into standalone output (sharp-tracing.ts), under the key the installed Next reads, inside Next's tracing root; a standalone build warns when it can't.CanopyCmsServicealways passes a resolved architecture,ARM_64by default, so CDK derives the image platform. Templates dropplatform; the deploy workflow runs onubuntu-24.04-arm.publishableKey,dynamic = 'force-dynamic'in the editor layout, noclerkMiddleware);NEXT_PUBLIC_CANOPY_MODE=prodas a constant build value.standalone-imageCI job (scripts/smoke/standalone-image.mjs): 14 checks against a Next 16.1.7 scaffold installed frompnpm packtarballs; pnpm and npm onubuntu-latest, pnpm onubuntu-24.04-arm. It fixed the three scaffold gaps it found:turbopack: {}on Next 16+,infrastructure/excluded fromtsconfig.jsonand.dockerignore, and the pnpm COPY carryingpnpm-workspace.yaml.#332: the scaffolded CDK app is type-checked again
#328's
infrastructure/exclusion left the CDK app unchecked, becausecdk.jsonruns it through tsx.init-deploy awsnow scaffoldsinfrastructure/tsconfig.json, which extends the project'stsconfig.jsonand turns offverbatimModuleSyntax,exactOptionalPropertyTypes,noPropertyAccessFromIndexSignature,incrementalandcomposite. The workflow template runsnpx tsc --noEmit -p infrastructurebefore AWS credentials and also triggers ontsconfig.json.scaffold-synth.test.tsruns that command and expects TS2561 on a misspelled prop. README says to makewithCanopy()the outermost config wrapper. Left open:scaffold-cdk-typecheck-published-shape.md.For adopters
docs/adopter-migration.mdhas one entry. It is breaking for a stack that setsplatformonfromImageAssetwithoutarchitecture.Known limitation (pre-existing)
A webpack build with pnpm bundles sharp, so image transforms fail; Turbopack, Next 16's default, is unaffected (
webpack-standalone-sharp-bundled.md).Review status
d63d4367): no finding at MEDIUM or above. One LOW (the smoke test's sitemap check can't tell the working-tree copy from the branch copy) is filed as item 5 ofcms-image-pr5-review-followups.md.c32410eb, via docs: claims pass over the CMS image epic #335 (merge6777c8e9, CI green). No code defects. The unmeasuredpnpm-workspace.yam[l]wildcard COPY was measured and works without the file.5fd4869e, mergeaf979ee1, CI green).int-202609-a(89620302, 10 PRs since8b854c18) was merged into the integration branch with a merge commit via Merge int-202609-a into int-202609-cms-image #341 (540ad4ed). Four conflicts, all resolved keeping both sides; two clean-merge interactions fixed (a duplicatenode:pathimport incms-deploy.test.ts, and an arm64 test reading acdk.outanother test re-synths); the three duplicateexamples/aws-deploymentdrift tasks consolidated intoexample-aws-deployment-drift-from-template.md. Merge int-202609-a into int-202609-cms-image #341 had its own code review (clean) and claim-check.540ad4ed— 11/11, including all threestandalone-imagelegs (pnpm and npm onubuntu-latest, pnpm onubuntu-24.04-arm) and the four E2E shards.Commit messages with wrong claims (history not rewritten)
bd9c7653(corrected by7807bcf0),3d7434e0(by6d6812c1),7240ab1a(see the #328 notes),b4d7eb1e(adocs(tasks)subject on a commit that also carries code).Further notes from PR 5 (#328)
7240ab1aoverclaims in its message: it says the CI path filter covered "all source and packaging inputs" (the tsconfigs, rootpackage.jsonand.nvmrccame later, ind10a5b0b) and that the container log tail prints on every exit path (it prints on failure or early stop). Later commits corrected the code and docs; history was not rewritten.turbopack: nullin an adopter config is treated as unset and gets{}, matching Next's own guard andwithCanopy's existingisSetrule.