You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 95555e7
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec): declare the per-row write-drop signal on the import and validate answers, and the served import warnings (#20930)
Closes#20921
Clause-②: yes (widening)
- `ImportRowResultSchema` and each
`ValidateDataResponseSchema.results[]` row gain an optional
`droppedFields`: `z.array(DroppedFieldsEventSchema)`, the reference
`BatchOperationResultSchema.droppedFields` already uses. No second
reason enum: the set is the engine's, `computed` included.
- `ImportRowResult.warnings` is declared as served:
`z.array(ValidateDataIssueSchema)`, set on ok dry-run rows only.
Producer `packages/rest/src/import-runner.ts:852`; it reaches the sync
route and the async job results; zero readers in objectui at
`741864f7b9`. Undeclared, `parse` stripped it.
- `ImportJobResultsSchema.results` describe: a capped sample, failures
first; an ok row's report reaches the async reader only inside it. The
cap is unchanged.
- Pins in `export.test.ts` and `validate-data.test.ts`. Ablation at
`3e9b7ece06` (each key renamed out of its schema, restore proven by blob
hash): 4 / 3 / 1 tests red; control 53/53 green.
- Local at `3e9b7ece06`: spec test 583 files, 17177 passed; spec
typecheck exit 0; `check:generated` 15 of 15 current; 104 of 108 derived
gates exit 0, 4 NOT MEASURED (they need whole-workspace builds; CI runs
them).
- Changeset: `@objectstack/spec` minor. It names objectui's
`STRIPPED_LINE` toast table as the exhaustive `reason` consumer.
## Acceptance notes
- The key is declared ahead of its producer: #20922 (engine) and the
REST item of #20701 set it. Until both land no server emits
`droppedFields`, and both describes say an absent key does not prove
nothing was dropped.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The import row report and the validate-only answer can now say which fields a write drops. `ImportRowResultSchema` and each `ValidateDataResponseSchema.results[]` row gain an optional `droppedFields`: an array of `DroppedFieldsEventSchema`, the engine's own strip event. So the reason vocabulary is the engine's (`readonly`, `readonly_when`, `primary_key`, `computed`), and there is no second enum. The row still succeeds: `ok`, `action` and `valid` are unchanged. The key is declared ahead of its producer. A server that does not produce the report omits the key, so an absent key alone does not prove nothing was dropped.
6
+
7
+
`ImportRowResultSchema` also declares `warnings`, which the REST import dry run already serves: the findings the validate verdict admits, in the `ValidateDataIssue` shape, on an ok dry-run row. Until now `ImportRowResultSchema.parse` stripped the key, and readers typed by the spec could not see it.
8
+
9
+
`ImportJobResultsSchema.results` now says what an async reader gets: a capped sample, failures first. An ok row's `droppedFields` or `warnings` reaches that reader only if the row falls inside the sample. The cap is unchanged.
10
+
11
+
A consumer that branches on `reason` must stay exhaustive over `DroppedFieldsEvent['reason']`. The known exhaustive consumer is objectui's write-warning toast table, `STRIPPED_LINE` in `packages/app-shell/src/providers/writeWarningToast.ts`, which covers all four reasons today. A reader that renders the import or preview report should word `reason` through that table rather than a second one. When the union widens again, the table keyed by it fails type-check on the missing reason, while a second table would fall behind without a sound.
|**results**|`{ row: integer; ok: boolean; action: Enum<'created' \| 'updated' \| 'skipped' \| 'failed'>; id?: string; … }[]`| ✅ | Capped sample of per-row outcomes, failures first. An ok row, and any `droppedFields` or `warnings` it carries, reaches this reader only if it falls inside the sample; `resultsTruncated` says whether the sample is partial, and the job counters count every row.|
219
219
|**resultsTruncated**|`boolean`| ✅ | Whether `results` is a capped sample of a larger set |
|**warnings**|`{ field: string; code: string; message: string }[]`| optional | Findings this deployment ADMITS rather than rejects (ADR-0104 value shapes under a warn-first posture), in the same `{ field, code, message }` shape the validate verdict carries. Set only on a dry-run row the verdict accepted: the row is `ok` because the write would store it and log the same complaint. A committed row never carries it, since the write path has no channel to report admitted findings on. |
233
+
|**droppedFields**|`{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed'> }[]`| optional | Write-observability: caller-supplied fields the engine LEGALLY strips from THIS row, one event per reason, in the engine's own `droppedFields` shape and reason vocabulary. A dry-run row reports the strips the write would make; a committed row reports the strips it made. The row still succeeds: `ok` and `action` are unchanged. Present only when at least one field was dropped. A server that does not produce this report omits the key too, so an absent key alone does not prove nothing was dropped. In an async job's `results`, an ok row's drops reach the reader only if the row falls inside that capped, failures-first sample. |
|**warnings**|`{ field: string; code: string; message: string }[]`| optional | Findings this deployment ADMITS rather than rejects (ADR-0104 value shapes under a warn-first posture), in the same `{ field, code, message }` shape the validate verdict carries. Set only on a dry-run row the verdict accepted: the row is `ok` because the write would store it and log the same complaint. A committed row never carries it, since the write path has no channel to report admitted findings on. |
361
+
|**droppedFields**|`{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed'> }[]`| optional | Write-observability: caller-supplied fields the engine LEGALLY strips from THIS row, one event per reason, in the engine's own `droppedFields` shape and reason vocabulary. A dry-run row reports the strips the write would make; a committed row reports the strips it made. The row still succeeds: `ok` and `action` are unchanged. Present only when at least one field was dropped. A server that does not produce this report omits the key too, so an absent key alone does not prove nothing was dropped. In an async job's `results`, an ok row's drops reach the reader only if the row falls inside that capped, failures-first sample. |
|**warnings**|`{ field: string; code: string; message: string }[]`| optional | Findings this deployment ADMITS rather than rejects (ADR-0104 value shapes under a warn-first posture), in the same `{ field, code, message }` shape the validate verdict carries. Set only on a dry-run row the verdict accepted: the row is `ok` because the write would store it and log the same complaint. A committed row never carries it, since the write path has no channel to report admitted findings on. |
380
+
|**droppedFields**|`{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed'> }[]`| optional | Write-observability: caller-supplied fields the engine LEGALLY strips from THIS row, one event per reason, in the engine's own `droppedFields` shape and reason vocabulary. A dry-run row reports the strips the write would make; a committed row reports the strips it made. The row still succeeds: `ok` and `action` are unchanged. Present only when at least one field was dropped. A server that does not produce this report omits the key too, so an absent key alone does not prove nothing was dropped. In an async job's `results`, an ok row's drops reach the reader only if the row falls inside that capped, failures-first sample. |
|**fields**|`string[]`| ✅ | Caller-supplied field names the engine removed from the write payload |
398
+
|**reason**|`Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed'>`| ✅ | Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, the primary-key strip of a payload id the engine ruled is not an identifier, or a computed (formula) field no driver has a column for |
|**posture**|`{ valueShapeStrict: boolean; mediaValueShapeStrict: boolean }`| ✅ | The ADR-0104 posture the verdict was reached under — reported because it is the difference between "this row is fine" and "this row is fine HERE". The same row can be an error on a self-certified deployment and an admitted warning on an un-migrated one, and a caller explaining a verdict needs to know which it got. An unconditionally-strict preview was considered and rejected (option B): it would fail rows on every un-migrated deployment that the write would have accepted. |
@@ -2987,6 +2987,7 @@ A write-path strip event: caller-supplied fields legally dropped from the payloa
2987
2987
|**valid**|`boolean`| ✅ | True when this row would be accepted by the write path. |
2988
2988
|**errors**|`{ field: string; code: string; message: string }[]`| ✅ | Findings that would REJECT this row. Empty when valid. |
2989
2989
|**warnings**|`{ field: string; code: string; message: string }[]`| ✅ | Findings the target deployment ADMITS rather than rejects — today, ADR-0104 value shapes under a warn-first posture. The row is valid; the write would store it and log the same complaint. |
2990
+
|**droppedFields**|`{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed'> }[]`| optional | Write-observability for the preview: caller-supplied fields the write would LEGALLY strip from THIS row, one event per reason, in the shape and reason vocabulary the write itself reports. A strip is not a finding: `valid` is unaffected, and the fields appear in neither `errors` nor `warnings`. Present only when at least one field would be dropped. A server that does not produce this report omits the key too, so an absent key alone does not prove nothing would be dropped. |
0 commit comments