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
feat(spec)!: media accept/maxSize declared + enforced, stored form is a reference — ADR-0104 D3 wave 2 (PR-5a) (#3555)
* feat(spec)!: media accept/maxSize declared + enforced, stored form is a reference — ADR-0104 D3 wave 2 (PR-5a)
accept and maxSize are now declared on FieldSchema, and enforced on the server.
Both were already READ by the upload widgets — field.accept, field.maxSize —
while the spec did not declare them, so an author who wrote them had the keys
silently stripped at parse and the constraint simply never existed. That is
exactly the ADR-0104 failure class this ADR exists to remove: a declaration
accepted in source, dropped from the contract, with no feedback anywhere.
Now that the platform owns the file, sys_file carries the authoritative MIME
type and byte size, so a record write is re-checked where the constraint
actually binds rather than only in the browser — a client-side check is a
convenience, not a control, since any caller talking to the API directly
bypasses it. Violations raise FileConstraintError and fail the write. The check
rides the pass that already loads each referenced sys_file row, so it costs no
extra read, and an entry is only judged against metadata the file actually
reports: no recorded MIME type cannot fail an accept test, no recorded size
cannot fail maxSize. "We don't know" must not become "not permitted" — a test
caught that distinction being got wrong.
The stored form of a media field narrows to an opaque sys_file id.
valueSchemaFor(field, 'stored') now yields an id for the whole media family;
the inline {url, name, size, …} blob becomes the 'expanded' read form, which
also still admits an unresolved id (storage service absent, file not committed)
exactly as an unexpanded lookup id stays valid. Two legacy forms stop
conforming, both deliberately: the inline blob, which is no longer stored but
derived; and an external URL, which was never a managed file — R7 retires those
toward an explicit `url` field, and under AI authoring that IS the point, since
it stops "managed file" and "external link" being the same declaration.
Not a breaking change today. Value-shape checking is warn-first (R1/R2): a
not-yet-backfilled row still writes and the author gets a warning naming the
field. Hard rejection arrives only under OS_DATA_VALUE_SHAPE_STRICT_ENABLED,
which a deployment opts into after running the backfill and confirming
reconciliation. The `!` marks the contract change for the v17 window, not a
runtime break on upgrade.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
* chore(spec): register accept/maxSize in the field liveness ledger
The spec-property liveness gate (ADR-0049, declared != enforced) flagged both
new properties as UNCLASSIFIED — which is the gate doing exactly its job, since
the whole reason these two are being added is that they were read by widgets
while nothing in the contract declared or enforced them.
Both are registered live, with the server-side enforcement site as evidence
rather than the widget that merely offers them to the file picker: a
client-side check is bypassed by any caller talking to the API directly, so it
is not what makes the property live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/references/data/field.mdx
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,6 +113,8 @@ const result = Address.parse(data);
113
113
|**scale**|`number`| optional | Decimal places |
114
114
|**min**|`number`| optional | Minimum value |
115
115
|**max**|`number`| optional | Maximum value |
116
+
|**accept**|`string[]`| optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. |
117
+
|**maxSize**|`integer`| optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. |
|**reference**|`string`| optional | Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. |
118
120
|**deleteBehavior**|`Enum<'set_null' \| 'cascade' \| 'restrict'>`| optional | What happens if referenced record is deleted |
0 commit comments