Skip to content

refactor(types): readonly-domain-types (stack 1/12, re-split #307) - #374

Merged
YosefHayim merged 1 commit into
mainfrom
refactor/types/readonly-stack-01-types
Aug 7, 2026
Merged

refactor(types): readonly-domain-types (stack 1/12, re-split #307)#374
YosefHayim merged 1 commit into
mainfrom
refactor/types/readonly-stack-01-types

Conversation

@YosefHayim

@YosefHayim YosefHayim commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Stack 1/12 of re-split HOLD #307

Domain: types
Base: main
Full green tip: refactor/foundation/readonly-types-full

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


Summary by cubic

Make core domain types readonly to enforce immutability and prevent accidental mutations. Adds a MutableDeep utility for rare, intentional mutation; no runtime behavior changes.

  • Refactors

    • Wrapped DTOs in Readonly<...> and arrays in readonly T[] across core types (catalog, store surfaces, plans, providers, artifacts, listings, credentials, dashboards, readiness, reconcile, etc.).
    • Tightened discriminated unions and map-like shapes to be read-only; made PlannedAction.description and PlannedAction.destructive readonly.
    • Added MutableDeep<Type> helper (src/core/types/mutable.ts) to opt into deep mutability when needed.
    • Small type cleanups (e.g., removed empty intersections like & {}).
  • Migration

    • Stop mutating shared DTOs; create copies instead (e.g., via spreads).
    • If mutation is required (tests/migrations), use MutableDeep<T> or copy into a mutable local.
    • Update function signatures and call sites to accept readonly arrays and avoid mutating methods.

Written for commit a2c2a7d. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a2c2a7d

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 a2c2a7d Aug 07, 2026 · 11:06 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:XL This PR changes 500-999 lines, ignoring generated files label Aug 7, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

refactor(types): make core domain type shapes readonly + add MutableDeep helper

✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Convert core domain types to Readonly/readonly arrays to enforce immutability by default.
• Tighten read-only API surfaces (ASC/Play) by returning readonly collections.
• Add MutableDeep helper for explicit opt-in deep mutation where necessary.
Diagram

graph TD
  A["Commands (plan/readiness/snapshot/adopt)"] --> B["Store clients (ASC/Play)"] --> C["API slice types"] --> D["Readonly domain types"] --> E["JSON/plan/report outputs"]
  F["MutableDeep helper"] --> D
  subgraph Legend
    direction LR
    _cmd["Command"] ~~~ _api["API surface"] ~~~ _type["Readonly types"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Introduce a ReadonlyDeep/DeepReadonly helper type instead of per-type Readonly
  • ➕ Less repetitive wrapping across many domain types
  • ➕ Easier to apply consistently to nested object graphs
  • ➖ Can be harder to reason about and debug in error messages
  • ➖ May accidentally over-constrain types that intentionally model mutable records (e.g., builder patterns)
2. Keep types mutable and enforce immutability via lint/runtime discipline
  • ➕ Minimal type churn and fewer downstream compilation cascades
  • ➕ Avoids readonly-array variance changes in function signatures
  • ➖ Does not enforce the read-only design intent at API boundaries
  • ➖ Mutable-by-default increases accidental mutation risk in planning/reporting flows

Recommendation: The PR’s approach (Readonly/readonly at the boundary types, plus a single MutableDeep escape hatch) is the most robust for enforcing read-only invariants in the plan/readiness/snapshot/adopt flows. Reviewers should verify that any code needing mutation now performs explicit copies or uses MutableDeep locally, rather than weakening the public types.

Files changed (27) +805 / -776

Enhancement (1) +7 / -0
mutable.tsAdd MutableDeep type utility +7/-0

Add MutableDeep type utility

• Introduces MutableDeep<Type>, which recursively removes readonly modifiers (including readonly arrays) while preserving function signatures. This provides an explicit, localized opt-in for cases that need to build/mutate structures derived from readonly domain types.

src/core/types/mutable.ts

Refactor (26) +798 / -776
adopt.tsMake adopt API and write-plan domain shapes readonly +36/-30

Make adopt API and write-plan domain shapes readonly

• Wraps adopt-domain types (EntitlementValue object variant, AdoptCatalogApi, AdoptTarget, ProductPiece, AdoptChange, PlannedWrite) in Readonly and converts returned collections to readonly arrays. This enforces the design intent that adopt reads ASC state and only writes local config, without mutating domain objects.

src/core/types/adopt.ts

agents.tsMake agent skill/rule domain types readonly +33/-33

Make agent skill/rule domain types readonly

• Converts SkillStep, ConsumerSkill, CommandMapRow, Guardrail, BaseContext, ContributorRule, ContributorSkill, and GeneratedAgentFile to Readonly shapes and readonly arrays. This stabilizes generated agent artifacts and prevents accidental mutation during rendering/generation.

src/core/types/agents.ts

appleCatalog.tsReadonly App Store Connect catalog resource types +179/-178

Readonly App Store Connect catalog resource types

• Marks ASC resource and input types as Readonly and changes array properties to readonly arrays (e.g., roles, settings, prices). Also tightens nested payloads like beta feedback screenshots to readonly structures to better reflect wire immutability.

src/core/types/appleCatalog.ts

artifacts.tsReadonly artifact metadata and prune result types +11/-11

Readonly artifact metadata and prune result types

• Makes DiscoveredTarget, PrunedArtifact, PruneOptions, PruneResult, and StoredArtifact readonly, and converts pruned lists to readonly arrays. This helps keep storage-provider results immutable across reporting and pruning flows.

src/core/types/artifacts.ts

catalog.tsReadonly product catalog config types +69/-66

Readonly product catalog config types

• Converts product-catalog configuration types (localizations, offers, subscriptions, IAPs, promoted purchases, and AppProducts) to Readonly shapes with readonly arrays. Nested objects (e.g., monthsSinceLastSubscribed) are also made Readonly for consistent immutability.

src/core/types/catalog.ts

commandDocs.tsReadonly command-doc generation types +13/-13

Readonly command-doc generation types

• Wraps doc-generation shapes (OptionSpec, CommandSpec, DocStats, GeneratedDoc, FeatureSection) in Readonly and converts options/subcommands/features lists to readonly arrays. This reinforces that generated docs are treated as immutable artifacts.

src/core/types/commandDocs.ts

config.tsReadonly Launch config types and arrays +15/-14

Readonly Launch config types and arrays

• Makes StorageConfig, LaunchConfig, LaunchConfigInput, and ResolvedBuildContext readonly; converts array fields like appRoots and envExclude to readonly arrays. This better reflects that resolved config/build context are immutable inputs into pipelines.

src/core/types/config.ts

credentials.tsReadonly credential record types and discriminated union cleanup +26/-24

Readonly credential record types and discriminated union cleanup

• Converts credential record types (AscKey, AccountRecord, AccountsFile, SigningAssets, AppleCredentials, KeystoreAssets, AndroidCredentials, ServiceAccount) to Readonly and updates list fields to readonly arrays. Also refactors BuildCredentials union to use an explicit Readonly discriminator object intersected with the credential payload type.

src/core/types/credentials.ts

dashboard.tsReadonly dashboard snapshot types +21/-21

Readonly dashboard snapshot types

• Makes dashboard payload types (apps, project wiring, accounts, artifacts, secrets, cloud host, overall state) Readonly and converts collection fields to readonly arrays. This stabilizes the dashboard data contract for rendering and JSON output.

src/core/types/dashboard.ts

doctor.tsReadonly doctor report types and API slice +25/-24

Readonly doctor report types and API slice

• Wraps DoctorCheck and DoctorReport in Readonly and converts checks to readonly arrays. Also makes DoctorAscApi and DoctorPlayApi Readonly API slices and updates DoctorContext apps to readonly arrays to enforce read-only inspection semantics.

src/core/types/doctor.ts

googlePlay.tsReadonly Google Play DTO/resource types +41/-41

Readonly Google Play DTO/resource types

• Converts Google Play types (tracks, releases, product/subscription resources, offers, reviews) to Readonly and switches repeated list fields to readonly arrays. Nested record entries are also made Readonly to prevent mutation of parsed API responses.

src/core/types/googlePlay.ts

insights.tsReadonly insights report domain types +12/-12

Readonly insights report domain types

• Marks ReviewDatum, RatingSummary, MonthlyRatingPoint, AppInsights, and InsightsReport as Readonly and converts trend/apps lists to readonly arrays. This matches the insights workflow as a pure synthesis step over review data.

src/core/types/insights.ts

listing.tsReadonly listing draft types +9/-9

Readonly listing draft types

• Converts ListingBrief, DraftListing, and LocaleDraft to Readonly and makes keywords/warnings lists readonly arrays. This keeps listing generation outputs immutable between preview and apply.

src/core/types/listing.ts

mcp.tsReadonly MCP protocol content/result shapes +9/-8

Readonly MCP protocol content/result shapes

• Marks McpTextContent and McpToolResult as Readonly and converts content to a readonly array. Also makes McpInputSchema a Readonly intersection to align with the protocol’s fixed object-type contract.

src/core/types/mcp.ts

migrate.tsReadonly migration report and parsed-source types +40/-40

Readonly migration report and parsed-source types

• Converts migration domain types (notes, artifacts, results, EAS/fastlane parsed shapes) to Readonly and uses readonly arrays for collections like notes, artifacts, lanes, and env keys. This clarifies the migration pipeline as a read/transform/write plan.

src/core/types/migrate.ts

plan.tsReadonly plan domain types and API union cleanup +20/-20

Readonly plan domain types and API union cleanup

• Removes redundant empty intersections in PlayCatalogApi and AscSurfacesApi unions. Converts AppPlan, SurfacePlan union members, PlanContext, and SurfacePlanner to Readonly and switches actions/apps lists to readonly arrays for immutable planning outputs.

src/core/types/plan.ts

playPricing.tsReadonly Play pricing normalization types +9/-9

Readonly Play pricing normalization types

• Marks pricing DTOs (PlayMoneyUnits, converted price structures) as Readonly and converts regions list to a readonly array. This better models parsed pricing responses as immutable data.

src/core/types/playPricing.ts

privacy.tsReadonly privacy scan types +11/-11

Readonly privacy scan types

• Converts PrivacyFinding, PrivacySurface, and PrivacyReport to Readonly and makes lists (collected data types, tracking domains, permissions, findings/scanned) readonly arrays. This aligns privacy scanning with a pure analysis/output contract.

src/core/types/privacy.ts

providers.tsReadonly provider interface contracts +21/-21

Readonly provider interface contracts

• Wraps provider interfaces (CredentialsProvider, BuildEngine, HostedBuildProvider, StorageProvider, Submitter, SecretStore, ComputeHost) in Readonly and tightens return collections (e.g., StorageProvider.list). This enforces provider boundaries as immutable contracts between pipeline and implementations.

src/core/types/providers.ts

readiness.tsReadonly readiness probe domain and API slices +61/-61

Readonly readiness probe domain and API slices

• Converts readiness domain types (AppReadiness, ProbeResult/Outcome, ProbeReport, ReadinessContext, ReadinessProbe, ReadinessOutcome) to Readonly with readonly arrays. Also makes AscReadinessApi/PlayReadinessApi Readonly and returns readonly item lists from ASC methods.

src/core/types/readiness.ts

reconcile.tsFreeze planned action description/destructive fields +9/-6

Freeze planned action description/destructive fields

• Documents PlannedAction semantics and makes description/destructive readonly to reflect that they are fixed at plan time. Wraps ReconcileReport in Readonly and converts actions to a readonly array to stabilize reconcile output payloads.

src/core/types/reconcile.ts

releaseTrain.tsReadonly release-train record types +7/-7

Readonly release-train record types

• Converts NativeCar, OtaCar, and TrainRecord to Readonly and makes the cars list readonly. This reinforces immutability of persisted train records between load/advance/save steps.

src/core/types/releaseTrain.ts

remote.tsReadonly remote host/SSH domain types +14/-14

Readonly remote host/SSH domain types

• Marks SshTarget, HostHandle, HostStatus, AwsConfig, RemoteTarget union members, and AllocateRequest as Readonly. This tightens the remote-build orchestration contract as immutable request/response data.

src/core/types/remote.ts

snapshot.tsReadonly snapshot capture/restore domain and API slices +58/-54

Readonly snapshot capture/restore domain and API slices

• Converts JsonValue object/array variants, snapshot entity/grouping types, capture unions, reports, and snapshot record to Readonly with readonly arrays. Also makes SnapshotAscApi/SnapshotPlayApi Readonly and tightens list-return types to readonly arrays for capture consistency.

src/core/types/snapshot.ts

storeSurface.tsReadonly store-surface config types +42/-42

Readonly store-surface config types

• Converts store-surface config types (release/notify, game center, clips, EU distribution, wallet, release attributes, surface config file overrides, MCP config) to Readonly and switches list-like fields to readonly arrays. This stabilizes config-as-code surfaces as immutable inputs.

src/core/types/storeSurface.ts

vitals.tsReadonly Play vitals query/result types +7/-7

Readonly Play vitals query/result types

• Marks PlayVitalsRow, VitalsWindow, and VitalsTimeline as Readonly and converts rows to a readonly array. This aligns vitals reporting with an immutable, normalized time-series output.

src/core/types/vitals.ts

Comment thread src/core/types/migrate.ts
platform?: string;
actions: string[];
};
actions: readonly string[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The new readonly array type makes beta.actions.sort() invalid because sort() mutates its receiver. The existing migration test and any similar callers must copy the array before sorting, or this contract should remain mutable where callers are expected to sort in place. [type error]

Severity Level: Major ⚠️
- ❌ Fastlane migration tests fail TypeScript checking.
- ⚠️ Migration CI cannot validate lane parsing behavior.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/types/migrate.ts
**Line:** 122:122
**Comment:**
	*Type Error: The new readonly array type makes `beta.actions.sort()` invalid because `sort()` mutates its receiver. The existing migration test and any similar callers must copy the array before sorting, or this contract should remain mutable where callers are expected to sort in place.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

readonly name: string;
put(artifact: BuildArtifact): Effect.Effect<StoredArtifact, unknown>;
list(): Effect.Effect<BuildArtifact[], unknown>;
list(): Effect.Effect<readonly BuildArtifact[], unknown>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: StorageProvider.list() now returns readonly BuildArtifact[], but existing callers pass its result directly to findBuild and filterBuilds, whose parameters are BuildArtifact[]. This makes those build-history and run-command call sites type-incompatible until they are updated to accept readonly arrays or copy the result. [api mismatch]

Severity Level: Critical 🚨
- ❌ Build-history typechecking fails in multiple commands.
- ❌ Run and resign artifact selection cannot compile.
- ⚠️ Release commands share the same incompatible history type.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/types/providers.ts
**Line:** 96:96
**Comment:**
	*Api Mismatch: `StorageProvider.list()` now returns `readonly BuildArtifact[]`, but existing callers pass its result directly to `findBuild` and `filterBuilds`, whose parameters are `BuildArtifact[]`. This makes those build-history and run-command call sites type-incompatible until they are updated to accept readonly arrays or copy the result.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

export type SnapshotContext = Readonly<{
config: LaunchConfig;
apps: AppDescriptor[];
apps: readonly AppDescriptor[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: SnapshotContext.apps and RestoreContext.apps are now readonly arrays, but the existing iosApps and androidApps helpers require mutable AppDescriptor[]. Snapshot sources pass these context arrays directly to those helpers, so capture and restore code becomes type-incompatible until the helpers accept readonly arrays or callers copy the arrays. [api mismatch]

Severity Level: Critical 🚨
- ❌ Apple snapshot capture sources fail typechecking.
- ❌ Google Play snapshot capture sources fail typechecking.
- ⚠️ Snapshot capture command compilation is blocked.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/types/snapshot.ts
**Line:** 168:168
**Comment:**
	*Api Mismatch: `SnapshotContext.apps` and `RestoreContext.apps` are now readonly arrays, but the existing `iosApps` and `androidApps` helpers require mutable `AppDescriptor[]`. Snapshot sources pass these context arrays directly to those helpers, so capture and restore code becomes type-incompatible until the helpers accept readonly arrays or callers copy the arrays.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Mutating readonly actions 🐞 Bug ≡ Correctness
Description
ReconcileReport.actions is now a readonly array, but reconcileJob still does
report.actions.push(...), which is a TypeScript error and prevents the current code from
compiling. This also violates the intended “immutable report” contract by mutating a report object
after creation.
Code

src/core/types/reconcile.ts[R13-16]

+export type ReconcileReport = Readonly<{
  bundleId: string;
-  actions: PlannedAction[];
-};
+  actions: readonly PlannedAction[];
+}>;
Evidence
The PR makes ReconcileReport.actions a readonly array, but the current implementation still
appends to it with push, which is illegal on readonly arrays in TypeScript.

src/core/types/reconcile.ts[13-16]
src/core/store/syncRun.ts[157-191]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ReconcileReport.actions` was changed to `readonly PlannedAction[]`, but `src/core/store/syncRun.ts` mutates that array with `push`. This becomes a TypeScript compilation error and breaks the intended immutable-domain boundary.

### Issue Context
`reconcileJob` currently computes a `ReconcileReport` via `reconcileApp(...)` and then appends asset actions by mutating `report.actions`.

### Fix Focus Areas
- src/core/types/reconcile.ts[12-16]
- src/core/store/syncRun.ts[157-193]

### Suggested fix
Prefer keeping `ReconcileReport` immutable:
- In `reconcileJob`, replace the in-place `push` with a new report object:
 - `const assetActions = yield* reconcileAssetActions(...)`
 - `const mergedReport: ReconcileReport = { ...report, actions: [...report.actions, ...assetActions] }`
 - return `{ job, report: mergedReport }`

(Alternative: if you want an internal mutable builder, introduce a separate mutable “builder” type and only expose `ReconcileReport` at the boundary.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Readonly arrays still mutable 🐞 Bug ⚙ Maintainability
Description
Some newly-Readonly domain types still declare nested arrays as mutable (Readonly<T>[]), so
callers can still push/splice these arrays even though the containing object is Readonly. This
undermines the readonly-domain guarantee and makes it easier to accidentally mutate shared data
structures.
Code

src/core/types/appleCatalog.ts[R42-47]

+export type CapabilitySetting = Readonly<{
  key: string;
-  options?: {
+  options?: Readonly<{
    key: string;
-  }[];
-};
+  }>[];
+}>;
Evidence
CapabilitySetting is wrapped in Readonly, but options is still a mutable array type, while a
neighboring array field (settings) was correctly converted to readonly ...[], showing
inconsistency. Similar mutable-array patterns exist in other newly-readonly types like Play country
availability and beta feedback screenshots.

src/core/types/appleCatalog.ts[42-53]
src/core/types/appleCatalog.ts[234-241]
src/core/types/googlePlay.ts[3-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A few types were converted to `Readonly<...>` but still expose mutable arrays via `Readonly<Element>[]`. That means the property is non-reassignable, but the array contents can still be mutated.

### Issue Context
This PR otherwise consistently switches many array properties to `readonly X[]`, suggesting the intent is deep immutability for domain types.

### Fix Focus Areas
- src/core/types/appleCatalog.ts[42-53]
- src/core/types/appleCatalog.ts[234-241]
- src/core/types/googlePlay.ts[3-14]

### Suggested fix
Change these fields to readonly arrays, e.g.:
- `options?: readonly { key: string }[]` (or `readonly Readonly<{ key: string }>[]`)
- `screenshots: readonly { url: string; width?: number; height?: number }[]`
- `releaseNotes?: readonly { language: string; text: string }[]`
- `countries: readonly { countryCode: string }[]`

Keep the element type `Readonly<...>` only if you specifically want element objects to be non-writable too.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 48 rules

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

Qodo Logo

Comment on lines +13 to +16
export type ReconcileReport = Readonly<{
bundleId: string;
actions: PlannedAction[];
};
actions: readonly PlannedAction[];
}>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Mutating readonly actions 🐞 Bug ≡ Correctness

ReconcileReport.actions is now a readonly array, but reconcileJob still does
report.actions.push(...), which is a TypeScript error and prevents the current code from
compiling. This also violates the intended “immutable report” contract by mutating a report object
after creation.
Agent Prompt
### Issue description
`ReconcileReport.actions` was changed to `readonly PlannedAction[]`, but `src/core/store/syncRun.ts` mutates that array with `push`. This becomes a TypeScript compilation error and breaks the intended immutable-domain boundary.

### Issue Context
`reconcileJob` currently computes a `ReconcileReport` via `reconcileApp(...)` and then appends asset actions by mutating `report.actions`.

### Fix Focus Areas
- src/core/types/reconcile.ts[12-16]
- src/core/store/syncRun.ts[157-193]

### Suggested fix
Prefer keeping `ReconcileReport` immutable:
- In `reconcileJob`, replace the in-place `push` with a new report object:
  - `const assetActions = yield* reconcileAssetActions(...)`
  - `const mergedReport: ReconcileReport = { ...report, actions: [...report.actions, ...assetActions] }`
  - return `{ job, report: mergedReport }`

(Alternative: if you want an internal mutable builder, introduce a separate mutable “builder” type and only expose `ReconcileReport` at the boundary.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +42 to +47
export type CapabilitySetting = Readonly<{
key: string;
options?: {
options?: Readonly<{
key: string;
}[];
};
}>[];
}>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Readonly arrays still mutable 🐞 Bug ⚙ Maintainability

Some newly-Readonly domain types still declare nested arrays as mutable (Readonly<T>[]), so
callers can still push/splice these arrays even though the containing object is Readonly. This
undermines the readonly-domain guarantee and makes it easier to accidentally mutate shared data
structures.
Agent Prompt
### Issue description
A few types were converted to `Readonly<...>` but still expose mutable arrays via `Readonly<Element>[]`. That means the property is non-reassignable, but the array contents can still be mutated.

### Issue Context
This PR otherwise consistently switches many array properties to `readonly X[]`, suggesting the intent is deep immutability for domain types.

### Fix Focus Areas
- src/core/types/appleCatalog.ts[42-53]
- src/core/types/appleCatalog.ts[234-241]
- src/core/types/googlePlay.ts[3-14]

### Suggested fix
Change these fields to readonly arrays, e.g.:
- `options?: readonly { key: string }[]` (or `readonly Readonly<{ key: string }>[]`)
- `screenshots: readonly { url: string; width?: number; height?: number }[]`
- `releaseNotes?: readonly { language: string; text: string }[]`
- `countries: readonly { countryCode: string }[]`

Keep the element type `Readonly<...>` only if you specifically want element objects to be non-writable too.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@YosefHayim, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: afd5be2a-9b6b-4c11-98bd-a77343867b17

📥 Commits

Reviewing files that changed from the base of the PR and between 40998a7 and a2c2a7d.

📒 Files selected for processing (27)
  • src/core/types/adopt.ts
  • src/core/types/agents.ts
  • src/core/types/appleCatalog.ts
  • src/core/types/artifacts.ts
  • src/core/types/catalog.ts
  • src/core/types/commandDocs.ts
  • src/core/types/config.ts
  • src/core/types/credentials.ts
  • src/core/types/dashboard.ts
  • src/core/types/doctor.ts
  • src/core/types/googlePlay.ts
  • src/core/types/insights.ts
  • src/core/types/listing.ts
  • src/core/types/mcp.ts
  • src/core/types/migrate.ts
  • src/core/types/mutable.ts
  • src/core/types/plan.ts
  • src/core/types/playPricing.ts
  • src/core/types/privacy.ts
  • src/core/types/providers.ts
  • src/core/types/readiness.ts
  • src/core/types/reconcile.ts
  • src/core/types/releaseTrain.ts
  • src/core/types/remote.ts
  • src/core/types/snapshot.ts
  • src/core/types/storeSurface.ts
  • src/core/types/vitals.ts

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.

12 issues found across 27 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/types/storeSurface.ts">

<violation number="1" location="src/core/types/storeSurface.ts:19">
P2: `NotifyConfig.events` remains mutable despite this readonly-domain conversion, allowing callers to mutate the notification policy through `push`, `splice`, or index assignment. Making this field a readonly array would keep the public config types consistent and prevent accidental policy mutation.</violation>
</file>

<file name="src/core/types/googlePlay.ts">

<violation number="1" location="src/core/types/googlePlay.ts:8">
P2: These types still let callers mutate nested Play data despite the new readonly-domain contract: `releaseNotes`, `countries`, `offerTags`, `prices`, and `listings` can be changed in place. Using `readonly ...[]` and `Readonly<Record<string, ...>>` for these fields would make the returned resources consistently immutable.</violation>
</file>

<file name="src/core/types/readiness.ts">

<violation number="1" location="src/core/types/readiness.ts:61">
P2: The read-only readiness API still exposes mutable result arrays, so a probe can `push`, `sort`, or `splice` a collection returned by the store layer despite the API's read-only boundary. Declaring each list result as `readonly Readonly<{ ... }>[]` would prevent accidental mutation and match the readonly collections elsewhere in this file.</violation>
</file>

<file name="src/core/types/insights.ts">

<violation number="1" location="src/core/types/insights.ts:26">
P2: Consumers can still mutate a returned insights report through `ratings.distribution[...]`, `ratings.sentiment[...]`, or `byStore[...]`, so the new readonly boundary is only partial. Using `Readonly<Record<...>>` for the summary maps and `Readonly<Partial<Record<...>>>` for `byStore` would keep mutation local to aggregation.</violation>
</file>

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

<violation number="1" location="src/core/types/snapshot.ts:110">
P3: The ASC snapshot reader still exposes mutable result arrays even though this type is introducing the read-only API boundary. Using `readonly Readonly<{ ... }>[]` for all four list results would prevent consumers from accidentally changing captured collections.</violation>
</file>

<file name="src/core/types/adopt.ts">

<violation number="1" location="src/core/types/adopt.ts:33">
P3: Entitlement lists remain mutable despite this type being converted to a readonly domain shape; using `readonly EntitlementValue[]` for the recursive array arm would prevent adopters from mutating imported entitlement data through the type.</violation>
</file>

<file name="src/core/types/appleCatalog.ts">

<violation number="1" location="src/core/types/appleCatalog.ts:44">
P3: Capability settings still expose a mutable `options` array, so consumers can mutate a value that this readonly-domain type is intended to protect. Mark the array itself readonly, consistent with `settings` and the other readonly collections in this file.</violation>

<violation number="2" location="src/core/types/appleCatalog.ts:236">
P3: Screenshot-feedback resources still expose a mutable `screenshots` array, weakening the readonly domain contract and allowing callers to alter the collection. Make the array readonly as well as its element type.</violation>
</file>

<file name="src/core/types/doctor.ts">

<violation number="1" location="src/core/types/doctor.ts:55">
P2: The doctor ASC surface still exposes a mutable capability list despite being documented as read-only, so callers can alter the returned domain collection. Mark the result array readonly as well.</violation>
</file>

<file name="src/core/types/mutable.ts">

<violation number="1" location="src/core/types/mutable.ts:2">
P3: The function branch re-emits `Return` without recursing, so MutableDeep is not actually deep for function/method return types: a method typed `() => Readonly<Foo>` stays `() => Readonly<Foo>`, and the produced "mutable" copy still fails when a caller mutates the returned value. Consider `(...args: Args) => MutableDeep<Return>` (guarding against self-referential returns) so the deep-mutability contract holds.</violation>

<violation number="2" location="src/core/types/mutable.ts:6">
P2: The `object` branch is unbounded and will also match class instances such as Date, Map, Set, RegExp, Promise, and Buffer, reducing them to plain `{ -readonly [Key in keyof ...] }` structural types. That strips their actual type identity and breaks downstream code expecting e.g. a `Map`/`Set`/`Date` value once MutableDeep is applied to a shape containing one. Constrain the guard to plain records (e.g. `Type extends Record<string | number | symbol, unknown>` after excluding arrays/indexables) or explicitly preserve known built-ins.</violation>
</file>

<file name="src/core/types/providers.ts">

<violation number="1" location="src/core/types/providers.ts:123">
P3: The Submitter doc comment now reads `Readonly<{@link BuildCredentials}>` and `Readonly<{@link SubmitTarget}>` — an artifact of a blanket find/replace that wraps the JSDoc `@link` tags in `Readonly<...>`. The `{@link ...}` references no longer resolve cleanly and the docs will render the literal `Readonly<`/`>` noise. Restore the plain `{@link BuildCredentials}` and `{@link SubmitTarget}` tags; the `Readonly` wrapper doesn't belong inside a doc comment.</violation>
</file>

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

Re-trigger cubic

Comment on lines +19 to +23
export type NotifyConfig = Readonly<{
webhookUrl?: string;
command?: string;
events?: Array<'build' | 'submit' | 'review' | 'rollout'>;
};
}>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: NotifyConfig.events remains mutable despite this readonly-domain conversion, allowing callers to mutate the notification policy through push, splice, or index assignment. Making this field a readonly array would keep the public config types consistent and prevent accidental policy mutation.

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

<comment>`NotifyConfig.events` remains mutable despite this readonly-domain conversion, allowing callers to mutate the notification policy through `push`, `splice`, or index assignment. Making this field a readonly array would keep the public config types consistent and prevent accidental policy mutation.</comment>

<file context>
@@ -7,22 +7,22 @@ import type {
+}>;
 /** Transition notifications under `LaunchConfig.notify`. */
-export type NotifyConfig = {
+export type NotifyConfig = Readonly<{
   webhookUrl?: string;
   command?: string;
</file context>
Suggested change
export type NotifyConfig = Readonly<{
webhookUrl?: string;
command?: string;
events?: Array<'build' | 'submit' | 'review' | 'rollout'>;
};
}>;
export type NotifyConfig = Readonly<{
webhookUrl?: string;
command?: string;
events?: readonly ('build' | 'submit' | 'review' | 'rollout')[];
}>;

@@ -1,77 +1,77 @@
import type { PlayMoneyUnits } from './playPricing.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: These types still let callers mutate nested Play data despite the new readonly-domain contract: releaseNotes, countries, offerTags, prices, and listings can be changed in place. Using readonly ...[] and Readonly<Record<string, ...>> for these fields would make the returned resources consistently immutable.

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

<comment>These types still let callers mutate nested Play data despite the new readonly-domain contract: `releaseNotes`, `countries`, `offerTags`, `prices`, and `listings` can be changed in place. Using `readonly ...[]` and `Readonly<Record<string, ...>>` for these fields would make the returned resources consistently immutable.</comment>

<file context>
@@ -1,77 +1,77 @@
-};
-export type PlayTrackInfo = { track: string; releases: PlayRelease[] };
-export type PlayCountryAvailability = {
+  releaseNotes?: Readonly<{ language: string; text: string }>[];
+}>;
+export type PlayTrackInfo = Readonly<{ track: string; releases: readonly PlayRelease[] }>;
</file context>

Readonly<{
id: string;
}[],
}>[],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The read-only readiness API still exposes mutable result arrays, so a probe can push, sort, or splice a collection returned by the store layer despite the API's read-only boundary. Declaring each list result as readonly Readonly<{ ... }>[] would prevent accidental mutation and match the readonly collections elsewhere in this file.

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

<comment>The read-only readiness API still exposes mutable result arrays, so a probe can `push`, `sort`, or `splice` a collection returned by the store layer despite the API's read-only boundary. Declaring each list result as `readonly Readonly<{ ... }>[]` would prevent accidental mutation and match the readonly collections elsewhere in this file.</comment>

<file context>
@@ -40,177 +40,177 @@ export type ProbeCheckResult = Effect.Effect<ProbeResult, unknown, ReadinessProb
+    Readonly<{
       id: string;
-    }[],
+    }>[],
     unknown
   >;
</file context>

* set so callers never divide by zero or branch on emptiness mid-render.
*/
export type RatingSummary = {
export type RatingSummary = Readonly<{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Consumers can still mutate a returned insights report through ratings.distribution[...], ratings.sentiment[...], or byStore[...], so the new readonly boundary is only partial. Using Readonly<Record<...>> for the summary maps and Readonly<Partial<Record<...>>> for byStore would keep mutation local to aggregation.

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

<comment>Consumers can still mutate a returned insights report through `ratings.distribution[...]`, `ratings.sentiment[...]`, or `byStore[...]`, so the new readonly boundary is only partial. Using `Readonly<Record<...>>` for the summary maps and `Readonly<Partial<Record<...>>>` for `byStore` would keep mutation local to aggregation.</comment>

<file context>
@@ -12,47 +12,47 @@ export type Sentiment = 'positive' | 'neutral' | 'negative';
  * set so callers never divide by zero or branch on emptiness mid-render.
  */
-export type RatingSummary = {
+export type RatingSummary = Readonly<{
   total: number;
   average: number;
</file context>

Comment thread src/core/types/doctor.ts
Comment on lines +55 to +60
listBundleIdCapabilities(bundleIdResourceId: string): Effect.Effect<
Readonly<{
capabilityType: string;
}>[],
unknown
>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The doctor ASC surface still exposes a mutable capability list despite being documented as read-only, so callers can alter the returned domain collection. Mark the result array readonly as well.

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

<comment>The doctor ASC surface still exposes a mutable capability list despite being documented as read-only, so callers can alter the returned domain collection. Mark the result array readonly as well.</comment>

<file context>
@@ -20,48 +20,49 @@ export type DoctorStatus = 'ok' | 'fail' | 'info';
+      }> | null,
+      unknown
+    >;
+    listBundleIdCapabilities(bundleIdResourceId: string): Effect.Effect<
+      Readonly<{
+        capabilityType: string;
</file context>
Suggested change
listBundleIdCapabilities(bundleIdResourceId: string): Effect.Effect<
Readonly<{
capabilityType: string;
}>[],
unknown
>;
listBundleIdCapabilities(bundleIdResourceId: string): Effect.Effect<
readonly Readonly<{
capabilityType: string;
}>[],
unknown
>;

Comment thread src/core/types/adopt.ts
| null
| EntitlementValue[]
| {
| Readonly<{

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: Entitlement lists remain mutable despite this type being converted to a readonly domain shape; using readonly EntitlementValue[] for the recursive array arm would prevent adopters from mutating imported entitlement data through the type.

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

<comment>Entitlement lists remain mutable despite this type being converted to a readonly domain shape; using `readonly EntitlementValue[]` for the recursive array arm would prevent adopters from mutating imported entitlement data through the type.</comment>

<file context>
@@ -30,104 +30,110 @@ export type EntitlementValue =
   | null
   | EntitlementValue[]
-  | {
+  | Readonly<{
       [key: string]: EntitlementValue;
-    };
</file context>

};
export type BetaFeedbackScreenshotSubmissionResource = BetaFeedbackSubmissionResource &
Readonly<{
screenshots: Readonly<{

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: Screenshot-feedback resources still expose a mutable screenshots array, weakening the readonly domain contract and allowing callers to alter the collection. Make the array readonly as well as its element type.

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

<comment>Screenshot-feedback resources still expose a mutable `screenshots` array, weakening the readonly domain contract and allowing callers to alter the collection. Make the array readonly as well as its element type.</comment>

<file context>
@@ -74,184 +74,185 @@ export type SandboxTesterResource = {
-};
+export type BetaFeedbackScreenshotSubmissionResource = BetaFeedbackSubmissionResource &
+  Readonly<{
+    screenshots: Readonly<{
+      url: string;
+      width?: number;
</file context>
Suggested change
screenshots: Readonly<{
screenshots: readonly Readonly<{

Comment on lines +44 to +46
options?: Readonly<{
key: string;
}[];
};
}>[];

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: Capability settings still expose a mutable options array, so consumers can mutate a value that this readonly-domain type is intended to protect. Mark the array itself readonly, consistent with settings and the other readonly collections in this file.

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

<comment>Capability settings still expose a mutable `options` array, so consumers can mutate a value that this readonly-domain type is intended to protect. Mark the array itself readonly, consistent with `settings` and the other readonly collections in this file.</comment>

<file context>
@@ -39,33 +39,33 @@ export type ProfileResource = {
+export type CapabilitySetting = Readonly<{
   key: string;
-  options?: {
+  options?: Readonly<{
     key: string;
-  }[];
</file context>
Suggested change
options?: Readonly<{
key: string;
}[];
};
}>[];
options?: readonly Readonly<{
key: string;
}>[];

Comment thread src/core/types/mutable.ts
@@ -0,0 +1,7 @@
export type MutableDeep<Type> = Type extends (...args: infer Args) => infer Return
? (...args: Args) => Return

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: The function branch re-emits Return without recursing, so MutableDeep is not actually deep for function/method return types: a method typed () => Readonly<Foo> stays () => Readonly<Foo>, and the produced "mutable" copy still fails when a caller mutates the returned value. Consider (...args: Args) => MutableDeep<Return> (guarding against self-referential returns) so the deep-mutability contract holds.

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

<comment>The function branch re-emits `Return` without recursing, so MutableDeep is not actually deep for function/method return types: a method typed `() => Readonly<Foo>` stays `() => Readonly<Foo>`, and the produced "mutable" copy still fails when a caller mutates the returned value. Consider `(...args: Args) => MutableDeep<Return>` (guarding against self-referential returns) so the deep-mutability contract holds.</comment>

<file context>
@@ -0,0 +1,7 @@
+export type MutableDeep<Type> = Type extends (...args: infer Args) => infer Return
+  ? (...args: Args) => Return
+  : Type extends readonly (infer Item)[]
+    ? MutableDeep<Item>[]
</file context>

Comment on lines +123 to +124
* submits to a Play track via fastlane `supply`. Each narrows Readonly<{@link BuildCredentials}> to its platform
* and maps the neutral Readonly<{@link SubmitTarget}> onto its store's concept (Android also reads

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: The Submitter doc comment now reads Readonly<{@link BuildCredentials}> and Readonly<{@link SubmitTarget}> — an artifact of a blanket find/replace that wraps the JSDoc @link tags in Readonly<...>. The {@link ...} references no longer resolve cleanly and the docs will render the literal Readonly</> noise. Restore the plain {@link BuildCredentials} and {@link SubmitTarget} tags; the Readonly wrapper doesn't belong inside a doc comment.

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

<comment>The Submitter doc comment now reads `Readonly<{@link BuildCredentials}>` and `Readonly<{@link SubmitTarget}>` — an artifact of a blanket find/replace that wraps the JSDoc `@link` tags in `Readonly<...>`. The `{@link ...}` references no longer resolve cleanly and the docs will render the literal `Readonly<`/`>` noise. Restore the plain `{@link BuildCredentials}` and `{@link SubmitTarget}` tags; the `Readonly` wrapper doesn't belong inside a doc comment.</comment>

<file context>
@@ -120,19 +120,19 @@ export type StorageProviderResolver = Readonly<{
  * `app-store-connect` submits to TestFlight/App Store via fastlane `pilot`/`deliver`; `google-play`
- * submits to a Play track via fastlane `supply`. Each narrows {@link BuildCredentials} to its platform
- * and maps the neutral {@link SubmitTarget} onto its store's concept (Android also reads
+ * submits to a Play track via fastlane `supply`. Each narrows Readonly<{@link BuildCredentials}> to its platform
+ * and maps the neutral Readonly<{@link SubmitTarget}> onto its store's concept (Android also reads
  * `buildContext.android`).
</file context>
Suggested change
* submits to a Play track via fastlane `supply`. Each narrows Readonly<{@link BuildCredentials}> to its platform
* and maps the neutral Readonly<{@link SubmitTarget}> onto its store's concept (Android also reads
* submits to a Play track via fastlane `supply`. Each narrows {@link BuildCredentials} to its platform
* and maps the neutral {@link SubmitTarget} onto its store's concept (Android also reads

@YosefHayim
YosefHayim merged commit a2c2a7d into main Aug 7, 2026
9 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant