bcquality/custom/knowledge/mcp/stored-derived-fields-must-not-be-exposed-directly.md
Michael Dieringer 935d756f05 Add mcp knowledge category with 5 rules
Rules derived from BC MCP API page development experience:

- api-page-flowfields-must-be-calcfields: FlowFields return empty on API
  pages unless explicitly CalcFields'd in OnAfterGetRecord
- stored-derived-fields-must-not-be-exposed-directly: Stored fields updated
  only via OnValidate triggers can be stale; recalculate live in OnAfterGetRecord
- api-page-key-fields-must-be-editable-on-insert: ODataKeyFields with
  Editable=false are rejected as unknown properties on POST
- api-page-least-privilege-write-access: Create dedicated minimal pages per
  write concern rather than widening general-purpose pages
- agent-must-not-write-business-process-status: Agents must only write
  developer-tracking fields; business status fields affect invoicing/time registration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:48:06 +02:00

44 lines
1.7 KiB
Markdown

# CURABIS MCP: Stored Derived Fields Must Be Recalculated in OnAfterGetRecord
## Core Principle
A stored field whose value is derived from other fields via `OnValidate` triggers can be stale. When the source data changes (e.g., new time entries posted), the stored derived field is not updated automatically — it only recalculates when a specific trigger fires. Exposing such a field directly via an API page returns a value that may be hours, days, or weeks out of date.
## Pattern to Avoid
```al
// WRONG: Exposes the stored snapshot — may be stale
field(timeLeft; Rec."Time left") { }
```
`"Time left"` is recalculated only when `"Estimated time"` is validated. If new time entries are posted, the stored value does not update.
## Correct Pattern
Recalculate in `OnAfterGetRecord` using a page variable:
```al
trigger OnAfterGetRecord()
begin
Rec.CalcFields("Elapsed time (Chargeable)");
TimeLeftCalc := Rec."Estimated time" - Rec."Elapsed time (Chargeable)";
end;
var
TimeLeftCalc: Decimal;
// In layout:
field(timeLeft; TimeLeftCalc) { } // live value
field(elapsedTime; Rec."Elapsed time (Chargeable)") { } // source FlowField
```
## Requirements
- Identify stored fields whose value is computed from other fields via triggers
- Do not expose them directly in API pages
- Recalculate from the authoritative source (FlowField or live query) in `OnAfterGetRecord`
- Expose both the recalculated result and the source FlowField so the consumer can verify
## Verification
Inspect the source table for any field with `FieldClass = Normal` whose value is set inside an `OnValidate` trigger on another field. If that field is exposed on an API page, verify it is recalculated in `OnAfterGetRecord` rather than read from `Rec` directly.