Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/qa/platform-checklist/areas/api-backend.json
Original file line number Diff line number Diff line change
Expand Up @@ -2165,7 +2165,7 @@
"title": "CEL formula stdlib: all 30 bare-callable stdlib functions give known answers against the BUILT package — calendar-day timezone contract, addMonths month-end clamp, floor(-1.2)==-2, null-dropping joins, mixed int/double arithmetic, isoDate/isoDatetime text — and a formula calling an unregistered function is REFUSED at the authoring door (422), never stored to read null",
"since": "v16",
"status": "active",
"revision": 6,
"revision": 7,
"priority": "P1",
"surface": "mixed",
"personas": [
Expand All @@ -2189,7 +2189,7 @@
"calendar-day timezone contract (ADR-0053 Phase 2 D1): pin now to an instant whose calendar day DIFFERS between UTC and the reference zone (e.g. 2026-08-30T22:00:00Z with timezone Asia/Shanghai — locally already 08-31) and assert today() == 2026-08-31T00:00:00Z (the reference-tz calendar day expressed as UTC MIDNIGHT — never an instant carrying wall-clock time), daysFromNow/daysAgo offset from that same day; an unknown zone falls back to the UTC calendar day, never throws",
"mixed-arithmetic overloads (registerNumericCoercions, stdlib.ts): a double-typed record field divided/multiplied by an int literal (record.amount / 100) evaluates to the promoted double instead of faulting 'no such overload' to silent null (#1928), while pure int 7/2 == 3 keeps integer-division semantics untouched",
"live-door parity: GET a showcase_project row and confirm budget_remaining materializes (read-path evaluation); author a scratch object through the metadata channel, declaring its sharingModel (fixtures.requires[1]), whose formula field composes two stdlib calls (e.g. joinNonEmpty + upper) and read back the known answer — the stdlib reaching the LIVE read path, not just the harness",
"the unregistered-function probe: on a scratch object in the scratch package (fixtures.requires[1]), PUT /api/v1/meta/object/qaformula_NAME?package=com.objectstack.qa.formula at the ACTIVE door (no ?mode=draft) with a formula field calling an UNregistered function (e.g. bad_f = sqrt(4)), its body declaring a sharingModel (fixtures.requires[1]) — without one the 422 is security-owd-unset's refusal, not the expression's. Capture the 422 INVALID_METADATA and its expression-invalid issue, then GET the object and confirm the field was not stored. A ?mode=draft save of the same body is admitted (draft saves defer the gate to promotion, #4463 D1), and its draft→active promotion is refused with the same expression-invalid finding. Promoted through POST /api/v1/packages/PACKAGE/publish-drafts, that refusal is an entry with outcome 'refused' inside the batch envelope, not the request's own 422 (packages/metadata-protocol/src/protocol.ts, the publish-drafts outcome union). What the door cannot see is unchanged underneath: a formula that compiles but faults at EVALUATE time, or a row stored before the gate, still reads null (applyFormulaPlan's `r.ok ? … : null`, packages/objectql/src/engine.ts), now with one warn line per (object, field) (#22031)"
"the unregistered-function probe: on a scratch object in the scratch package (fixtures.requires[1]), PUT /api/v1/meta/object/qaformula_NAME?package=com.objectstack.qa.formula at the ACTIVE door (no ?mode=draft) with a formula field calling an UNregistered function (e.g. bad_f = sqrt(4)), its body declaring a sharingModel (fixtures.requires[1]) — without one the 422 is security-owd-unset's refusal, not the expression's. Capture the 422 INVALID_METADATA and its expression-invalid issue, then GET the object and confirm the field was not stored. A ?mode=draft save of the same body is admitted (draft saves defer the gate to promotion, #4463 D1), and its draft→active promotion is refused with the same expression-invalid finding at BOTH promotion doors: the single-item POST /api/v1/meta/object/qaformula_NAME/publish answers 422 INVALID_METADATA itself (publishMetaItem), while through POST /api/v1/packages/PACKAGE/publish-drafts that refusal is an entry with outcome 'refused' inside the batch envelope, not the request's own 422 (packages/metadata-protocol/src/protocol.ts, the publish-drafts outcome union). What the door cannot see is unchanged underneath: a formula that compiles but faults at EVALUATE time, or a row stored before the gate, still reads null (applyFormulaPlan's `r.ok ? … : null`, packages/objectql/src/engine.ts), now with one warn line per (object, field) (#22031)"
],
"acceptance": [
{
Expand Down Expand Up @@ -2223,7 +2223,7 @@
"evidence": "the responses vs the expectation"
},
{
"clause": "a formula calling an UNREGISTERED function is refused at the authoring door, before it is stored: an active-state object save (PUT /api/v1/meta/object/NAME, no ?mode=draft) whose formula field calls sqrt(...) answers 422 INVALID_METADATA carrying the build's located finding (rule expression-invalid, at that field's expression, naming `sqrt` as a NAME fault), and nothing lands; a ?mode=draft save is admitted (#4463 D1) and its promotion is refused with the same finding (through publish-drafts, as an outcome 'refused' entry of the batch envelope, steps[6]). This is the loud refusal the earlier EXPECTED-FAIL wording waited for (#22031, fixing #22019)",
"clause": "a formula calling an UNREGISTERED function is refused at the authoring door, before it is stored: an active-state object save (PUT /api/v1/meta/object/NAME, no ?mode=draft) whose formula field calls sqrt(...) answers 422 INVALID_METADATA carrying the build's located finding (rule expression-invalid, at that field's expression, naming `sqrt` as a NAME fault), and nothing lands; a ?mode=draft save is admitted (#4463 D1) and its promotion is refused with the same finding at BOTH promotion doors (the single-item POST /api/v1/meta/object/NAME/publish answers 422 INVALID_METADATA itself, and publish-drafts reports an outcome 'refused' entry of the batch envelope, steps[6]). This is the loud refusal the earlier EXPECTED-FAIL wording waited for (#22031, fixing #22019)",
"oracle": "api",
"verify": "the PUT answers 422 with code INVALID_METADATA and an issue whose rule is expression-invalid, located at the field's expression and naming the function; a follow-up GET /api/v1/meta/object/NAME shows the field was not stored. Pinned through the real saveMetaItem / publishMetaItem by packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts (the 'runtime authoring gate on OBJECT writes — the formula field verdict (#22019)' block: sqrt(record.amount) refused 422 on an active save and on a draft's promotion, nothing lands, floor(record.amount) still saves), and at the rule layer by packages/lint/src/runtime-gate.object-formula-writes.test.ts. A row stored BEFORE the gate still reads null on the read path, now with one warn line per (object, field) (packages/objectql/src/engine-formula-fault-log.test.ts). That is the residual, not this clause",
"evidence": "the 422 response (code + the expression-invalid issue) + the post-refusal metadata read"
Expand Down Expand Up @@ -2281,7 +2281,8 @@
"change": "steps[6] and acceptance[5] say how the draft's promotion is refused. Checklist-accuracy finding of the 18.2.0 wave 2 (QA run #26456, release-18.2.0-w2-manual-browser, at 756220c4; wave anchor #25807, slice A): the run promoted through publish-drafts and met the refusal as an outcome:'refused' entry inside the batch envelope, while 'refused the same way' read as a second HTTP 422. Re-read at base e4ba846f (metadata-protocol protocol.ts: outcome 'published' | 'refused' | 'nothing_to_publish'). The clause's assertion (refused, nothing lands) is unchanged",
"ref": "#25807"
},
{ "revision": 6, "date": "2026-10-31", "change": "The function count is 30, not 29, and the scratch-object legs name their writable package. Re-point of the red row QA run #27441 (18.3.0 sweep, wave 3, manual set, at c4c515ad) parked on sweep anchor #27377 §1, plus its fixture finding. (1) The registry gained label(record.f), a macro bindOptionLabel registers beside the permission predicates, and the docs table lists it, so the title, steps[0], steps[2], knownGaps[0], acceptance[0] (clause and evidence) and source[0]/[1] count 30, steps[2] adds the label() known-answer rows, and the receiver-only names outside the count are `can` and `hasCapability` (registerPermissionPredicate is now bindPermissionPredicate). (2) fixtures.requires[1] and steps[6]: a runtime object save needs a writable package and its namespace prefix as well as a sharingModel. Re-read at base 488b3ed0: packages/formula/src/stdlib.ts registerBoundStdLib and bindOptionLabel, packages/formula/src/validate.ts CEL_STDLIB_FUNCTIONS docblock ('30 added by registerStdLib'), content/docs/data-modeling/formulas.mdx, packages/metadata-protocol/src/protocol-authoring-gate.ts (NAMESPACE_PREFIX). The clause still fails on any count drift in either direction", "ref": "#27441" }
{ "revision": 6, "date": "2026-10-31", "change": "The function count is 30, not 29, and the scratch-object legs name their writable package. Re-point of the red row QA run #27441 (18.3.0 sweep, wave 3, manual set, at c4c515ad) parked on sweep anchor #27377 §1, plus its fixture finding. (1) The registry gained label(record.f), a macro bindOptionLabel registers beside the permission predicates, and the docs table lists it, so the title, steps[0], steps[2], knownGaps[0], acceptance[0] (clause and evidence) and source[0]/[1] count 30, steps[2] adds the label() known-answer rows, and the receiver-only names outside the count are `can` and `hasCapability` (registerPermissionPredicate is now bindPermissionPredicate). (2) fixtures.requires[1] and steps[6]: a runtime object save needs a writable package and its namespace prefix as well as a sharingModel. Re-read at base 488b3ed0: packages/formula/src/stdlib.ts registerBoundStdLib and bindOptionLabel, packages/formula/src/validate.ts CEL_STDLIB_FUNCTIONS docblock ('30 added by registerStdLib'), content/docs/data-modeling/formulas.mdx, packages/metadata-protocol/src/protocol-authoring-gate.ts (NAMESPACE_PREFIX). The clause still fails on any count drift in either direction", "ref": "#27441" },
{ "revision": 7, "date": "2026-11-01", "change": "QA run #28656 (W3-RT) finding: acceptance[5] and steps[6] named only publish-drafts as the draft's promotion door. The single-item POST /api/v1/meta/object/NAME/publish refuses too, 422 INVALID_METADATA with the same expression-invalid finding: #28656 read it live at 6e11a929, publishMetaItem's refusal is pinned in packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts (the formula field verdict block, on a draft's promotion), and the route is meta.publishItem in packages/rest/src/rest-route-ledger.ts. Both now name the two promotion doors. The clause asks for both refusals, so it is stronger, never weaker. Re-verified against source at base 1c5b1773ec. Wave anchor #27377, wave-3 accuracy slice S10 (#28701); finding parked in anchor comment 6557904764", "ref": "#28656" }
]
},
{
Expand Down
Loading
Loading