Skip to content

Commit a44c526

Browse files
committed
docs(cli): name the seed-value judge's hook-rewrite and manifest coverage limits (#27259)
Contract review should-fixes, comment and changeset text only: the walk's header names that a beforeInsert hook is not run at authoring time and that a dataset whose object is declared only under manifest.objects is unjudged; validateInsertValues' docblock no longer over-states that `required` is left to the load (an explicit [] on a required multi-value field is refused here, as at load). Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8e7f125 commit a44c526

3 files changed

Lines changed: 19 additions & 2 deletions

File tree

‎.changeset/27259-validate-seed-record-values.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Metadata-protocol: narrowed
1515
- **Before.** A seed record such as `budget: { value: 10, currency: 'USD' }` on a currency field passed `os validate` with "Validation passed" (exit 0) and `os build` emitted the artifact. The row was refused only when the seed loaded, by the record validator: `Budget is a currency amount: write it as a bare number …`, one dropped row in the boot log.
1616
- **Now.** Both commands judge every seed record value with the judge the load runs, and refuse with that judge's code and sentence, located at the value: `data[0].records[4].budget: Budget is a currency amount: write it as a bare number … (invalid_number)`, exit 1. `--json` carries each issue as `{ location, object, field, code, message }`.
1717
- **One judge.** The verdict is `validateInsertValues` (`@objectstack/objectql`, new): the insert write's own normalizations (a blank on a typed field is `null`, a numeric string is its number, a scalar on a multi-value field is wrapped) and then the same record-validator call the insert path makes. The field map is the runtime registry's, built from the stack's objects and picklists. So a value the load accepts after a rewrite (`'10'` on a number field) still passes.
18-
- **Left to the load.** `required` (a default or a hook may fill the value), a field the object does not declare (`defineSeed` already refuses it), and every value the load rewrites or a deployment decides: an expression envelope (the `cel` template), a `seedFile(path)`, a reference, file or structured-JSON field, a field bound to a picklist another package declares, and a dataset whose object another package declares.
18+
- **Left to the load.** `required` on a missing value (a default or a hook may fill it; an explicit `[]` on a required multi-value field is refused `required` here, as at load), a field the object does not declare (`defineSeed` already refuses it), and every value the load rewrites or a deployment decides: an expression envelope (the `cel` template), a `seedFile(path)`, a reference, file or structured-JSON field, a field bound to a picklist another package declares, and a dataset whose object another package declares.
19+
- **Known limits.** A `beforeInsert` hook is not run at authoring time, so a value a hook would rewrite into an accepted shape is refused here though the load stores it. A dataset whose object is declared only under `manifest.objects`, outside `packages[]`, is not judged.
1920
- **Who sees it.** A stack shipping a seed row the load already drops. The four example apps in this repository (`app-todo`, `app-crm`, `app-showcase`, `app-multi-package`) carry none and pass unchanged.
2021

2122
**The remedy.** Write the value in the shape the message names, for a currency a bare number such as `10`, with the currency in the field's `currencyConfig`.

‎packages/cli/src/utils/seed-record-values.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@
4141
*
4242
* A field the object does not declare is left to the answer it already has
4343
* (`defineSeed` refuses it at load of the config).
44+
*
45+
* ## Known limits
46+
*
47+
* - A `beforeInsert` hook is not run here. The seed write runs object hooks
48+
* (the seed context skips automation triggers, not hooks), and they run
49+
* BEFORE the record validator, so a hook that rewrites a seed value into an
50+
* accepted shape lets the load store it while this walk refuses it as
51+
* written. The authoring door has no hook runtime by design; a refusal of
52+
* a value a hook would have fixed comes from here.
53+
* - Coverage is the folded stack's: `authoringRuleUnionStack` folds the
54+
* stack's own collections and its `packages[]` bodies. A dataset under
55+
* `manifest.data` whose object is declared only under `manifest.objects`
56+
* (outside `packages[]`) finds no field map and goes unjudged, by the
57+
* dataset clause above: fail-open, never a false refusal.
4458
*/
4559

4660
import { createLogger } from '@objectstack/core';

‎packages/objectql/src/validation/record-validator.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1976,7 +1976,9 @@ export function validateRecord(
19761976
*
19771977
* What it leaves to the load, by construction:
19781978
* - a missing value (`undefined`, `null`, blank): whether the row needs one is
1979-
* `required`, which a `defaultValue` or a hook may still satisfy;
1979+
* `required`, which a `defaultValue` or a hook may still satisfy. A
1980+
* SUPPLIED empty array on a required multi-value field is not missing: it
1981+
* is refused `required` here, as the insert path refuses it;
19801982
* - a name the schema does not declare: the undeclared-field refusal is
19811983
* another door's;
19821984
* - every non-value rule: validation rules, references resolving, permissions.

0 commit comments

Comments
 (0)