Part of objectstack-ai/hotcrm#689 (never mirrored until now). Measured on @objectstack/*17.0.0 GA during HotCRM's GA close-out sweep (hotcrm#1150 / #1152), using that card's own reproduction verbatim.
The client-facing behaviour is correct and unchanged — a clean 400 INVALID_FIELD. This is about what happens behind it. Two halves, which have different states.
Half A — validation ordering: REPRODUCES
An unknown field is still rejected by the driver, not by schema validation, and everything upstream of the statement has already run.
POST /api/v1/data/crm_account {"name":"P689 probe","zzz_nonexistent_field":"x"}
-- 400 {"error":"Unknown field 'zzz_nonexistent_field' on object 'crm_account'","code":"INVALID_FIELD"}
server log:
ERROR Insert operation failed {"object":"crm_account","error":{"message":
"insert into `crm_account` (`account_number`, `created_at`, `created_by`, `id`, `is_active`,
`name`, `name_normalized`, `organization_id`, `owner_id`, `territory`, `tier`, `type`,
`updated_at`, `updated_by`, `zzz_nonexistent_field`) values ('ACC-000011',
'2026-08-14T13:08:49.769Z', 'DWci...', 'q-UpNH4tqX3n3wKP', true, 'P689 probe ...',
'p689 probe ...', 'org_mssy5q4o0pc5iqui', ...) returning *
- table crm_account has no column named zzz_nonexistent_field"
An id, an auto-number, a normalized name, owner/creator resolution and three column defaults were all produced for a request that was about to be refused.
Hooks run too.crm_forecast uses nameField: 'display_title', so name is undeclared there:
insert into `crm_forecast` (..., `period`, `period_end`, `period_label`, `period_start`, ...)
values (..., 'quarter', '2026-09-30', 'Q3 2026', '2026-07-01', ...)
- table crm_forecast has no column named name
period_label = 'Q3 2026' was never sent by the caller; it is derived by the app's beforeInsert hook. The hook executed on a request that was then rejected.
Independent of the log, the auto-number sequence shows the same thing — this observable does not depend on log contents at all:
valid create before the bad request : ACC-000013
the bad request : 400 INVALID_FIELD
valid create after the bad request : ACC-000015
-- gap across the rejected request : 1 (the refused request consumed ACC-000014)
Half B — value logging: PARTLY fixed, the exposure survives
| sub-claim on hotcrm#689 | GA state |
|---|
the entry appears as [REST] Unhandled error, a false unhandled signal | FIXED — zero [REST] Unhandled error lines carry a SqliteError or no column named in a full run; the entry is now a single handled ERROR Insert operation failed |
| the full INSERT with every value is written at ERROR level | REPRODUCES |
| each entry duplicates the whole statement twice (message + stack) | REPRODUCES — the stack field opens SqliteError: insert into ... with the statement again |
Confirmed with planted canaries: a request carrying description: 'DESCRIPTION-VALUE-CANARY' and a misspelled zzz_secret_field: 'SENSITIVE-CANARY-9f3a2b' put both literals into the log, alongside the organization id and the acting user id. A single mistyped field name in a client request is still enough to write an entire row's values to disk at ERROR level.
Suggested direction
Reject unknown fields against the object schema before hooks and before statement construction; and when a driver-level error does reach the logger, log the failing column and the object rather than the bound statement.
Evidence: hotcrm#1152 probe 50-689.mjs; readings posted on hotcrm#689.
Generated by Claude Code
Part of objectstack-ai/hotcrm#689 (never mirrored until now). Measured on
@objectstack/*17.0.0 GA during HotCRM's GA close-out sweep (hotcrm#1150 / #1152), using that card's own reproduction verbatim.The client-facing behaviour is correct and unchanged — a clean
400 INVALID_FIELD. This is about what happens behind it. Two halves, which have different states.Half A — validation ordering: REPRODUCES
An unknown field is still rejected by the driver, not by schema validation, and everything upstream of the statement has already run.
server log:
An id, an auto-number, a normalized name, owner/creator resolution and three column defaults were all produced for a request that was about to be refused.
Hooks run too.
crm_forecastusesnameField: 'display_title', sonameis undeclared there:period_label = 'Q3 2026'was never sent by the caller; it is derived by the app'sbeforeInserthook. The hook executed on a request that was then rejected.Independent of the log, the auto-number sequence shows the same thing — this observable does not depend on log contents at all:
Half B — value logging: PARTLY fixed, the exposure survives
[REST] Unhandled error, a false unhandled signal[REST] Unhandled errorlines carry aSqliteErrororno column namedin a full run; the entry is now a single handledERROR Insert operation failedstackfield opensSqliteError: insert into ...with the statement againConfirmed with planted canaries: a request carrying
description: 'DESCRIPTION-VALUE-CANARY'and a misspelledzzz_secret_field: 'SENSITIVE-CANARY-9f3a2b'put both literals into the log, alongside the organization id and the acting user id. A single mistyped field name in a client request is still enough to write an entire row's values to disk at ERROR level.Suggested direction
Reject unknown fields against the object schema before hooks and before statement construction; and when a driver-level error does reach the logger, log the failing column and the object rather than the bound statement.
Evidence: hotcrm#1152 probe
50-689.mjs; readings posted on hotcrm#689.Generated by Claude Code