Skip to content

refactor(readiness): readonly-domain-types (stack 10/12, re-split #307) - #383

Closed
YosefHayim wants to merge 1 commit into
refactor/types/readonly-stack-09-releasefrom
refactor/types/readonly-stack-10-readiness
Closed

refactor(readiness): readonly-domain-types (stack 10/12, re-split #307)#383
YosefHayim wants to merge 1 commit into
refactor/types/readonly-stack-09-releasefrom
refactor/types/readonly-stack-10-readiness

Conversation

@YosefHayim

@YosefHayim YosefHayim commented Aug 7, 2026

Copy link
Copy Markdown
Owner

User description

Stack 10/12 of re-split HOLD #307

Domain: readiness
Base: refactor/types/readonly-stack-09-release
Full green tip: refactor/foundation/readonly-types-full

Land stack in order. Intermediate PRs may not typecheck alone.


Summary by cubic

Adopts read-only types across readiness, migrate, plan, and snapshot modules to improve immutability and type safety. Updates APIs to accept readonly collections, uses MutableDeep for internal builders, and hardens snapshot JSON parsing.

  • Refactors
    • Switched many params/returns to readonly: planners (runPlanners), readiness probes/scopes, migrate commands/tests, snapshot orchestrator/commands/sources.
    • Used MutableDeep<...> when constructing objects (Fastlane/EAS parsers, Play product mapping) to satisfy read-only models.
    • Tightened snapshot store schema (removed Schema.mutable), added JSON guards (isJsonObject/isJsonRecord) and readonly field helpers; replaced filter+type-guard with flatMap to drop nulls.
    • Simplified product aggregation to return only present keys; updated readFastlaneSetup to build results without mutation.
    • Minor type tweaks across adopters and planners (e.g., readonly capability settings, profiles, planned actions, inputs).

Written for commit 55007a4. Summary will update on new commits.

Review in cubic


CodeAnt-AI Description

Strengthen readiness, planning, migration, and snapshot data handling

What Changed

  • Snapshot capture and restore now reject malformed array, null, or non-object data instead of treating it as valid records
  • Fastlane migration keeps all discovered Appfile, Matchfile, and Supplyfile details when multiple files are present
  • Product, capability, certificate, planning, readiness, and snapshot workflows now safely accept read-only input collections without changing their results
  • App Store and Google Play planner configuration is checked against the expected surface type

Impact

✅ Safer snapshot restores
✅ Complete Fastlane migration details
✅ Fewer failures when processing read-only configuration

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 55007a4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 55007a4 Aug 07, 2026 · 11:07 11:10

@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 7, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Refactor readiness stack: adopt readonly domain types across migrate/plan/snapshot

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Update readiness, migrate, plan, and snapshot code to accept readonly domain collections.
• Replace in-place mutation with MutableDeep intermediates and functional object construction.
• Tighten snapshot JSON parsing and schema definitions to match immutable JsonValue types.
Diagram

graph TD
  Types["Readonly domain types"] --> Adopt["Adopt"] & Migrate["Migrate"] & Plan["Plan"] & Readiness["Readiness"]
  Readiness --> Snapshot["Snapshot"]
  Mutable["MutableDeep helper"] --> Adopt & Migrate & Snapshot
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep domain types mutable; enforce immutability at boundaries only
  • ➕ Fewer call-site changes; simpler incremental object construction
  • ➕ Less churn across tests and helper functions
  • ➖ Weakens the readonly guarantee across the codebase
  • ➖ Allows accidental mutation in long-lived structures (harder-to-debug side effects)
2. Use type assertions/casts instead of `MutableDeep` for builders
  • ➕ Minimal code changes
  • ➕ Fastest to land in a stacked refactor
  • ➖ Easy to mask real mismatches between runtime shape and declared readonly types
  • ➖ Accumulates technical debt and reduces reviewer confidence
3. Introduce explicit builder helpers for common domain objects
  • ➕ Centralizes mutation; produces clean readonly outputs without MutableDeep
  • ➕ Improves consistency across modules
  • ➖ More up-front design work and new API surface
  • ➖ Harder to fit into a multi-PR stacked migration

Recommendation: Current approach is the best fit for a stacked readonly migration: keep domain types truly readonly, and use MutableDeep only as a short-lived construction aid where needed. It preserves the immutability guarantee without resorting to unsafe casts, while avoiding the overhead of introducing builder APIs mid-stack.

Files changed (34) +176 / -133

Refactor (28) +162 / -124
capabilities.tsAdopt readonly capability settings collections +1/-1

Adopt readonly capability settings collections

• Updates the settings-by-type map to store readonly arrays of 'CapabilitySetting'. This aligns the adopter with readonly domain settings without changing runtime behavior.

src/core/adopt/capabilities.ts

certs.tsTreat profiles list as readonly +1/-1

Treat profiles list as readonly

• Changes the 'profiles' variable to 'readonly ProfileResource[]' to match readonly domain types. Control flow and ASC calls remain the same.

src/core/adopt/certs.ts

configWriter.tsReturn minimal AppProducts objects without mutation +6/-4

Return minimal AppProducts objects without mutation

• Rewrites 'aggregateProductPieces' to return explicit object literals for each presence/absence combination. Avoids mutating an initially-empty 'products' object, which is incompatible with readonly shapes.

src/core/adopt/configWriter.ts

products.tsUse MutableDeep for incremental localization construction +2/-1

Use MutableDeep for incremental localization construction

• Introduces 'MutableDeep' and uses it when building 'ProductLocalization' objects before returning them as readonly-shaped values. This preserves readonly domain types while still allowing stepwise property assignment.

src/core/adopt/products.ts

command.tsAccept readonly selected apps for export compliance repair +1/-1

Accept readonly selected apps for export compliance repair

• Changes the 'fixExportCompliance' parameter to 'readonly AppDescriptor[]'. Improves type compatibility with upstream selectors returning readonly lists.

src/core/doctor/command.ts

command.tsPrint migration notes from readonly arrays +1/-1

Print migration notes from readonly arrays

• Updates 'printMigrationNotes' to accept 'readonly MigrationNote[]'. Behavior is unchanged; it iterates and logs notes as before.

src/core/migrate/command.ts

eas.tsUse MutableDeep for EAS profile and credentials summaries +3/-2

Use MutableDeep for EAS profile and credentials summaries

• Adds 'MutableDeep' and uses it for objects that are constructed incrementally ('EasSubmitProfile', 'CredentialsSummary'). This prevents readonly-type assignment errors while preserving readonly externally.

src/core/migrate/eas.ts

fastlane.tsRemove mutation-heavy construction in Fastlane parsing/setup +30/-11

Remove mutation-heavy construction in Fastlane parsing/setup

• Uses 'MutableDeep' for parsing outputs that are built incrementally (Appfile/Matchfile/Supplyfile/FastlaneLane). Refactors 'readFastlaneSetup' to build optional sections first and return merged objects via spreads instead of mutating a base object; updates helpers to accept readonly lane arrays.

src/core/migrate/fastlane.ts

scaffold.tsBuild env example from readonly keys +1/-1

Build env example from readonly keys

• Changes 'buildEnvExample' to accept 'readonly string[]'. Output and formatting logic are unchanged.

src/core/migrate/scaffold.ts

orchestrator.tsRun planners from readonly list +1/-1

Run planners from readonly list

• Changes 'runPlanners' to accept 'readonly SurfacePlanner[]'. Execution order and effect composition remain the same.

src/core/plan/orchestrator.ts

appStoreSurface.tsMake planned actions readonly in surface specs +5/-2

Make planned actions readonly in surface specs

• Updates surface spec types to return 'readonly PlannedAction[]' for both app-level and team-level reconciles. Aligns planner interfaces with readonly domain action lists.

src/core/plan/planners/appStoreSurface.ts

euDistribution.tsAdd explicit config typing for sidecar resolution +2/-1

Add explicit config typing for sidecar resolution

• Imports 'EuDistributionConfig' and applies a generic parameter to 'resolveSidecarConfig' for stronger inference under readonly types. No runtime logic changes.

src/core/plan/planners/euDistribution.ts

gameCenter.tsAdd explicit Game Center config typing for sidecar resolution +2/-1

Add explicit Game Center config typing for sidecar resolution

• Imports 'GameCenterConfig' and supplies it to 'resolveSidecarConfig' to satisfy stricter readonly typings. Planning logic is unchanged.

src/core/plan/planners/gameCenter.ts

playProducts.tsCompute Play product targets from readonly app lists +1/-1

Compute Play product targets from readonly app lists

• Updates 'targetsFor' to accept 'readonly AppDescriptor[]'. Target derivation behavior remains the same.

src/core/plan/planners/playProducts.ts

playSubscriptions.tsCompute Play subscription targets from readonly app lists +4/-1

Compute Play subscription targets from readonly app lists

• Updates 'targetsFor' signature to accept 'readonly AppDescriptor[]' and reformats the signature for clarity. Target selection logic is unchanged.

src/core/plan/planners/playSubscriptions.ts

wallet.tsAdd explicit Wallet config typing for sidecar resolution +2/-1

Add explicit Wallet config typing for sidecar resolution

• Imports 'WalletConfig' and uses 'resolveSidecarConfig<WalletConfig>' to satisfy readonly inference. Planner behavior stays the same.

src/core/plan/planners/wallet.ts

appScopes.tsScope iOS/Android apps from readonly app lists +2/-2

Scope iOS/Android apps from readonly app lists

• Updates 'iosApps' and 'androidApps' to accept 'readonly AppDescriptor[]'. Output stays mutable 'ScopedApp[]', but input is now readonly-compatible.

src/core/readiness/appScopes.ts

iapCodeReference.tsAccept readonly product id lists for source scanning +1/-1

Accept readonly product id lists for source scanning

• Changes the probe helper to take 'readonly string[]' for product ids. Keeps scanning behavior identical while matching readonly domain inputs.

src/core/readiness/probes/iapCodeReference.ts

profileEntitlements.tsCompute entitled apps from readonly app lists +1/-1

Compute entitled apps from readonly app lists

• Updates 'entitledApps' to accept 'readonly AppDescriptor[]'. Entitlement mapping logic is unchanged.

src/core/readiness/probes/profileEntitlements.ts

orchestrator.tsCapture snapshot from readonly sources list +1/-1

Capture snapshot from readonly sources list

• Changes 'captureSnapshot' to accept 'readonly SnapshotSource[]'. Orchestration logic remains the same.

src/core/snapshot/orchestrator.ts

snapshotCommand.tsPropagate readonly app/entity collections through snapshot CLI +4/-4

Propagate readonly app/entity collections through snapshot CLI

• Makes snapshot app selection inputs/outputs readonly ('selectSnapshotApps', session 'apps'). Also changes 'savedEntitiesFor' to return 'readonly AppEntities[]', aligning CLI plumbing with immutable snapshot domain types.

src/core/snapshot/snapshotCommand.ts

appleListing.tsHarden captured listing JSON narrowing and readonly field types +22/-10

Harden captured listing JSON narrowing and readonly field types

• Adds 'isJsonObject' to robustly narrow 'JsonValue' nodes to plain objects, and updates helpers to accept readonly records. Replaces type-guard filtering with 'flatMap' to build the captured apps list without nulls, matching readonly array types.

src/core/snapshot/sources/appleListing.ts

appleProducts.tsSimplify imports and build captured apps via flatMap +5/-7

Simplify imports and build captured apps via flatMap

• Removes unused snapshot type imports and replaces 'filter' type-guard with 'flatMap' to omit null app captures. Improves compatibility with readonly 'apps' arrays in capture outcomes.

src/core/snapshot/sources/appleProducts.ts

appleSubscriptions.tsSimplify imports and build captured apps via flatMap +5/-7

Simplify imports and build captured apps via flatMap

• Removes unused snapshot type imports and replaces 'filter' type-guard with 'flatMap' to omit null app captures. Keeps capture semantics the same while meeting readonly typing constraints.

src/core/snapshot/sources/appleSubscriptions.ts

playProducts.tsUse MutableDeep while building Play product overrides/localizations +3/-2

Use MutableDeep while building Play product overrides/localizations

• Introduces 'MutableDeep' for incrementally-constructed 'ProductLocalization' and 'PlayProductOverride' objects. Returned values remain readonly-compatible domain objects.

src/core/snapshot/sources/playProducts.ts

playRestore.tsReturn readonly JSON records and centralize JSON record narrowing +16/-5

Return readonly JSON records and centralize JSON record narrowing

• Adds 'isJsonRecord' and updates 'jsonRecord'/'stringField' to use readonly record types, ensuring malformed or undefined captured nodes are rejected consistently. This aligns restore parsing with the immutable 'JsonValue' union.

src/core/snapshot/sources/playRestore.ts

playSubscriptions.tsAccept readonly inputs when normalizing Play subscription structures +3/-3

Accept readonly inputs when normalizing Play subscription structures

• Updates helpers ('regionalPrices', 'basePlans', 'listings') to accept readonly arrays. Normalization logic and serialized output shape remain unchanged.

src/core/snapshot/sources/playSubscriptions.ts

store.tsMake snapshot schemas immutable to match readonly snapshot domain types +36/-50

Make snapshot schemas immutable to match readonly snapshot domain types

• Removes 'Schema.mutable' wrappers and restructures schema declarations so snapshot entities, app entities, capture outcomes, and snapshot reports are modeled as immutable arrays/records. Aligns runtime schema validation with readonly domain types used throughout snapshot capture/restore.

src/core/snapshot/store.ts

Tests (6) +14 / -9
eas.test.tsTest helpers accept readonly migration outputs +2/-2

Test helpers accept readonly migration outputs

• Updates test helper signatures ('artifact', 'notesAt') to accept readonly arrays. Keeps test intent the same while matching new readonly types.

src/core/migrate/eas.test.ts

fastlane.test.tsAdjust Fastlane tests for readonly collections +6/-3

Adjust Fastlane tests for readonly collections

• Updates helper function signatures to accept readonly arrays and adjusts sorting to spread 'beta.actions' before sorting. Keeps assertions equivalent while accommodating readonly/iterable action collections.

src/core/migrate/fastlane.test.ts

orchestrator.test.tsPlanner test harness accepts readonly planners list +1/-1

Planner test harness accepts readonly planners list

• Updates 'runPlannerSet' to accept 'readonly SurfacePlanner[]' so tests match the orchestrator’s readonly signature.

src/core/plan/orchestrator.test.ts

playSubscriptions.test.tsTest helper accepts readonly subscription configs +3/-1

Test helper accepts readonly subscription configs

• Updates helper 'productsWith' to accept 'readonly SubscriptionConfig[]'. Keeps fixture generation equivalent.

src/core/plan/planners/playSubscriptions.test.ts

orchestrator.test.tsRun readiness probes from readonly probe arrays +1/-1

Run readiness probes from readonly probe arrays

• Updates the probe runner helper to accept 'readonly ReadinessProbe[]'. Test behavior is unchanged.

src/core/readiness/orchestrator.test.ts

orchestrator.test.tsSnapshot capture tests accept readonly source lists +1/-1

Snapshot capture tests accept readonly source lists

• Updates the capture runner helper to accept 'readonly SnapshotSource[]'. Test behavior is unchanged.

src/core/snapshot/orchestrator.test.ts

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 467cc76a-5f7f-49e4-a660-0c9835dd7a68

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 34 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/core/snapshot/sources/appleListing.ts">

<violation number="1" location="src/core/snapshot/sources/appleListing.ts:52">
P3: This new `isJsonObject` type guard is byte-for-byte identical to the `isJsonRecord` guard being added in `src/core/snapshot/sources/playRestore.ts` (same body: typeof-object, null, Array.isArray checks, same `Readonly<{ [key: string]: JsonValue }>` predicate, added in this same batch). Both files now maintain duplicate narrowing logic. Since `playRestore.ts` already exports narrow-useful helpers (`jsonRecord`, `stringField`) and `appleListing.ts` has its own `fieldString`, this guard could live once in a shared module (or appleListing could reuse `jsonRecord`) so the two sources don't drift if the JsonValue shape or the guard's null/array handling ever changes.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

.map(([locale, fields]) => toEntity(locale, fields));
});
/** Narrow a captured {@link JsonValue} to a plain object (rejecting arrays and null). */
const isJsonObject = (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This new isJsonObject type guard is byte-for-byte identical to the isJsonRecord guard being added in src/core/snapshot/sources/playRestore.ts (same body: typeof-object, null, Array.isArray checks, same Readonly<{ [key: string]: JsonValue }> predicate, added in this same batch). Both files now maintain duplicate narrowing logic. Since playRestore.ts already exports narrow-useful helpers (jsonRecord, stringField) and appleListing.ts has its own fieldString, this guard could live once in a shared module (or appleListing could reuse jsonRecord) so the two sources don't drift if the JsonValue shape or the guard's null/array handling ever changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/core/snapshot/sources/appleListing.ts, line 52:

<comment>This new `isJsonObject` type guard is byte-for-byte identical to the `isJsonRecord` guard being added in `src/core/snapshot/sources/playRestore.ts` (same body: typeof-object, null, Array.isArray checks, same `Readonly<{ [key: string]: JsonValue }>` predicate, added in this same batch). Both files now maintain duplicate narrowing logic. Since `playRestore.ts` already exports narrow-useful helpers (`jsonRecord`, `stringField`) and `appleListing.ts` has its own `fieldString`, this guard could live once in a shared module (or appleListing could reuse `jsonRecord`) so the two sources don't drift if the JsonValue shape or the guard's null/array handling ever changes.</comment>

<file context>
@@ -48,8 +48,20 @@ const captureListing = (
       .map(([locale, fields]) => toEntity(locale, fields));
   });
+/** Narrow a captured {@link JsonValue} to a plain object (rejecting arrays and null). */
+const isJsonObject = (
+  capturedNode: JsonValue,
+): capturedNode is Readonly<{ [key: string]: JsonValue }> => {
</file context>

@YosefHayim

Copy link
Copy Markdown
Owner Author

Superseded by land of tip stack #386 (same 12 domain commits).

@YosefHayim YosefHayim closed this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant