From eabcc366651fd42a911f6b7a2d211090f6938c51 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 07:36:16 +0000 Subject: [PATCH 1/3] fix(spec): Field.autonumber declares readonly: true (#5628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FieldSchema.readonly` is a two-part contract: "never editable in forms" AND server-enforced on both write paths. #5503 closed the server half for `autonumber` BY TYPE — a caller-supplied record number is stripped before any driver sees it, flag or no flag. The form half is keyed on the FLAG, and the builder never set it, so a renderer deciding editability from `field.readonly` drew an editable "record number" input whose value the server was already guaranteed to discard. `Field.autonumber(...)` now emits `readonly: true`, applied AFTER the author's config so it cannot be spread away, with the authoring type narrowed to `readonly?: true` — `Field.autonumber({ readonly: false })` is a compile error at the metadata site rather than a silently coerced value. A hand-written `{ type: 'autonumber' }` literal is unaffected: it is covered by the by-type server enforcement, which never depended on the flag. Measured side effect, neutralized in the same change: the DataProtocol create ingress (`stripReadonlyForInsert`, #3043) knows only the `isSystem` exemption, while the engine's runtime-owned strip also honours `preserveAudit` (#3493). With the flag present the ingress would have deleted a historical import's legacy record number BEFORE the engine could keep it — and no test would have gone red, because every existing preserveAudit pin calls `engine.insert` directly. The ingress now skips runtime-owned field types outright and leaves them to the engine strip, which runs on every insert path including the direct `engine.insert` callers the ingress never sees. Author-declared `readonly` on every other type keeps its full #3043 width. The backing set moves to `@objectstack/spec/data` as `RUNTIME_OWNED_FIELD_TYPES` — the protocol's one statement of the ownership — now that a second consumer needs it; objectql keeps the reasoning comment and imports the membership. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY --- .changeset/autonumber-builder-readonly.md | 48 ++++++++ packages/metadata-protocol/src/protocol.ts | 18 +++ .../engine-autonumber-runtime-owned.test.ts | 110 ++++++++++++++++++ .../objectql/src/validation/rule-validator.ts | 11 +- packages/spec/api-surface/data.json | 1 + .../data/field-autonumber-readonly.test.ts | 94 +++++++++++++++ packages/spec/src/data/field.zod.ts | 43 ++++++- 7 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 .changeset/autonumber-builder-readonly.md create mode 100644 packages/spec/src/data/field-autonumber-readonly.test.ts diff --git a/.changeset/autonumber-builder-readonly.md b/.changeset/autonumber-builder-readonly.md new file mode 100644 index 0000000000..0fb6abb06c --- /dev/null +++ b/.changeset/autonumber-builder-readonly.md @@ -0,0 +1,48 @@ +--- +"@objectstack/spec": minor +"@objectstack/metadata-protocol": patch +--- + +feat(spec): `Field.autonumber` declares the field `readonly: true` (#5628) + +`FieldSchema.readonly` is a **two-part** contract: "never editable in forms" +AND server-enforced on both write paths. #5503 closed the server half for +`autonumber` **by type** — a caller-supplied record number is stripped before +any driver sees it, flag or no flag. The form half is keyed on the **flag**, and +`Field.autonumber` never set it. So an authoring/rendering layer that decides +editability from `field.readonly` drew an editable "record number" input whose +value the server was already guaranteed to discard: the user types one, the +create succeeds, and the record comes back carrying the number the sequence +issued instead. Data was never at risk (that half has been enforced since +#5503/#5627); what was wrong is what the form told the user. + +`Field.autonumber(...)` now emits `readonly: true`. The injection is applied +**after** the author's config, so it cannot be spread away, and the authoring +type rejects the one config that contradicts it — `Field.autonumber({ readonly: +false })` is a **compile error** rather than a silently coerced value, because +an "editable record number" is not a state the runtime can deliver. Restating +`readonly: true` stays legal. A hand-written `{ type: 'autonumber' }` literal +(YAML/JSON metadata, or a plain object in TS) is unchanged and unaffected: it is +covered by the by-type server enforcement, which never depended on the flag. + +Two consequences worth knowing: + +- **A flow that writes an autonumber field is now caught at `os validate`.** + `flow-update-readonly-field` reads the static flag, so an `update_record` node + writing a builder-authored record number — already a silent no-op at run time + — is now reported at design time instead of in server WARN logs. +- **The historical-import exemption is unchanged**, and stays that way by + construction. The DataProtocol create ingress (`stripReadonlyForInsert`, + #3043) knows only the `isSystem` exemption, while the engine's runtime-owned + strip also honours `preserveAudit` (#3493 — a migration reinstating legacy + record numbers). Now that the field carries the flag, the ingress would have + deleted that value *before* the engine could keep it, so the ingress skips + runtime-owned field types outright and leaves them to the engine strip, which + runs on every insert path (including the direct `engine.insert` callers the + ingress never sees). Author-declared `readonly` on every other field type is + stripped at the ingress exactly as wide as before. + +The set backing "which types the runtime owns" is now declared once in the +protocol — `RUNTIME_OWNED_FIELD_TYPES`, exported from `@objectstack/spec/data` +— and read by both consumers (objectql's write-path strips, the DataProtocol +ingress) instead of each carrying its own literal. diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index 6a46349b5b..e71963491f 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -28,6 +28,7 @@ import { parseFilterAST, isFilterAST, VALID_AST_OPERATORS, REFERENCE_VALUE_TYPES, referenceTargetOf, AggregationFunction, DateGranularity, resolveSearchFieldResolution, SEARCHABLE_TEXTUAL_TYPES, SEARCHABLE_ENUM_TYPES, SEARCH_AUTO_EXCLUDED_FIELDS, + RUNTIME_OWNED_FIELD_TYPES, RPC_QUERY_ALIAS_SLOTS, foldQueryAliasSlots, type QueryAliasConflict, type QueryAliasSlot, type DroppedFieldsEvent, type QueryAST, type EngineQueryOptionsParsed, @@ -1026,6 +1027,20 @@ const CLONE_STRIP_FIELDS: readonly string[] = [ * reject. The #3043 threat is app approval/status/verdict fields (the issue's * `sporadic_application` / `assessment`), never `sys_`; this is the same * platform-vs-authored boundary `applySystemFields` uses for ownership. + * + * SCOPE, second boundary — RUNTIME-OWNED field types + * ({@link RUNTIME_OWNED_FIELD_TYPES}: today `autonumber`) are left to the + * ENGINE's own insert strip (`stripRuntimeOwnedFields`, #5503), which runs on + * every insert path including the direct `engine.insert` callers this ingress + * never sees. Skipping them here removes no protection and prevents this seam + * from PRE-EMPTING an exemption it does not implement: the engine strip honours + * `preserveAudit` (#3493 — a historical import reinstating legacy record + * numbers) while this one knows only `isSystem`. Before #5628 the distinction + * was academic, because an `autonumber` field carried no `readonly` flag for the + * loop below to notice; now that `Field.autonumber` injects one, stripping here + * would silently delete the value a historical import is entitled to keep, + * BEFORE the engine could apply the whitelist. Author-declared `readonly` on + * every other type is untouched — the #3043 strip is exactly as wide as it was. */ function stripReadonlyForInsert(schema: any, data: any, context: any): any { if (context?.isSystem) return data; @@ -1037,6 +1052,9 @@ function stripReadonlyForInsert(schema: any, data: any, context: any): any { let out = row; for (const name of Object.keys(fields)) { if (!fields[name]?.readonly) continue; + // [#5628] The engine's runtime-owned strip owns these, with the + // wider exemption set. See the note above. + if (RUNTIME_OWNED_FIELD_TYPES.has(String(fields[name]?.type ?? ''))) continue; if (!(name in out)) continue; if (out === row) out = { ...row }; delete out[name]; diff --git a/packages/objectql/src/engine-autonumber-runtime-owned.test.ts b/packages/objectql/src/engine-autonumber-runtime-owned.test.ts index 1344a436ef..275267bd88 100644 --- a/packages/objectql/src/engine-autonumber-runtime-owned.test.ts +++ b/packages/objectql/src/engine-autonumber-runtime-owned.test.ts @@ -537,3 +537,113 @@ describe('#5503 — autonumber is runtime-owned: UPDATE', () => { expect(res.account_number).toBe('HOOK-0002'); }); }); + +/** + * #5628 — the FLAGGED autonumber field: `Field.autonumber` now injects + * `readonly: true` so the form half of the `readonly` contract ("never editable + * in forms") holds for a record number the server was already guaranteed to + * discard. Every case above uses an UNflagged `type: 'autonumber'`, so none of + * them sees what that flag changes on the way in. + * + * What it changes is WHICH strip gets to the field first. A `readonly` field is + * also stripped at the DataProtocol create INGRESS (`stripReadonlyForInsert`, + * #3043) — a seam that knows only the `isSystem` exemption, while the engine's + * runtime-owned strip also honours `preserveAudit` (#3493: a historical import + * reinstating legacy record numbers). Left alone, the flag would therefore have + * SILENTLY narrowed a documented exemption: the ingress would delete the legacy + * number before the engine could keep it, with no test anywhere going red, + * because every existing preserveAudit pin calls `engine.insert` directly. + * + * So the ingress skips runtime-owned types outright (they are covered by the + * engine strip on EVERY insert path, including the direct `engine.insert` + * callers the ingress never sees) and these cases pin both halves of that: the + * ordinary caller is still stripped, and the historical import still keeps its + * value — flagged or not, the verdicts are identical. + */ +describe('#5628 — a `readonly: true` autonumber keeps the #5503 exemption set', () => { + // What `Field.autonumber({ label: 'Invoice No.' })` produces since #5628. + const INVOICE = { + name: 'an_invoice', + label: 'Invoice', + fields: { + id: { name: 'id', label: 'ID', type: 'text' as const, primaryKey: true }, + name: { name: 'name', label: 'Name', type: 'text' as const }, + invoice_number: { + name: 'invoice_number', + label: 'Invoice No.', + type: 'autonumber' as const, + readonly: true, + autonumberFormat: 'INV-{0000}', + }, + }, + }; + + let rig: Awaited>; + beforeEach(async () => { + rig = await makeEngine(); + rig.engine.registry.registerObject(INVOICE as any); + }); + + it('still strips an ordinary caller-supplied number and issues the sequence value', async () => { + const created = await rig.protocol.createData({ + object: 'an_invoice', + data: { name: 'forge', invoice_number: 'INV-9999' }, + }); + expect(created.record.invoice_number).toBe('INV-0001'); + expect(rig.createdRows.at(-1)?.invoice_number).toBe('INV-0001'); + }); + + it('still REPORTS the strip to the caller (#3407 / #3431)', async () => { + const created = await rig.protocol.createData({ + object: 'an_invoice', + data: { name: 'forge', invoice_number: 'INV-9999' }, + }); + const dropped = (created as { droppedFields?: DroppedFieldsEvent[] }).droppedFields ?? []; + expect(dropped.flatMap((e) => e.fields)).toContain('invoice_number'); + }); + + it('keeps a legacy number for a `preserveAudit` historical import THROUGH THE INGRESS (#3493)', async () => { + // The regression this whole describe exists for: the ingress strip has no + // `preserveAudit` exemption, so if it acted on the flag the value would be + // gone before the engine's whitelist ran. + const created = await rig.protocol.createData({ + object: 'an_invoice', + data: { name: 'legacy', invoice_number: 'LEGACY-0007' }, + context: { preserveAudit: true }, + }); + expect(created.record.invoice_number).toBe('LEGACY-0007'); + }); + + it('keeps an explicit number for a system write through the ingress', async () => { + const created = await rig.protocol.createData({ + object: 'an_invoice', + data: { name: 'seeded', invoice_number: 'INV-000042' }, + context: { isSystem: true }, + }); + expect(created.record.invoice_number).toBe('INV-000042'); + }); + + it('an author-declared `readonly` field of an ORDINARY type is still stripped at the ingress', async () => { + // The #3043 strip keeps its full width — only runtime-owned types moved. + rig.engine.registry.registerObject({ + name: 'an_case', + label: 'Case', + fields: { + id: { name: 'id', label: 'ID', type: 'text' as const, primaryKey: true }, + title: { name: 'title', label: 'Title', type: 'text' as const }, + approval_status: { + name: 'approval_status', + label: 'Approval', + type: 'text' as const, + readonly: true, + defaultValue: 'draft', + }, + }, + } as any); + const created = await rig.protocol.createData({ + object: 'an_case', + data: { title: 'forged', approval_status: 'approved' }, + }); + expect(created.record.approval_status).toBe('draft'); + }); +}); diff --git a/packages/objectql/src/validation/rule-validator.ts b/packages/objectql/src/validation/rule-validator.ts index 623904cb24..52cc03e0e9 100644 --- a/packages/objectql/src/validation/rule-validator.ts +++ b/packages/objectql/src/validation/rule-validator.ts @@ -190,7 +190,7 @@ import { ExpressionEngine, collectCelRootIdentifiers } from '@objectstack/formula'; import type { Expression } from '@objectstack/spec'; -import { AUDIT_PROVENANCE_FIELDS } from '@objectstack/spec/data'; +import { AUDIT_PROVENANCE_FIELDS, RUNTIME_OWNED_FIELD_TYPES } from '@objectstack/spec/data'; import Ajv, { type ValidateFunction } from 'ajv'; // #5029 — `format` is NOT built into ajv 8; it ships in this separate package. // See the `const ajv` note below for why the runtime registers it. @@ -820,8 +820,15 @@ export function stripReadonlyWhenFieldsMulti( * * Keep this set to types whose value is (a) persisted, (b) issued by the * runtime, and (c) never legitimately supplied by a caller. + * + * The set itself now lives in `@objectstack/spec` (`RUNTIME_OWNED_FIELD_TYPES`, + * #5628) — the protocol's one statement of the ownership — because a SECOND + * consumer needs it: the DataProtocol create ingress, whose `readonly` strip + * carries a NARROWER exemption set than this module's (no `preserveAudit`), and + * which therefore has to recognise these types to stay out of their way. A + * literal copied over there is the drift `AUDIT_TIMELINE_FIELDS` below stopped + * paying for. This module keeps the reasoning; the membership is imported. */ -const RUNTIME_OWNED_FIELD_TYPES: ReadonlySet = new Set(['autonumber']); /** * Whether the runtime owns this field's value outright — i.e. the field is diff --git a/packages/spec/api-surface/data.json b/packages/spec/api-surface/data.json index a39209b60b..0208439aaf 100644 --- a/packages/spec/api-surface/data.json +++ b/packages/spec/api-surface/data.json @@ -419,6 +419,7 @@ "REFERENCE_VALUE_TYPES (const)", "RETIRED_FILTER_OPERATORS (const)", "RPC_QUERY_ALIAS_SLOTS (const)", + "RUNTIME_OWNED_FIELD_TYPES (const)", "RangeOperatorSchema (const)", "RecordFlow (type)", "RecordFlowContainer (type)", diff --git a/packages/spec/src/data/field-autonumber-readonly.test.ts b/packages/spec/src/data/field-autonumber-readonly.test.ts new file mode 100644 index 0000000000..f86488085d --- /dev/null +++ b/packages/spec/src/data/field-autonumber-readonly.test.ts @@ -0,0 +1,94 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * #5628 — `Field.autonumber` declares the field `readonly: true`. + * + * `FieldSchema.readonly` is a TWO-part contract: "never editable in forms" AND + * server-enforced on both write paths. #5503 / PR #5627 closed the server half + * for `autonumber` BY TYPE ({@link RUNTIME_OWNED_FIELD_TYPES}) — a + * caller-supplied record number is stripped before any driver sees it, flag or + * no flag. The FORM half is keyed on the FLAG, and the builder did not set it, + * so an authoring/rendering layer deciding editability from `field.readonly` + * drew an editable "record number" input whose value the server was already + * guaranteed to discard: the user types one, the create succeeds, and the + * record comes back carrying the number the sequence issued instead (#4632's + * "second-class" shape — the write path reports the drop in `droppedFields`, + * but a renderer need not surface that). + * + * These cases pin the flag on the builder's output, and the AUTHORING-TIME + * verdict on the one config that contradicts it. `readonly: false` on an + * autonumber field is not a preference the runtime can honour — the value is + * issued by the engine or the driver's persistent sequence, so an "editable + * record number" cannot exist. It is rejected at the authoring site by `tsc` + * (a loud compile error where the metadata is written) rather than accepted and + * silently coerced, which is the lenient-consumer shape ADR-aligned authoring + * exists to avoid. + */ + +import { describe, it, expect } from 'vitest'; +import { Field, FieldSchema, RUNTIME_OWNED_FIELD_TYPES } from './field.zod'; + +describe('#5628 — Field.autonumber injects readonly: true', () => { + it('declares the field read-only', () => { + const f = Field.autonumber({ label: 'Auto Number' }); + expect(f.type).toBe('autonumber'); + expect(f.readonly).toBe(true); + }); + + it('keeps every other config key the author wrote', () => { + const f = Field.autonumber({ label: 'Invoice No.', autonumberFormat: 'INV-{0000}', required: true }); + expect(f).toMatchObject({ + type: 'autonumber', + label: 'Invoice No.', + autonumberFormat: 'INV-{0000}', + required: true, + readonly: true, + }); + }); + + it('sets the flag with no config at all', () => { + expect(Field.autonumber().readonly).toBe(true); + }); + + it('restating `readonly: true` is allowed and changes nothing', () => { + expect(Field.autonumber({ readonly: true, label: 'No.' }).readonly).toBe(true); + }); + + it('rejects `readonly: false` at the authoring site (compile error), not at runtime', () => { + // The pin: this line must NOT compile. `tsconfig.test.json` puts this file + // in front of `tsc` (#5286), so the directive is a real check — delete the + // injection's type narrowing and `pnpm --filter @objectstack/spec typecheck` + // fails on an unused @ts-expect-error. + // @ts-expect-error an autonumber field is runtime-owned: `readonly` is always true + const f = Field.autonumber({ readonly: false }); + // And a caller that reaches the builder from untyped JS (or through a cast) + // still gets the flag: the injection is applied AFTER `config`, so it cannot + // be spread away. Silent coercion is acceptable ONLY because the authoring + // surface above rejects the same input loudly. + expect(f.readonly).toBe(true); + }); + + it('the builder output parses as a valid field (the flag is a real authoring key)', () => { + const parsed = FieldSchema.safeParse({ + name: 'invoice_number', + label: 'Invoice No.', + ...Field.autonumber({ autonumberFormat: 'INV-{0000}' }), + }); + expect(parsed.success).toBe(true); + expect(parsed.success && parsed.data.readonly).toBe(true); + }); +}); + +describe('#5628 / #5503 — RUNTIME_OWNED_FIELD_TYPES is the protocol vocabulary', () => { + it('names `autonumber`', () => { + expect(RUNTIME_OWNED_FIELD_TYPES.has('autonumber')).toBe(true); + }); + + it('does NOT name the other calculated types, nor ordinary ones', () => { + // `formula` is computed on read (no stored caller value to strip); + // `summary` is a stored roll-up a caller MAY legitimately seed (#6014). + for (const t of ['formula', 'summary', 'text', 'number']) { + expect(RUNTIME_OWNED_FIELD_TYPES.has(t)).toBe(false); + } + }); +}); diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index 3094253293..39b08f27ea 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -78,6 +78,25 @@ export const FieldType = z.enum([ export type FieldType = z.input; +/** + * Field types whose stored value the RUNTIME owns outright — issued by the + * engine (or the driver's persistent sequence), never supplied by a caller on + * either write path. Today exactly `autonumber` (#5503). + * + * This is the PROTOCOL's statement of that ownership, so the consumers that act + * on it read one vocabulary instead of each carrying its own literal: objectql's + * write-path strips (`isRuntimeOwnedField` / `stripRuntimeOwnedFields`, which + * treat these types as implicitly read-only), and the DataProtocol create + * ingress, which defers to those strips rather than pre-empting them with its + * own narrower exemption set (`stripReadonlyForInsert`, #5628). + * + * Keep the set to types whose value is (a) persisted, (b) issued by the runtime, + * and (c) never legitimately supplied by a caller. `formula` and `summary` are + * deliberately NOT here: they are derived-on-read/roll-up, not stored values a + * caller could forge into a sequence. + */ +export const RUNTIME_OWNED_FIELD_TYPES: ReadonlySet = new Set(['autonumber']); + /** * Select Option Schema * @@ -910,7 +929,29 @@ export const Field = { avatar: (config: FieldInput = {}) => ({ type: 'avatar', ...config } as const), formula: (config: FieldInput = {}) => ({ type: 'formula', ...config } as const), summary: (config: FieldInput = {}) => ({ type: 'summary', ...config } as const), - autonumber: (config: FieldInput = {}) => ({ type: 'autonumber', ...config } as const), + /** + * Auto-number — a record number the RUNTIME issues from its sequence. + * + * The builder injects `readonly: true` (#5628). `readonly` is a TWO-part + * contract (see `FieldSchema.readonly`): "never editable in forms" AND + * server-enforced on both write paths. #5503 closed the server half for + * `autonumber` by type ({@link RUNTIME_OWNED_FIELD_TYPES}), but the FORM half + * is keyed on the flag — so without it a renderer drew an editable "record + * number" box whose value the server was already guaranteed to discard: the + * user types a number, the create succeeds, and the record comes back + * carrying a different one. Declaring the flag the builder's output already + * behaves like is the shortest "declared = enforced" path. + * + * The injection is UNCONDITIONAL — it is applied after `config`, so it cannot + * be spread away — and `readonly: false` is a compile error at the authoring + * site rather than a silent coercion: an autonumber field is runtime-owned by + * construction, so "editable record number" is not a state the author can + * ask for. Restating `readonly: true` is allowed (it is merely redundant). + * A hand-written `{ type: 'autonumber' }` literal is unaffected — it is + * covered by the by-TYPE server enforcement, which never depended on the flag. + */ + autonumber: (config: FieldInput & { readonly?: true } = {}) => + ({ type: 'autonumber', ...config, readonly: true } as const), markdown: (config: FieldInput = {}) => ({ type: 'markdown', ...config } as const), html: (config: FieldInput = {}) => ({ type: 'html', ...config } as const), password: (config: FieldInput = {}) => ({ type: 'password', ...config } as const), From ca657d269784633cc3c05011a9de6f8d80c16040 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 08:36:01 +0000 Subject: [PATCH 2/3] chore(spec): finish the post-merge wholesale regen (docs references, api-surface) Recovery commit: completes the regen the dev agent never reached before the container restart. check:generated 10/10. --- packages/spec/api-surface/data.json | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/spec/api-surface/data.json b/packages/spec/api-surface/data.json index 0208439aaf..e825bd1377 100644 --- a/packages/spec/api-surface/data.json +++ b/packages/spec/api-surface/data.json @@ -10,9 +10,10 @@ "AUDIT_PROVENANCE_FIELDS (const)", "Address (type)", "AddressSchema (const)", + "AddressValue (type)", "AddressValueSchema (const)", - "AggregationFunction (const)", - "AggregationMetricType (const)", + "AggregationFunction (type)", + "AggregationMetricType (type)", "AggregationNode (type)", "AggregationNodeSchema (const)", "AggregationPipeline (type)", @@ -50,7 +51,9 @@ "CONTEXT_TOKEN_DESCRIPTIONS (const)", "CONTEXT_TOKEN_SUGGESTIONS (const)", "CONTEXT_TOKEN_WRAPPED_RE (const)", + "CalendarDateValue (type)", "CalendarDateValueSchema (const)", + "ClockTimeValue (type)", "ClockTimeValueSchema (const)", "ComparisonOperatorSchema (const)", "Compatibility (type)", @@ -60,6 +63,7 @@ "ConsistencyLevel (type)", "ConsistencyLevelSchema (const)", "ContextToken (type)", + "ContextTokenPlaceholder (type)", "ContextTokenPlaceholderSchema (const)", "ContextTokenSchema (const)", "CrossFieldValidation (type)", @@ -104,6 +108,7 @@ "DataEngineDeleteOptions (type)", "DataEngineDeleteOptionsSchema (const)", "DataEngineDeleteRequestSchema (const)", + "DataEngineExecuteRequest (type)", "DataEngineExecuteRequestSchema (const)", "DataEngineFilter (type)", "DataEngineFilterSchema (const)", @@ -111,6 +116,7 @@ "DataEngineFindRequestSchema (const)", "DataEngineInsertOptions (type)", "DataEngineInsertOptionsSchema (const)", + "DataEngineInsertRequest (type)", "DataEngineInsertRequestSchema (const)", "DataEngineQueryOptions (type)", "DataEngineQueryOptionsParsed (type)", @@ -124,6 +130,7 @@ "DataEngineUpdateOptions (type)", "DataEngineUpdateOptionsSchema (const)", "DataEngineUpdateRequestSchema (const)", + "DataEngineVectorFindRequest (type)", "DataEngineVectorFindRequestSchema (const)", "DataTypeMapping (type)", "DataTypeMappingSchema (const)", @@ -134,6 +141,7 @@ "DateGranularityValue (type)", "DateMacroDirection (type)", "DateMacroParamMatch (interface)", + "DateMacroPlaceholder (type)", "DateMacroPlaceholderSchema (const)", "DateMacroToken (type)", "DateMacroTokenSchema (const)", @@ -141,7 +149,7 @@ "DefaultValueToken (type)", "Dimension (type)", "DimensionSchema (const)", - "DimensionType (const)", + "DimensionType (type)", "DisplayNameObjectMeta (interface)", "Document (type)", "DocumentParsed (type)", @@ -168,8 +176,9 @@ "DriverInterfaceSchema (const)", "DriverOptions (type)", "DriverOptionsSchema (const)", + "DriverSslToggle (type)", "DriverSslToggleSchema (const)", - "DriverType (const)", + "DriverType (type)", "DroppedFieldsEvent (type)", "DroppedFieldsEventSchema (const)", "ESignatureConfig (type)", @@ -235,11 +244,14 @@ "FieldReferenceSchema (const)", "FieldSchema (const)", "FieldType (type)", + "FileLikeValue (type)", "FileLikeValueSchema (const)", "FilePersistenceConfig (type)", "FilePersistenceConfigParsed (type)", "FilePersistenceConfigSchema (const)", + "FileReferenceIdValue (type)", "FileReferenceIdValueSchema (const)", + "FileValue (type)", "FileValueSchema (const)", "Filter (type)", "FilterArray (type)", @@ -289,6 +301,7 @@ "ImportFieldMappingSchema (const)", "IndexSchema (const)", "InjectedSystemColumnPlan (interface)", + "InstantValue (type)", "InstantValueSchema (const)", "JSONValidation (type)", "JSONValidationParsed (type)", @@ -306,6 +319,7 @@ "LocalStoragePersistenceConfigSchema (const)", "LocationCoordinates (type)", "LocationCoordinatesSchema (const)", + "LocationValue (type)", "LocationValueSchema (const)", "LogicalOperatorKey (type)", "MAX_BULK_PER_ROW_HOOK_ROWS (const)", @@ -427,6 +441,7 @@ "RecordSurface (type)", "RecordSurfaceOptions (interface)", "RecordSurfaceViewport (type)", + "ReferenceIdValue (type)", "ReferenceIdValueSchema (const)", "ReferenceResolution (type)", "ReferenceResolutionError (type)", @@ -541,9 +556,9 @@ "TemporalWriterForm (type)", "TenancyConfig (type)", "TenancyConfigSchema (const)", - "TimeUpdateInterval (const)", + "TimeUpdateInterval (type)", "TitleEligibleFieldDef (interface)", - "TransformType (const)", + "TransformType (type)", "UniqueScope (type)", "UniqueScopeSchema (const)", "UnknownAuthoringKeyFinding (interface)", From 53209f6cc48361bae46019a362842b5dbeda01d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 09:12:39 +0000 Subject: [PATCH 3/3] test(objectql): type the new runtime-owned ingress cases cleanly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check:type-check-debt --re-measure caught +3 in objectql's TEST_DEBT layer (355 -> 358): the new cases copied the file's frozen-debt idioms — one-arg registerObject (packageId is required) and .at() under the package's lib target. Fixed to the file's clean two-arg idiom and indexed access; raw count back to 355, the 32-case suite stays green. --- .../objectql/src/engine-autonumber-runtime-owned.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/objectql/src/engine-autonumber-runtime-owned.test.ts b/packages/objectql/src/engine-autonumber-runtime-owned.test.ts index 275267bd88..0be82815b2 100644 --- a/packages/objectql/src/engine-autonumber-runtime-owned.test.ts +++ b/packages/objectql/src/engine-autonumber-runtime-owned.test.ts @@ -581,7 +581,7 @@ describe('#5628 — a `readonly: true` autonumber keeps the #5503 exemption set' let rig: Awaited>; beforeEach(async () => { rig = await makeEngine(); - rig.engine.registry.registerObject(INVOICE as any); + rig.engine.registry.registerObject(INVOICE as any, 'test'); }); it('still strips an ordinary caller-supplied number and issues the sequence value', async () => { @@ -590,7 +590,7 @@ describe('#5628 — a `readonly: true` autonumber keeps the #5503 exemption set' data: { name: 'forge', invoice_number: 'INV-9999' }, }); expect(created.record.invoice_number).toBe('INV-0001'); - expect(rig.createdRows.at(-1)?.invoice_number).toBe('INV-0001'); + expect(rig.createdRows[rig.createdRows.length - 1]?.invoice_number).toBe('INV-0001'); }); it('still REPORTS the strip to the caller (#3407 / #3431)', async () => { @@ -639,7 +639,7 @@ describe('#5628 — a `readonly: true` autonumber keeps the #5503 exemption set' defaultValue: 'draft', }, }, - } as any); + } as any, 'test'); const created = await rig.protocol.createData({ object: 'an_case', data: { title: 'forged', approval_status: 'approved' },