# #1294 Phase 1 Aggregate Authoring Gate Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Block new cross-child aggregate formula authoring across metadata CRUD, Studio validate, and source-backed pack flows without removing the legacy runtime compatibility path.

**Architecture:** Add one shared AST-based aggregate-authoring validator in analytics, then delegate metadata, validate API, and pack validate/apply entry points to it. Preserve legacy runtime paths by rejecting only new authoring, and on update only when the formula shape actually changes.

**Tech Stack:** Go, Gin, NexusCRM metadata services, source-backed pack validation, Go tests

---

### Task 1: Add the shared aggregate-authoring validator

**Files:**
- Create: `backend/internal/features/analytics/aggregate_authoring_validation.go`
- Modify: `backend/internal/features/analytics/formula_view_validation.go`
- Test: `backend/internal/features/analytics/formula_view_service_test.go`

- [ ] **Step 1: Write failing validation tests**

```go
func TestValidateFormulaForObjectRejectsAggregateAuthoring(t *testing.T) {
    err := validateFormulaForTestError(t, "Issue__c", schemas, "SUM(WorkLog__c.Hours__c)")
    if !strings.Contains(err.Error(), "rollupsummary") {
        t.Fatalf("error = %q, want rollupsummary guidance", err.Error())
    }
}
```

- [ ] **Step 2: Run the targeted test to confirm the old path still accepts aggregates**

Run: `cd backend && go test ./internal/features/analytics -run 'TestValidateFormulaForObjectRejectsAggregateAuthoring|TestValidateAggregateParity_' -count=1`
Expected: the new reject test fails before implementation; existing parity tests continue to show the runtime path still exists.

- [ ] **Step 3: Add the shared helper and wire `ValidateFormulaForField`**

```go
func ValidateNoAggregateAuthoring(expr string) error {
    ast, err := Parse(expr)
    if err != nil {
        return err
    }
    if len(extractAggregates(ast)) > 0 {
        return fmt.Errorf("cross-child aggregate formulas are not supported for authoring; declare a rollupsummary field with RollupConfig instead")
    }
    return nil
}
```

- [ ] **Step 4: Re-run analytics tests**

Run: `cd backend && go test ./internal/features/analytics -run 'TestValidateFormulaForObjectRejectsAggregateAuthoring|TestValidateAggregateParity_|TestValidateFormulaForField_' -count=1`
Expected: new reject tests pass; runtime aggregate tests that build legacy views still pass.

### Task 2: Enforce the gate in metadata create, batch create, and update

**Files:**
- Modify: `backend/internal/features/metadata/field_create.go`
- Modify: `backend/internal/features/metadata/field_batch.go`
- Modify: `backend/internal/features/metadata/formula_type_validation.go`
- Modify: `backend/internal/features/metadata/validation_service.go`
- Test: `backend/internal/features/metadata/validation_service_test.go`
- Test: `backend/internal/features/metadata/field_update_test.go`

- [ ] **Step 1: Add failing metadata tests**

```go
func TestValidateFormulaExpressionDeclaredTypeRejectsAggregateAuthoring(t *testing.T) {
    formula := "SUM(Child__c.Amount__c)"
    err := ValidateFormulaAuthoringContract("Parent__c", "Total__c", constants.FieldTypeFormula, &formula)
    if err == nil {
        t.Fatal("want aggregate authoring rejection")
    }
}
```

- [ ] **Step 2: Add a shared metadata-facing wrapper that composes type and aggregate checks**

```go
func ValidateFormulaAuthoringContract(objectAPIName, fieldAPIName string, declaredType models.FieldType, formula *string) error {
    if err := ValidateFormulaExpressionDeclaredType(objectAPIName, fieldAPIName, declaredType, formula); err != nil {
        return err
    }
    if !HasNonEmptyFormulaExpression(formula) {
        return nil
    }
    return analytics.ValidateNoAggregateAuthoring(*formula)
}
```

- [ ] **Step 3: Switch create and batch create to the new wrapper**

Run: `cd backend && go test ./internal/features/metadata -run 'TestValidateFormulaExpressionDeclaredType|TestCreateField|TestBatchCreate' -count=1`
Expected: aggregate authoring create/batch tests now pass.

- [ ] **Step 4: Gate update only on formula-shape mutations**

```go
if existing != nil && formulaAuthoringShapeChanged(updated, existing) {
    if err := ValidateFormulaAuthoringContract("", field.APIName, field.Type, field.Formula); err != nil {
        return err
    }
}
```

- [ ] **Step 5: Re-run metadata tests**

Run: `cd backend && go test ./internal/features/metadata -run 'TestValidateFormulaExpressionDeclaredType|TestValidateFieldMetadataForUpdate|TestUpdateField' -count=1`
Expected: aggregate authoring updates fail; presentation-only edits on legacy aggregate formulas remain allowed.

### Task 3: Enforce the gate in source-backed pack validate and apply

**Files:**
- Modify: `backend/internal/controlplane/packs/pack_field_shapes.go`
- Modify: `backend/internal/controlplane/packs/pack_field_spec_writer.go`
- Test: `backend/internal/controlplane/packs/pack_field_shapes_test.go`
- Test: `backend/internal/controlplane/packs/pack_source_test.go`
- Test: `backend/internal/controlplane/packs/platform_core_test.go`

- [ ] **Step 1: Add failing pack validation tests**

```go
specs := []ObjectSpec{{
    APIName: "Parent__c",
    Fields: []FieldSpec{{
        APIName: "Total__c",
        Type: "formula",
        Formula: "SUM(Child__c.Amount__c)",
        ReturnType: "number",
    }},
}}
```

- [ ] **Step 2: Add a new pack diagnostic for aggregate formula authoring**

```go
out = append(out, diag(
    "field.formula.aggregate_authoring_requires_rollupsummary", p,
    "cross-child aggregate formulas are not supported for authoring; declare a rollupsummary field with RollupConfig instead",
))
```

- [ ] **Step 3: Add defense-in-depth apply rejection in `buildPackFieldSpecWriteWithResolver`**

Run: `cd backend && go test ./internal/controlplane/packs -run 'TestValidateInstallablePackSource|TestValidateObjectSpecFieldShapes|TestBuildPackFieldSpecWrite' -count=1`
Expected: validate-installable rejects aggregate formulas, and direct pack-field construction rejects them too.

### Task 4: Final verification and commit

**Files:**
- Modify: `docs/superpowers/specs/2026-05-24-1294-phase1-aggregate-authoring-gate-design.md`
- Modify: `docs/superpowers/plans/2026-05-24-1294-phase1-aggregate-authoring-gate.md`

- [ ] **Step 1: Run focused verification for all touched packages**

Run: `cd backend && go test ./internal/features/analytics ./internal/features/metadata ./internal/controlplane/packs -count=1`
Expected: PASS for all three packages.

- [ ] **Step 2: Run repo hygiene check**

Run: `git diff --check`
Expected: no whitespace or patch-format errors.

- [ ] **Step 3: Commit**

```bash
git add backend/internal/features/analytics backend/internal/features/metadata backend/internal/controlplane/packs docs/superpowers/specs/2026-05-24-1294-phase1-aggregate-authoring-gate-design.md docs/superpowers/plans/2026-05-24-1294-phase1-aggregate-authoring-gate.md
git commit -m "fix(formula): block aggregate formula authoring"
```
