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 2178d80
Browse filesBrowse the repository at this point in the historyBrowse files
docs(skills): teach the seed modes the loader runs (ignore default, replace deletes nothing) and that a deleted seed row stays deleted (#31097)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} -->
_Requested by **Irene** · [project
thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xTkTkeVm9Y984jksBytJ867)_Fixes#31090
Item 3 of the card (the seed `mode` text in
`packages/spec/src/data/seed.zod.ts`, comment 6702892720) is routed to
the spec lane as #31099 (with the stale `replace` string in
`packages/cli/src/commands/diff.ts:81`). This PR does not touch
`packages/spec`; the wording owed is listed below.
Clause-②: no
Metadata-protocol: none
`dispatch-gates --tier` reads a MANDATORY model tier for `skills/**`
(clause ① of the model-tiering ruling); it is waived by the standing
order of the maintainer in this project (2026-11-03), and this card ran
on the project's default model. The dispatching seat runs the contract
review.
## 维护者速读
- 改了什么:`objectstack-data` 技能的 `references/seeds.md` 改正种子模式:默认是
`ignore`(只建不改),`replace` 不删任何行,`insert` 每次重放都会重复插入;并补一句「AppPlugin
的启动和按组织重放不会把人删掉的种子行插回(发布、装包不记账,会插回),`seed-replayer` 带 `{ reseed: true }`
才恢复」。`content/docs/data-modeling/seed-data.mdx` 新增一小节讲同一规则,并列出不记账的加载入口。
- 为什么改:技能表格仍写 `upsert`(默认)和 `replace` =「先删光再插」,自 #30081 起已不成立;PR #31083
落地的「已落地键」规则文档里没有,客户想找回示例数据没有文档可查。
- 风险与回滚:只改文字和一条评测的期望描述;`seeds.md` token 1566 → 1560(上限不动);发
`@objectstack/skills` patch;回滚即 revert。
- 先看:`skills/objectstack-data/references/seeds.md` 的「Import
modes」表及其下一段,`seed-data.mdx` 新小节「A Deleted Seed Row Stays
Deleted」,对照下文「Code sites」。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期退出
0,`skills/objectstack-data/references/seeds.md is 1560 tokens (ceiling
1566; headroom 6)`。
- 需要你做的:`skills/**` 属 Tier H(人合),需要 `os-zhuang` 或 `hotlong` 的一次
approval,之后席位落地。
## What changes
**`skills/objectstack-data/references/seeds.md`** (whole-file sweep):
```diff
-| `externalId` | `'name'` | Stable business key used for upsert / update lookup |
-| `mode` | `'upsert'` | Import strategy (see below) |
+| `externalId` | `'name'` | Stable business key every keyed mode matches on |
+| `mode` | `'ignore'` | Import strategy (see below) |
@@ Import modes
-| `upsert` (default) | Update by `externalId`, insert if missing. Idempotent. | Reference data, bootstrap rows |
-| `insert` | Insert all; fail on duplicate `externalId`. | Append-only / audit tables |
+| `ignore` (default) | Create-only: insert a missing row, leave a matched row as is. | Data the customer owns |
+| `upsert` | Insert if missing; every replay writes declared columns back. | Reference data kept current |
+| `insert` | Insert every record, no match: duplicates on each replay. | Rows loaded once |
| `update` | Update only existing rows; never create. | Patching existing config |
-| `ignore` | Insert; silently skip duplicates. | Additive bootstrap |
-| `replace` ⚠️ | Delete everything, then insert. **Data loss.** | Cache / lookup tables only — never user data |
+| `replace` ⚠️ | Insert with no match; deletes nothing, the caller clears the table. | Cache / lookup tables only |
+
+A seed replays on each boot, install and re-publish. AppPlugin's boot and organization
+replays (not a publish or package install) do not re-insert a keyed row a
+person deleted; `seed-replayer` with `{ reseed: true }` restores it.
@@ Seed best practices
-| Default to `upsert` | Idempotent re-runs |
+| Leave `mode` unset; `upsert` only for reference data | Replays keep customer edits |
-| Use `replace` only on cache/lookup tables, with comments | Data-loss footgun |
+| `replace` only on cache tables the caller clears | It never deletes: duplicates otherwise |
```
Also in that file:
- Quick start: the demo `categorySeed` (≈L36) drops `mode: 'upsert'`, so
demo data takes the create-only default (as `seed-data.mdx` teaches).
The reference `statusSeed` (≈L26) keeps `mode: 'upsert'`: reference data
is the case `upsert` is for.
- `applyOn` section: its example `defineSeed(Invoice, …)` gains `mode:
'upsert'`. Without it the example ran under `ignore`, where `applyOn:
'create'` changes nothing, so it silently relied on the old default.
**`skills/objectstack-data/evals/hooks-security-seeds-datasources.json`**,
case 4. The demo `Opportunity` seed had no `mode`, yet the case required
`applyOn: 'create'` to keep a replay off `stage`, which only matters
under the old `upsert` default. The prompt now asks for demo rows
refreshed from the seed on every replay except `stage`, so the right
answer is `mode: 'upsert'` plus `columns: { stage: { applyOn: 'create' }
}`, and `expected_output` says so. The assertions are unchanged and
every one of them is now live (review S3: the dataset declares `upsert`,
rather than dropping the `must_contain`).
**`content/docs/data-modeling/seed-data.mdx`**: a new subsection, "A
Deleted Seed Row Stays Deleted", after the `insert` subsection: the
landed-keys rule; the loads that keep and honour it (AppPlugin's
single-tenant boot seed, per-organization replay and dev hot reload, for
a known organization); the loads that keep no record, where a deleted
row comes back (a seed publish, a package install or upgrade including
the marketplace install, heal and sample-data reseed, a load with no
organization, a composition without `sys_seed_landed_key`, the
marketplace's fallback `seed-replayer`); the once-more case for a row
deleted before the upgrade; and the `seed-replayer` `{ reseed: true }`
door. The `ignore` and `upsert` rows of the modes table link to it,
scoped to "a boot or organization replay". The rest of the page
(`:74-80`) already matched the loader.
Plus `.changeset/skills-data-seed-modes-landed-keys.md`:
`'@objectstack/skills': patch`.
## Why
An AI that follows the published skill writes seeds expecting `upsert`
replays by default and a `replace` that clears the table. Neither is
what the loader does since #30081. After #31021 (PR #31083), a seed row
a person deleted stays deleted across replays, and nothing documented
that or how to restore it.
## Code sites (each taught fact, measured on `main` b631573)
| Taught fact | Code site |
|---|---|
| The default `mode` is `ignore`, and the loader's `defaultMode` is that
same default | `packages/spec/src/data/seed.zod.ts:193` `mode:
SeedMode.default('ignore')`;
`packages/spec/src/data/seed-loader.zod.ts:253`;
`packages/metadata-protocol/src/seed-loader.ts:1303` `dataset.mode \|\|
config.defaultMode` |
| `ignore`: matched row skipped, missing row inserted |
`packages/metadata-protocol/src/seed-loader.ts:3298-3304`
(`writeRecord`), `:3355-3356` (`decideWriteAction`) |
| `upsert`: matched row updated with the declared columns (skipped only
on a no-op), missing row inserted | `seed-loader.ts:3284-3296`,
`:3350-3354` |
| `insert`: inserts every record, no match | `seed-loader.ts:3267-3270`,
`:3357-3360` |
| `update`: matched row updated, missing row skipped |
`seed-loader.ts:3272-3282`, `:3345-3349` |
| `replace` deletes nothing: it inserts, "caller should have cleared the
table" | `seed-loader.ts:3306-3310`, `:3358-3360`; no `'replace'` branch
anywhere in `packages/metadata-protocol/src` or `packages/runtime/src`
clears a table |
| The record of landed keys is the organization-owned
`sys_seed_landed_key` (cascades with the organization) |
`packages/platform-objects/src/system/sys-seed-landed-key.object.ts:47`,
`:88` |
| Only `upsert` / `ignore` datasets read and write it |
`seed-loader.ts:1406-1408` |
| A recorded key whose row is gone is not inserted again; a
never-recorded key still lands | `seed-loader.ts:1946-1951`
`isWithdrawnRow`; option doc `:671-690` |
| A reference to the removed row is left unwritten, not reported
unresolved | `seed-loader.ts:2701` (`resolveReferenceItem` answers
`withdrawn` via `isWithdrawnSeedTarget`, `:4119-4122`), handled at
`:2066` and `:2851` |
| Loads that honour it: boot seed, per-organization replay, dev hot
reload | `packages/runtime/src/app-plugin.ts:2098` (boot), `:1996`
(replayer), `:2526` (hot reload), all through `seedLoaderOptionsFor`
`:84-98` |
| The boot seed is single-tenant; a multi-tenant boot skips it for the
per-organization replay | `app-plugin.ts:2069`
(`organizationWallActive`), `:2084` (`multi-tenant mode — skipping
inline seed`) |
| Loads that keep no record (no `landedKeys` option) |
`packages/metadata-protocol/src/protocol-publish-batch.ts:103` (publish
apply); `packages/runtime/src/domains/packages.ts:4729` (package
install);
`packages/cloud-connection/src/marketplace-install-local-plugin.ts:2982`,
`:4768`, `:4880` (install-local loads); its fallback `seed-replayer`
`:1689` (`registerSeedReplayerIntoKernel`, only when none is registered)
|
| No organization or no `sys_seed_landed_key`: no record, said once |
`seed-loader.ts:4051-4070` |
| The deliberate re-seed: kernel service `seed-replayer`,
`(organizationId, { reseed: true })` | `app-plugin.ts:1963` (signature),
`:1996` (`reseed === true` → `'reseed'`), `:2042` →
`packages/runtime/src/seed-datasets.ts:41` service name; callable by
server code holding a plugin context (`ctx.getService`), e.g.
`packages/plugins/organizations/src/organizations-plugin.ts:408`; it has
no HTTP route |
| A row deleted before this release comes back once, then is remembered
| PR #31083 body, "Boundaries, by design"; the first load records every
row still present (`seed-loader.ts:2491` `recordLandedKeys`) |
## Acceptance notes
### Token budget (`node scripts/check-skills-token-ratchet.mjs`, exit 0)
| File | `main` | head | Ceiling |
|---|---|---|---|
| `skills/objectstack-data/references/seeds.md` | 1566 | 1560 | 1566
(unchanged) |
| `skills/objectstack-data/SKILL.md` | 6078 | 6078 (not touched) | 6078
|
| `skills/objectstack-data/evals/hooks-security-seeds-datasources.json`
| 1753 | 1786, measured, not ratcheted (eval fixture) | — |
No ceiling moves; the ratchet script is not touched. Every added line in
`seeds.md` is at most 120 bytes (longest: the `replace` row, 119 B;
`ignore` row 111 B, `upsert` row 106 B). The corrected rows plus the
landed-keys lines were paid by deleting restatement in the same file:
| Deleted | Surviving home |
|---|---|
| Intro: "`defineSeed()` is type-safe: TypeScript checks every record's
field keys against the object definition." | Same file, Seed best
practices row "Always use `defineSeed()`, never `SeedSchema.parse()` —
Lose compile-time field checking otherwise" |
| `applyOn` intro: "A seed is replayed (every dev-server boot, every
re-publish)" | Same file, the line under the modes table: "A seed
replays on each boot, install and re-publish." |
| `applyOn` intro: "right for master data," | The modes table, `upsert`
row: "Reference data kept current" |
| `applyOn` parenthesis: the mode-by-mode restatement, shortened to
"`update` never writes it; no other mode updates a matched row" | The
modes table rows for `ignore` / `insert` / `replace` |
| Determinism gate: "CEL + pinned `now` is what guarantees that — using
`Date.now()` will fail CI." | Same file, Dynamic values (CEL): "This is
the only correct way … `new Date()` ships the package author's clock to
every customer and breaks build determinism." |
| Quick start `categorySeed`: `mode: 'upsert',` | Not restatement:
removed as a correction (demo data takes the default). |
### Review fixes (contract review 6703924081)
- M1: `seed-data.mdx` names the loads that keep no record, so a deleted
row comes back on a publish or a package install or upgrade, and says
the boot seed is the single-tenant boot. The `ignore` row's "(unless a
person deleted it)" now reads "(a boot or organization replay skips one
a person deleted)".
- S1: the mdx names the marketplace fallback `seed-replayer`, a load
with no organization, and a composition without `sys_seed_landed_key`.
`seeds.md` scopes its sentence to "AppPlugin's boot and organization
replays (not a publish or package install)".
- S2: `seeds.md` says again when a seed replays: "each boot, install and
re-publish".
- S3: eval case 4's demo dataset now declares `upsert` (prompt and
`expected_output`); the `must_contain` list is unchanged and live.
- S4: done by the seat (#31099); the body links it.
- S5: the `ignore` and `upsert` rows are 111 B and 106 B.
- N1: the mdx `upsert` row links the rule too. N2: the `externalId` row
says "every keyed mode matches on".
### Sweep hits
Grep for `upsert (default)`, `default.*upsert` and `replace.*delete`
(plus the seed `mode` claims by hand) over
`skills/objectstack-data/SKILL.md`, `rules/*.md`, `evals/*.json` and
`references/`:
- `references/seeds.md:51` (`externalId` "upsert / update lookup", N2),
`:52` (`mode` default), `:63` (`upsert` (default)), `:64` (`insert`
"fail on duplicate"), `:66` (`ignore` "silently skip duplicates"), `:67`
(`replace` "Delete everything"), `:160` ("Default to `upsert`"), `:163`
(`replace` "Data-loss footgun"): fixed.
- `references/seeds.md:36` (demo seed `mode: 'upsert'`) and `:82-86`
(`applyOn` example without `mode`): fixed, as above. `:26` (reference
data `mode: 'upsert'`): correct, left.
- `evals/hooks-security-seeds-datasources.json` case 4: implicit old
default (it required `applyOn: 'create'` on a dataset with no `mode`),
fixed as above.
- `SKILL.md`, `rules/*.md`, `evals/objects-fields-relationships.json`:
no hit. (`SKILL.md:104` "derived verbs (search/export/upsert/…)" is the
API verb, not a seed mode.)
Other `content/docs` pages that restate seed modes: the generated
reference pages (`references/data/seed.mdx:33`,
`references/data/seed-loader.mdx:198`,
`references/api/package-api.mdx:246`,
`references/kernel/manifest.mdx:100`) already say `ignore` is the
default and `replace` inserts into a table the caller cleared; they pick
up the spec wording below when it lands. No hand-written page outside
`seed-data.mdx` restates them wrongly. Releases pages were not read for
edits (release-owned).
Outside both lanes, found while measuring:
`packages/cli/src/commands/diff.ts:81` still says `replace` means "a
replay deletes every row of the object and inserts these". It is CLI
output text, carried on #31099; not touched here.
### Spec wording owed (spec lane, item 3, filed as #31099; not edited
here)
`packages/spec/src/data/seed.zod.ts`:
- `:27` enum comment `'replace', // Delete ALL records in object then
insert (Dangerous - use for cache tables)` → e.g. `// Insert with no
match; the loader deletes nothing, the caller clears the table (cache
tables)`.
- `:24` enum comment `'insert', // Try to insert, fail on duplicate` →
e.g. `// Insert every record with no match: a replay duplicates rows
unless a unique index refuses them`.
- `:171-172` docstring "a load inserts the records whose row is missing"
and `:195-196` `.describe()` "a missing row is inserted and an existing
row is left as it is" → add: a missing row whose key already landed in
the organization (a person removed it) is not inserted again by
AppPlugin's loads (boot, per-organization replay, dev hot reload); the
`seed-replayer` service with `{ reseed: true }` inserts it.
- `:116-117` the strict-object `history` string "on the defaults (`mode:
upsert`, …)" describes the default at the time; accurate as history,
optional to annotate.
## Tests
None: the diff is teaching text and one eval expectation, with no
runtime behaviour to test. The gates below are the checks.
## Verification
<details><summary>Local gates on b739914: 68 derived, 68 run with exit
0, 0 NOT MEASURED (`dispatch-gates --ran`)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 68 commands (43 pnpm, 25 direct
node) for the 4 changed paths. Each ran under a 900 s timeout with
`NODE_OPTIONS=--max-old-space-size=8192`, its exit code captured before
any pipe, over the full `pnpm build` (exit 0) made at 6ed361c (the
review commit touches no built package). All 68 exited 0 at 6ed361c and
again at b739914.
- They include `check-skills-token-ratchet.mjs`, `check:skill-examples`,
`check:skill-refs`, `check:skill-compatibility`,
`check:skill-frame-sync`, `check:skill-identifier-liveness`,
`check:doc-authoring`, `check:doc-anchors`, `check:docs`,
`check:dts-closure`, `check:dual-build-cjs-loads`,
`check-changeset-no-major.mjs`, `check-empty-changeset.mjs` and
`check-comment-mask-corpus.mjs`.
- `--ran` printed: `68 derived famil(ies) accounted for — 68 run, 0
NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of
them is 3)`.
- Also run, outside the derived list: `pnpm check:changeset-fixed` (exit
0), the one artifact-roster family whose roster sits under `.changeset`.
</details>
- `node scripts/pm/check-governed-merges.mjs --branch
claude/project-thread-ba8x76`: GOVERNED, Tier H (人合): `skills/**`; 86
changed lines (+63 / −23), under 3000.
- `node scripts/check-commit-card-trailers.mjs --range
origin/main..HEAD`: exit 0 before each push.
- `main` moved after the branch point; none of its commits touches these
files or the ratchet.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No schema touched.)
- [x] 2. Every name I added uses the casing for its kind. (No names
added.)
- [x] 3. No formatter ran. (Markdown, MDX and one JSON string only.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. `skills/**` publishes `@objectstack/skills`, so the PR carries
a `'@objectstack/skills': patch` changeset. Both declaration lines are
filled in above.
- [x] 6. `packages/spec` not touched; nothing to regenerate.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (The 维护者速读 section is
Chinese, per the lane template.)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J8VVAkA4qny8vRgP2NzRgs
<!-- signed: domain:skills#3 · huangyiirene ·
session_01J8VVAj7CvjkGXvgXzd6RZ7 -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
docs(skills): the objectstack-data seeds reference now teaches the seed modes the loader runs: `ignore` is the default (create-only), `replace` deletes nothing (it inserts into a table the caller cleared), and `insert` duplicates on each replay. It also says that AppPlugin's boot and organization replays do not re-insert a seed row a person deleted (a publish or package install keeps no record, so the row comes back there), and that `seed-replayer` with `{ reseed: true }` restores it. The data-modeling seed guide teaches the same rule and names the loads that keep no record.
Copy file name to clipboardExpand all lines: content/docs/data-modeling/seed-data.mdx
+34-2Lines changed: 34 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -71,8 +71,8 @@ customer has since edited.
71
71
72
72
| Mode | Behavior | Use Case |
73
73
|:-----|:---------|:---------|
74
-
|`ignore`|**Default.** Create-only: insert a record whose row is missing, leave an existing row exactly as it is | Business and starter data the customer owns once it is loaded |
75
-
|`upsert`| Create if new, and on **every** replay write each declared column back onto the matched row | Reference data the package must keep current |
74
+
|`ignore`|**Default.** Create-only: insert a record whose row is missing (a boot or organization replay skips one a person [deleted](#a-deleted-seed-row-stays-deleted)), leave an existing row exactly as it is | Business and starter data the customer owns once it is loaded |
75
+
|`upsert`| Create if new (a boot or organization replay skips one a person [deleted](#a-deleted-seed-row-stays-deleted)), and on **every** replay write each declared column back onto the matched row | Reference data the package must keep current |
76
76
|`insert`| Insert **every** record unconditionally — no existing-row check, so it **duplicates the table on each replay boot** ([#3434](https://github.com/objectstack-ai/objectstack/issues/3434)). Rarely what you want | Append-only rows loaded exactly once (never re-run) |
77
77
|`update`| Update only, skip if not found | Migration patches on existing rows |
78
78
|`replace`| Insert without checking for an existing match — the seed loader does **not** delete anything itself; the target table must already be empty (or cleared by the caller) | Cache / lookup tables rebuilt from an already-truncated table |
@@ -153,6 +153,38 @@ check**, so a replayed `insert` dataset duplicates its table on each restart
153
153
It is almost never the right mode: leave `mode` unset (create-only), or declare
154
154
`upsert` for reference data, with a stable `externalId` instead. `os validate` warns on any `mode: 'insert'` seed.
155
155
156
+
### A Deleted Seed Row Stays Deleted
157
+
158
+
Each organization records which seed rows landed in it, by `externalId`, in the
159
+
platform object `sys_seed_landed_key`. When a person deletes a seeded row, the
160
+
next boot or organization replay does not insert it again: an `ignore` or
161
+
`upsert` dataset inserts a missing row only if its key never landed in that
162
+
organization. A row an upgrade adds to the seed has never landed, so it
163
+
is still inserted. A reference to the deleted row from another seed row is left
164
+
unwritten rather than reported as an error.
165
+
166
+
The record is kept and honoured only where AppPlugin runs the load for a known
167
+
organization: the single-tenant boot seed (`objectstack serve`, `os start`, on
168
+
every restart; a multi-tenant boot seeds through the per-organization replay
169
+
instead), the per-organization replay (`seed-replayer`), and the dev hot reload.
170
+
A seed publish, a package install or upgrade (`os package install`, the
171
+
marketplace install, its heal and its sample-data reseed) keep no record, so a
172
+
deleted row comes back on those. So do a load with no organization, a
173
+
composition that does not register `sys_seed_landed_key`
174
+
(`@objectstack/platform-objects`), and the marketplace's fallback
175
+
`seed-replayer`, which it registers when no app has seed data.
176
+
177
+
A row deleted before your runtime had this rule comes back once, on the first
178
+
replay after the upgrade, and stays deleted from then on.
179
+
180
+
To bring the deleted rows back on purpose, a host or plugin calls the
181
+
`seed-replayer` kernel service with `{ reseed: true }`:
182
+
183
+
```typescript
184
+
const replay =ctx.getService('seed-replayer'); // ctx: the plugin's context
Copy file name to clipboardExpand all lines: skills/objectstack-data/evals/hooks-security-seeds-datasources.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -33,8 +33,8 @@
33
33
},
34
34
{
35
35
"id": 4,
36
-
"prompt": "Seed the `country` table for every environment; add demo `opportunity` rows (dev/test only) closing 45 days after install, owned by the installer. A nightly job advances `stage`; a seed replay must not reset it.",
37
-
"expected_output": "`defineSeed(Country, { externalId: 'code', mode: 'upsert', records })` for reference data; `defineSeed(Opportunity, { env: ['dev', 'test'], ... })` for demo rows — natural-key lookups, CEL values (`close_date: cel`daysFromNow(45)``, `owner_id: cel`os.user.id``), parents first; `columns: { stage: { applyOn: 'create' } }` so an `upsert` replay leaves the job-owned column.",
36
+
"prompt": "Seed the `country` table for every environment; add demo `opportunity` rows (dev/test only) closing 45 days after install, owned by the installer, and refreshed from the seed on every replay except `stage`: a nightly job advances it, and a replay must not reset it.",
37
+
"expected_output": "`defineSeed(Country, { externalId: 'code', mode: 'upsert', records })` for reference data; `defineSeed(Opportunity, { mode: 'upsert', env: ['dev', 'test'], ... })` for the refreshed demo rows — natural-key lookups, CEL values (`close_date: cel`daysFromNow(45)``, `owner_id: cel`os.user.id``), parents first; `columns: { stage: { applyOn: 'create' } }` so the `upsert` replay leaves the job-owned column (the `ignore` default would refresh nothing).",
0 commit comments