Repository: angular
Issue: feat(signal): better guardrails to avoid wrong signal object mutations
Labels: area: core,core: reactivity,cross-cutting: signals

### Which @angular/* package(s) are relevant/related to the feature request?

core

### Description

Hey! 

I'm aware that the "not reactive" mutation of signal-objects is a known tradeoff (see for example https://github.com/angular/angular/issues/58440). Nevertheless, I wonder if there's really no way to mitigate the potential problems coming from it by throwing some sort of errors (at least at dev time). 

Here is a minimal example: 
```ts
@Injectable({
  providedIn: 'root',
})
export class Store {
  private readonly state = signal({ value: 0 });

  readonly counter = computed(() => this.state());
  readonly frozenCounter = computed(() => Object.freeze(this.state()));

  increase() {
    this.state.update((v) => ({ ...v, value: v.value + 1 }));
  }

  decrease() {
    this.state.update((v) => ({ ...v, value: v.value - 1 }));
  }
}

@Component({
  template: `
    <h3>App</h3>
    <p>Computed: {{ value() }}</p>
    <p>Store: {{ store.counter().value }}</p>
  `,
})
export class App {
  protected readonly store = inject(Store);
  protected readonly value = computed(() => this.store.counter().value);

  increase() {
    // this.store.increase(); // ✅ correct
    this.store.counter().value++; // ❌ wrong, but no error
    // this.store.frozenCounter().value++; // ✅ fine: throws an error
  }

  decrease() {
    // this.store.decrease(); // ✅ correct
    this.store.counter().value--; // ❌ wrong, but no error
    // this.store.frozenCounter().value--; // ✅ fine: throws an error
  }
}
```
https://stackblitz.com/edit/stackblitz-starters-bqez6f1y

A service acting as a store is just a popular use case, but essentially any signal having an object is potentially affected by the same problem. In all such cases, exposing / passing `computeds` or `readonly` signals don't make 100% sure the state cannot be accidentally mutated. At least, I'm not aware of any "ng-way" to achieve it. 

So I wonder: would be freezing / deepFreezing signals something the team has considered? For example as an opt-in in dev mode... [as recently done by ngrx-store team](https://github.com/ngrx/platform/blob/main/CHANGELOG.md#breaking-changes-1). Alternatively, any other suggestion in userland to address the problem? 

I understand this might favour a more "strictly immutable approach" to the data flow... and in general, it might make the signal implementation more complicated. 

I hope I haven't missed anything trivial (in case sorry). 
Thanks a lot! 

### Proposed solution

That I'm aware of: 
- Opt-in freeze / deepFreeze for all signals (at least in dev mode) + runtime errors, 
- expose a `read` / `transform` / ... option for signal / computed and cherry-pick the cases to freeze: `const state = signal({ value: 0 }, {read: Object.freeze})` + runtime errors, 
- purely at ts level (likely impossible => breaking change + I'm for sure missing something else 😅): 
```ts
// freeze case, deepFreeze likely more complicated (if DeepReadonly possible 😅)
type Readonly<T> = { readonly [P in keyof T]: T[P] };

declare function computed<T>(computation: () => T, options?: CreateComputedOptions<T>): Signal<Readonly<T>>;
```