Skip to content

refactor(credentials): readonly-domain-types (stack 7/12, re-split #307) - #380

Closed
YosefHayim wants to merge 1 commit into
refactor/types/readonly-stack-06-buildfrom
refactor/types/readonly-stack-07-credentials
Closed

refactor(credentials): readonly-domain-types (stack 7/12, re-split #307)#380
YosefHayim wants to merge 1 commit into
refactor/types/readonly-stack-06-buildfrom
refactor/types/readonly-stack-07-credentials

Conversation

@YosefHayim

@YosefHayim YosefHayim commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Stack 7/12 of re-split HOLD #307

Domain: credentials
Base: refactor/types/readonly-stack-06-build
Full green tip: refactor/foundation/readonly-types-full

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


Summary by cubic

Refactor the credentials domain to adopt readonly types and safer immutability. No behavior changes; updates are type-focused to align with the readonly stack.

  • Refactors
    • Removed Schema.mutable usage; schemas now define immutable structures for accounts and apps arrays.
    • Updated function params to readonly arrays (e.g., extensions, apps, accounts, containers, readiness).
    • Used MutableDeep when constructing records (AccountRecord, SigningAssets, ApnsKeyRecord) to keep write paths mutable while exposing readonly types.
    • Added defensive array copies when persisting (apps, extensions).
    • Adjusted tests to pass readonly arrays.

Written for commit f2f3c9c. 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: f2f3c9c

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 f2f3c9c 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:M This PR changes 30-99 lines, ignoring generated files label Aug 7, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

refactor(credentials): adapt consumers to readonly domain types

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Switch credentials consumers to accept/return readonly arrays and readonly domain records.
• Remove mutable Effect Schema wrappers where types are now readonly-compatible.
• Use MutableDeep + defensive array copies when constructing/updating domain records.
Diagram

graph TD
  A["Build/CLI"] --> B["accounts.ts"] --> G["credentials types"]
  A --> C["appleSigning.ts"] --> G
  A --> D["pushKeyStore.ts"] --> G
  A --> E["signingPreflight.ts"] --> F["capabilities.ts"] --> G
  B --> H["MutableDeep util"]
  C --> H
  D --> H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Add domain constructors (factory functions) in credentials types module
  • ➕ Centralizes readonly-safe construction logic (no MutableDeep at call sites)
  • ➕ Reduces repeated array-copy patterns (e.g., apps/extensions)
  • ➕ Makes invariants explicit (e.g., when to set resolvedAt/extensionProfiles)
  • ➖ Introduces new API surface area and migration work across the stack
  • ➖ Harder to keep factories minimal while the readonly migration is in-flight
2. Keep Schema.mutable and convert to readonly at boundaries (mapping step)
  • ➕ Minimizes changes to runtime decoding behavior and existing Schema patterns
  • ➕ Limits type churn inside consumers
  • ➖ Adds extra conversion code (and potentially runtime cost) everywhere decoded data is used
  • ➖ Still requires careful deep-copying to avoid mutation leaking into readonly types

Recommendation: Current approach (propagate readonly arrays + use MutableDeep only at mutation boundaries, copying arrays when assigning) is the right fit for a staged readonly migration: it keeps changes local, preserves functional behavior, and makes mutation points explicit. Consider introducing shared factory helpers later if MutableDeep usage proliferates across many domains.

Files changed (6) +35 / -34

Refactor (5) +33 / -32
accounts.tsMake account registry schemas and APIs readonly-compatible +22/-25

Make account registry schemas and APIs readonly-compatible

• Removes Schema.mutable wrappers for AccountRecord/AccountsFile decoding and updates several function signatures to accept readonly AccountRecord arrays. Uses MutableDeep when building/updating records and copies apps arrays to avoid retaining mutable references under readonly types.

src/core/credentials/accounts.ts

appleSigning.tsMake cached signing asset loader accept readonly extensions +3/-2

Make cached signing asset loader accept readonly extensions

• Changes loadCachedSigningAssets to take readonly extensions and uses MutableDeep for incremental construction of SigningAssets, preserving conditional extensionProfiles assignment.

src/core/credentials/appleSigning.ts

capabilities.tsAccept readonly container id lists for portal notice generation +1/-1

Accept readonly container id lists for portal notice generation

• Updates appGroupPortalNotice to take a readonly string array, aligning with readonly domain typing and avoiding unnecessary mutability requirements.

src/core/credentials/capabilities.ts

pushKeyStore.tsConstruct APNs key records via MutableDeep under readonly types +2/-1

Construct APNs key records via MutableDeep under readonly types

• Uses MutableDeep when creating ApnsKeyRecord values during import, keeping the exported domain type readonly while allowing incremental field assignment in the implementation.

src/core/credentials/pushKeyStore.ts

signingPreflight.tsPropagate readonly arrays through signing preflight inputs +5/-3

Propagate readonly arrays through signing preflight inputs

• Updates extension bundle id handling and warning generation to accept readonly arrays, ensuring signing preflight functions compose cleanly with readonly config/domain data.

src/core/credentials/signingPreflight.ts

Tests (1) +2 / -2
appleSigning.test.tsAdjust test helpers to accept readonly arrays +2/-2

Adjust test helpers to accept readonly arrays

• Updates test helper parameters (installed UUIDs and capability types) to readonly string arrays to match the new readonly-facing APIs.

src/core/credentials/appleSigning.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

};
if (input.teamId !== null && input.teamId !== undefined) record.teamId = input.teamId;
if (input.apps !== undefined && input.apps.length > 0) record.apps = input.apps;
if (input.apps !== undefined && input.apps.length > 0) record.apps = [...input.apps];

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: When an existing account is re-imported after identity resolution returns an empty app list—for example, a transient Apple lookup failure—the rebuilt record omits apps instead of preserving the previously cached list. This silently discards usable account metadata and can make a previously resolved account appear unresolved; preserve the existing list when the new identity result is unavailable, or distinguish a successful empty result from a failed lookup. [stale reference]

Severity Level: Major ⚠️
- ⚠️ Account summaries lose previously cached application names.
- ⚠️ Credential status and account pickers lose useful metadata.

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/credentials/accounts.ts
**Line:** 216:216
**Comment:**
	*Stale Reference: When an existing account is re-imported after identity resolution returns an empty app list—for example, a transient Apple lookup failure—the rebuilt record omits `apps` instead of preserving the previously cached list. This silently discards usable account metadata and can make a previously resolved account appear unresolved; preserve the existing list when the new identity result is unavailable, or distinguish a successful empty result from a failed lookup.

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
👍 | 👎

Comment on lines +239 to +241
let next: AccountRecord = { ...account, resolvedAt: timestamp };
if (teamId != null) next = { ...next, teamId };
if (apps.length > 0) next = { ...next, apps: [...apps] };

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: An identity refresh with apps = [] advances resolvedAt but leaves the old app list untouched. Since the identity service converts app-list lookup failures to an empty array, revoked access or a failed lookup can permanently display stale applications while marking the account as freshly resolved. Replace the cached list on a successful refresh, and separately represent lookup failure if stale data must be retained. [cache]

Severity Level: Major ⚠️
- ⚠️ Credential refresh can display stale application access.
- ⚠️ `resolvedAt` falsely indicates current identity data.

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/credentials/accounts.ts
**Line:** 239:241
**Comment:**
	*Cache: An identity refresh with `apps = []` advances `resolvedAt` but leaves the old app list untouched. Since the identity service converts app-list lookup failures to an empty array, revoked access or a failed lookup can permanently display stale applications while marking the account as freshly resolved. Replace the cached list on a successful refresh, and separately represent lookup failure if stale data must be retained.

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
👍 | 👎

keyId: string,
bundleId: string,
extensions: string[] = [],
extensions: 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 default empty extension list allows callers that omit extensions—notably the re-sign flow—to receive cached assets without validating or returning profiles for embedded extensions. Re-signing an IPA with an extension can therefore leave the extension using its old profile/signature while only the main app is updated. Pass the artifact app's extension bundle IDs from the caller or require them explicitly. [api mismatch]

Severity Level: Major ⚠️
- ❌ Apple re-signing can use incomplete extension signing assets.
- ⚠️ Embedded extensions may retain incompatible signing metadata.

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/credentials/appleSigning.ts
**Line:** 88:88
**Comment:**
	*Api Mismatch: The default empty extension list allows callers that omit `extensions`—notably the re-sign flow—to receive cached assets without validating or returning profiles for embedded extensions. Re-signing an IPA with an extension can therefore leave the extension using its old profile/signature while only the main app is updated. Pass the artifact app's extension bundle IDs from the caller or require them explicitly.

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
👍 | 👎

extensions: readonly string[],
entitlements: Record<string, unknown> | undefined,
): Effect.Effect<TargetSigningReadiness[], unknown> => {
const required = mapEntitlementsToCapabilities(entitlements).enable;

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: Extensions are always assigned an empty required-capability set, so preflight only detects whether an extension App ID is registered and never reports capabilities missing from that extension. An extension that requires, for example, push notifications or another entitlement not present on the main app will pass preflight and fail later during provisioning or export. Gather each target's entitlements and map its own required capabilities. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Extension capability gaps escape preflight warnings.
- ❌ Provisioning can fail later during archive or export.

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/credentials/signingPreflight.ts
**Line:** 65:68
**Comment:**
	*Incomplete Implementation: Extensions are always assigned an empty required-capability set, so preflight only detects whether an extension App ID is registered and never reports capabilities missing from that extension. An extension that requires, for example, push notifications or another entitlement not present on the main app will pass preflight and fail later during provisioning or export. Gather each target's entitlements and map its own required capabilities.

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
👍 | 👎

@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: afa1cf41-24b3-4f0e-b87b-c7ec7bc6b32a

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.

No issues found across 6 files

Re-trigger cubic

@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:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant