Skip to content

seed-loader: a dropped reference FIELD is invisible in the summary — totalErrored counts dropped records only, so the boot banner reads clean #3932

Description

@os-zhuang

Split out of #3911 per Prime Directive #10 — out of scope for the fix in #3926, but it is the reason that bug felt silent to the reporter.

The gap

SeedLoaderService has two distinct failure outcomes, and only one of them is counted:

Outcome errors[] errored / totalErrored Row written?
Record dropped (unresolvable ref, no pass 2; write threw; unresolvable CEL) no
Field dropped (invalid reference value → delete record[ref.field]) not incremented yes, minus the association

The field-drop paths (packages/metadata-protocol/src/seed-loader.ts) push a ReferenceResolutionError and log a warn, but never errored++. result.success does flip to false (it reads allErrors.length), so the load is not silent — but every count understates the damage.

Why it matters downstream

packages/runtime/src/app-plugin.ts feeds the CLI boot banner from the summary:

recordSeedOutcome(ctx, { source, inserted, updated, skipped, rejected: totalErrored });

A seed that writes 10 rows, 3 of which lost a lookup, reports rejected: 0. The banner (the one signal that survives os dev's boot-quiet window and the default warn log level — the whole point of #3415/#3430) reads clean. And the warn that does fire says:

[Seeder] Seed loading completed with 0 dropped record(s) and 1 error(s) for <app>

"0 dropped record(s)" is literally true and actively misleading: nothing was dropped — a field was.

Repro

Any invalid reference value on a lookup field, e.g. the wrapper form:

defineSeed(Contact, { externalId: 'email', records: [
  { email: 'a@example.com', account: { externalId: 'Acme' } },  // object, not a natural key
]});

→ row lands without account; summary.totalErrored === 0; banner shows rejected: 0.

Before #3926 a natural-key array on a multiple: true lookup hit the same path — which is exactly why #3911 was reported as "记录落库但整个关联字段缺失 … 仅在日志出现一条 warn".

Suggested direction

Not just errored++ on the field-drop paths — that would overstate the other direction (the row was written, so counting it as a rejected record makes inserted + updated + skipped + rejected stop reconciling against total). Probably a distinct counter, e.g. referencesDropped on SeedLoadResult / SeedLoaderSummary, surfaced by recordSeedOutcome as its own banner column so "wrote the row, lost the link" is a first-class, countable outcome rather than an inference from success: false.

Worth deciding deliberately since it touches the SeedLoaderResult contract in packages/spec/src/data/seed-loader.zod.ts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions