67ed2689ce
First step of unifying application behaviors across sources (NPM, TARBALL, LOCAL), following up on #22868. No storage layout changes. ## Problem Marketplace/tarball installs (`ApplicationInstallService`) and CLI dev sync (`ApplicationDevelopmentService`) each implemented the second half of application delivery — metadata sync, SDK client generation, registration refresh — with quietly diverging behavior: - Install decided SDK regeneration on `!isVersionUpgrade` (application row exists), dev sync on `!application.version`. The version-based check is the robust one: a failed first install leaves an application row without a version, and the row-based check then skipped SDK generation on retry unless the schema changed. - Install refreshed the registration manifest (`updateFromManifest`) without syncing its variable schemas, while catalog sync, tarball upload, and dev sync all do. An NPM install that bumped `latestAvailableVersion` with new `serverVariables` left the registration's variable schemas stale until the next catalog cron. - The guards protecting shared registrations from workspace writes (npm-sourced or not owned by the workspace) lived only inside dev sync's `syncRegistrationMetadata`. ## Change New `ApplicationManifestApplyService` (application-manifest module) owns those steps for both flows: - `applyManifestToWorkspace`: `synchronizeFromManifest` + SDK client generation when it's the first successful apply (no persisted version) or the schema changed. Used by install and dev sync. - `refreshRegistrationFromManifest`: guarded registration update + variable schema sync. Callers acting for a workspace (dev sync) pass `onlyIfOwnedByWorkspaceId`, moving the npm/ownership guard into the shared service; installs pass `latestAvailableVersion` + `preventVersionDowngrade` as before. Returns whether anything was written so dependent side effects (registration asset store in dev sync) skip together with it. - `ApplicationRegistrationService.updateFromManifest` now returns whether it wrote, so variable schemas are only synced from manifests that were actually applied — previously a downgraded install would still have synced variables from the older manifest if we had naively added the sync there. Install and dev services lose the duplicated logic (and their direct `SdkClientGenerationService` / variable-service dependencies); hooks and file writes stay where they were. ## Behavior deltas (all deliberate) - NPM/TARBALL installs now sync registration variable schemas when they refresh the registration. - Retrying a failed first install regenerates the SDK client even when the schema diff is empty. ## Follow-ups (agreed plan, separate PRs) Single semver version gate; one registration-metadata writer with lock coverage for dev-sync/tarball; unified upgrade incl. TARBALL; recoverable upgrades + stale file cleanup; tarball storage decoupled from the owner workspace; orphan cleanup cron; faster public-asset serving (ETag/304); parallel install file writes; PREBUILT execution mode for app logic functions. ## Validation - New spec `application-manifest-apply.service.spec.ts` (9 tests: SDK generation matrix, ownership/npm guards, downgrade-skip short-circuits variable sync) - All 26 application suites pass; typecheck, oxlint (type-aware) and oxfmt green on twenty-server --- _Generated by [Claude Code](https://claude.ai/code/session_015L4zvAL9bsk7azYkbhcZSg)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22921?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->