The type mapper lowered the type tsc infers from an empty object literal
(`const o = {}`) as the empty record, while a declared `{}` already lowered
as a checked-dynamic value. tsc's `{}` admits every non-nullish value in
both cases: union subtype reduction folds primitives into a literal's `{}`
(`["12", {}]` is `{}[]`, `flag ? "x" : {}` is `{}`), and `let o = {}`
accepts `o = "x"`. Such a program built and then threw a TypeError at run
time ("expected object at $[0], got string" in a JavaScript file, "a
'string' value is not representable in the target union" in TypeScript),
where Node prints the values.
An empty object type with no members, index signatures or signatures now
lowers as a checked-dynamic value, the same rule as `object` and `Object`.
As an arm of a union that keeps another non-nullish arm, which subtype
reduction would have removed, it holds only the literal's objects and
stays the empty record.
An assertion's scalar check (`as string`, `as number`, `as boolean`) on a
dynamic value whose result goes straight back into a dynamic slot keeps
the original value, since JavaScript erases assertions: passing `{} as
unknown as string` to process.loadEnvFile reports Node's
ERR_INVALID_ARG_TYPE instead of a TypeError from the check.
Object.assign's two-argument path for a dynamic target now applies the
same refusal as the general path when a class instance is copied into a
fresh object literal whose result the checker types as the class, so
`Object.assign({}, instance)` keeps its compile-time refusal now that the
literal lowers as a dynamic value.