Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/20773-etl-entry-syncconfig-schedule-deleted-in-17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@objectstack/spec': patch
---

fix(spec): two ADR-0087 migration entries state what the tree does — `etl-pipeline-layer-retired` dates the `syncConfig.schedule` deletion to `@objectstack/spec` 17, and `driver-sql-unresolvable-where-column-refused` names the remote `aggregate()` refusals

Clause-②: no

**`etl-pipeline-layer-retired` (protocol 17).** The entry explains that connector-attached
`syncConfig` has no reader outside `packages/spec`, and cites the same measurement that removed
`syncConfig.schedule`. Its `replacement` text said that key was retired "in 18". It was deleted
in `@objectstack/spec` 17 under ADR-0049 (first released in 17.5.0), as the note at the deleted
position in `integration/connector.zod.ts` already says. The sentence now reads "the same
measurement that deleted `syncConfig.schedule` in @objectstack/spec 17 under ADR-0049".

**`driver-sql-unresolvable-where-column-refused` (protocol 18).** The entry named only a `where`
column on `find()` / `findOne()` / `count()` and `INVALID_FILTER` / 400. On the remote face of
`TursoDriver`, the `aggregate()` door answered `[]` for a missing column or a missing table. It
now refuses as the local face does: `INVALID_FILTER` / 400 for a `where` column the table lacks, `INVALID_FIELD` / 400 for a
`groupBy` or aggregation column the table lacks, and `DATABASE_ERROR` / 500 for an object whose
table is absent. The entry's `surface` now names that door and those codes. Its remedy adds
grouping and aggregating, and running schema sync so the object's table exists. Its acceptance
criterion now also covers a report or dashboard that groups by, or aggregates over, a name the
object has no column for.

Text only: no entry id, conversion or matching logic changes, and `os migrate meta` rewrites
exactly what it rewrote before. The generated migration registry, `spec-changes.json` and the
protocol upgrade guide carry the corrected text.
2 changes: 1 addition & 1 deletion docs/protocol-upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ ONE AUTHOR-REACHABLE SURFACE reaches this indirectly and is why it is not purely
- **`enhanced-api-error-field-errors-renamed`** — `api.enhancedApiError.fieldErrors` → fields
- Why not automatic: The wire has always carried `fields` — the validators, import coercion, validation-failure.ts, @objectstack/client and the console's field-error extractor all say `fields`, and nothing ever emitted `fieldErrors`, so a reader keying on it was reading a field no server sent (ADR-0078's silently-inert declaration, on the error envelope). This is a RESPONSE surface: no stack, example or template carries the key, so there is no source for the chain to rewrite — the schema tombstones it via retiredKey() and consumers move their read themselves. ADR-0114 D4 (the field-level error code catalog).
- Done when: No consumer reads `error.fieldErrors`; per-field validation detail is read from `error.fields`, and constructing an EnhancedApiError with `fieldErrors` fails to parse with the rename prescription instead of silently losing the array.
- **`etl-pipeline-layer-retired`** — `automation.etlPipeline / automation.etlPipelineRun / automation.etlSource / automation.etlDestination / automation.etlTransformation (the whole L2 layer of automation/etl.zod.ts, its four enums and the `ETL` factory — 9 defs, 27 exported names)` → (removed — no protocol surface replaces it, deliberately. Layer by layer: connector-attached synchronisation is `ConnectorSchema.syncConfig` (`integration/connector.zod.ts`), which is PARSED AND VALIDATED but NOT EXECUTED — a declared shape, not a running sync. `AutomationEngine.registerConnector` runs `ConnectorSchema.parse` and stores the parsed definition; nothing reads `syncConfig` back off it, and the key has no reader outside `packages/spec` at all — the same measurement that retired `syncConfig.schedule` in 18 under ADR-0049, with the other cron-typed positions nothing reads. What the platform DOES execute on a connector is its `actions`: a flow's `connector_action` node resolves the registered handler and awaits it, so an author who needs data actually moved drives it from there. Per-field value transformation on import is `shared/mapping.zod.ts`, whose `transform` is applied row by row by the REST import path and recorded key by key in `packages/spec/liveness/mapping.json`; scheduling is `system/job.zod.ts`. What has NO replacement is multi-source, multi-stage movement with joins and aggregations — because it never had an implementation either. It returns through the ENFORCE route: the engine first, the vocabulary second)
- **`etl-pipeline-layer-retired`** — `automation.etlPipeline / automation.etlPipelineRun / automation.etlSource / automation.etlDestination / automation.etlTransformation (the whole L2 layer of automation/etl.zod.ts, its four enums and the `ETL` factory — 9 defs, 27 exported names)` → (removed — no protocol surface replaces it, deliberately. Layer by layer: connector-attached synchronisation is `ConnectorSchema.syncConfig` (`integration/connector.zod.ts`), which is PARSED AND VALIDATED but NOT EXECUTED — a declared shape, not a running sync. `AutomationEngine.registerConnector` runs `ConnectorSchema.parse` and stores the parsed definition; nothing reads `syncConfig` back off it, and the key has no reader outside `packages/spec` at all — the same measurement that deleted `syncConfig.schedule` in @objectstack/spec 17 under ADR-0049, with the other cron-typed positions nothing reads. What the platform DOES execute on a connector is its `actions`: a flow's `connector_action` node resolves the registered handler and awaits it, so an author who needs data actually moved drives it from there. Per-field value transformation on import is `shared/mapping.zod.ts`, whose `transform` is applied row by row by the REST import path and recorded key by key in `packages/spec/liveness/mapping.json`; scheduling is `system/job.zod.ts`. What has NO replacement is multi-source, multi-stage movement with joins and aggregations — because it never had an implementation either. It returns through the ENFORCE route: the engine first, the vocabulary second)
- Why not automatic: The reading the spec dual-source cleanup used to retire L1 `DataSyncConfig` (its automation copy deleted as dead), re-measured one layer up and identical: narrative-only. No engine ever parsed, scheduled or executed an `ETLPipeline`. Measured on origin/main immediately before the removal: the only non-spec references in this repo are two fumadocs-generated documentation sources (`apps/docs/.source/*.ts`), not executors; objectui has no reference at all; there is no `liveness/etl.json` or `pipeline.json`, so no ADR-0049 gate ever had a reading on it — while the same file family's EXECUTED half does have one (`liveness/mapping.json`), which is the contrast that makes the absence meaningful rather than an oversight. The `etl` string in this registry was the one untested link the finding named, and it is not a loader path: it was the id of the retry-vocabulary entry for `ETLPipeline.retry` (a third retry-policy vocabulary the retry convergence had not covered), absorbed here. The layer was ADR-0078's asymmetry in its purest form — an author could write a complete ten-stage pipeline, get no error, and get no execution. It was also advertised: `packages/spec/docs/SYNC_ARCHITECTURE.md` named `ETLPipeline` as the recommended destination for authors displaced by the L1 retirement and listed ten transformation types with copyable examples down to `script | Custom JavaScript/Python`. That document is rewritten in the same change; a retirement whose own doc still recommends the retired layer is self-contradictory, and forwarding L1's authors to a second layer with no executor was the defect compounding rather than closing. ⚠️ `etl-retry-converged-onto-retry-policy` is SUBSUMED here, the way the `activationEvents`, dynamic plugin-loading and widget / i18n retirements each let an earlier tombstone go with the shape that carried it: both land in the unreleased protocol 17, so composed, a rename of `retry.maxAttempts` on a shape that does not survive the major has no observable effect — and keeping both would tell an upgrader to rewrite a key on a schema the same upgrade deletes. The `maxAttempts` `retiredKey()` tombstone goes with the shape that carried it, which is strictly stronger than the tombstone: there is no longer a `retry` block to author the key into. Route 3 — no carrier key, no parse site, so no D2 conversion and no tombstone; RETIRED_DEFS_BY_MAJOR plus this entry are the declaration. ADR-0049, ADR-0078.
- Done when: No source imports `ETLPipeline`, `ETLPipelineParsed`, `ETLPipelineSchema`, `ETLPipelineRun(Schema)`, `ETLSource(Schema)`, `ETLDestination(Schema)`, `ETLTransformation(Schema)`, `ETLEndpointType(Schema)`, `ETLTransformationType(Schema)`, `ETLSyncMode(Schema)`, `ETLRunStatus(Schema)` or the `ETL` factory from `@objectstack/spec/automation`; `tsc` reports TS2724/TS2305 on any that survives. Every author who was pointed at L2 has been re-pointed by name: SYNC_ARCHITECTURE.md no longer lists an L2 row, no longer recommends `ETLPipeline` as L1's destination and no longer advertises a transformation-type table. The surviving layers still parse unchanged — a connector declaring `syncConfig` and an import declaring `mapping.transform` both behave exactly as they did in 16.x.
- **`export-axis-opt-in`** — `security.permissionSet.objects[].allowExport (ABSENT — a permission set that never declared the key)` → an explicit `allowExport: true` on the object entry (or the `*` wildcard) of every permission set whose holders are meant to keep exporting
Expand Down
Loading
Loading