# #1294 Phase 1 Aggregate Authoring Gate Design

**Issue:** `#1294`

**Goal:** Reject new formula authoring that declares cross-child aggregates while keeping the existing runtime aggregate compatibility path intact until Phase 2 and Phase 3 remove it.

## Scope

Phase 1 changes only authoring admission. It does not delete the aggregate parser/compiler/runtime path.

In scope:

- metadata field create
- metadata field batch create
- metadata field update when the formula shape is being changed
- formula validate API used by Studio
- source-backed pack validate/install
- source-backed pack apply as a defense-in-depth write gate

Out of scope:

- removing `AggregateExpr`
- removing aggregate compiler lowering
- removing aggregate view-builder support
- migrating existing persisted aggregate formulas
- tenant overlay authoring paths

## Authoritative Rule

Cross-child aggregation belongs to rollup authoring, not formula authoring.

- Reject: `SUM(Child.Amount)`, `COUNT(Child.*)`, `AVG(Child.Amount)`, `SUM(Child.Amount, ParentId)`
- Allow: scalar formula functions such as `MIN(a, b)` and `MAX(a, b)`
- Allow for now: existing persisted aggregate formulas at runtime

The rule must come from the real formula AST, not string matching.

## Single Read Path

Add a shared analytics-level helper that:

1. Parses a candidate formula with the existing DSL parser
2. Detects `AggregateExpr` nodes using the current AST walk
3. Returns a stable error describing that cross-child aggregates must be declared as `rollupsummary` / `RollupConfig`

Every Phase 1 admission gate delegates to this helper. No second implementation based on regex or ad hoc parser branching is allowed.

## Entry-Point Behavior

### Metadata create and batch create

New formula fields or formula-bearing writes must fail closed if the formula AST contains any `AggregateExpr`.

### Metadata update

Do not reject label/help-text-only edits on already persisted aggregate formulas.

Reject only when the update changes the formula authoring shape, meaning at least one of these facts changes:

- `Formula`
- `FormulaMode`
- `ReturnType`
- `Type`

That keeps Phase 1 aligned with the issue contract: block new authoring without breaking harmless metadata maintenance on legacy rows.

### Formula validate API

`POST /metadata/formula-views/:objectName/validate` must return `valid=false` for aggregate formulas so Studio stops teaching the old contract.

The schema-aware validation entry point is `FormulaViewSvc.ValidateFormulaForField(...)`, so the new gate belongs there.

### Source-backed pack validate/install

`ValidateInstallablePackSource(...)` must emit a pack diagnostic for aggregate formulas declared in object specs.

### Source-backed pack apply

Even though install currently runs validate first, apply must also reject aggregate formulas in `buildPackFieldSpecWriteWithResolver(...)` so a future caller cannot bypass the validate gate and still persist the wrong shape.

## Error Contract

Use one consistent message family across metadata and analytics:

- cross-child aggregate formulas are not supported for authoring
- declare a `rollupsummary` field with `RollupConfig` instead

Pack validation uses its own diagnostic code, but the message should point to the same remedy.

## Tests

Required coverage:

- create rejects aggregate formulas
- batch create rejects aggregate formulas
- update rejects aggregate formula authoring changes
- update still allows presentation-only edits on legacy aggregate formulas
- formula validate rejects aggregate formulas
- pack validate rejects aggregate formulas
- pack apply rejects aggregate formulas even if validate is bypassed

Runtime aggregate tests stay in place in Phase 1 because the compatibility path remains intentionally live.
