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
fix(qa): give the expression ledger a fail-policy for "nothing evaluates this" (#16660)
`FAIL_POLICIES` had four members and all four describe what an EVALUATOR does
when the expression is bad. Five rows classify slots that have no evaluator at
all, so each had to borrow a member claiming something stronger: four carried
`compile-error` (the Zod parse is the refusal — true, and a property every row
in the ledger shares) and `cel-advanced-policy` carried `fail-closed`, a RUNTIME
refusal, on a slot whose own `enforcement` cell reads `(no runtime consumer
yet)`. On a security-flavoured row that reads as a security guarantee.
Adds `unevaluated` and re-states all five rows with it. Every other row's
`failPolicy` was re-read against the new member and left where it was.
Minting a word that could be borrowed as loosely would reproduce the defect one
member wider, so the member arrives with a pin. "Non-empty runtime enforcement"
cannot be checked as `enforcement !== ''` — `ExprSurface` makes the cell
required, so every row has one. The checkable question is what the cell SAYS:
an `unevaluated` row must state the absence it claims and must not name a
runtime evaluator site, and it cannot be `state: 'enforced'`. The detector
carries a positive control against the COMPILE rows another pin already
requires to name the canonical compiler, so an emptied regex reds instead of
passing vacuously.
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Co-authored-by: Claude <noreply@anthropic.com>
'PARSE ONLY — `CronExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to `{dialect:"cron",source}`; nothing evaluates the result. service-knowledge/knowledge-service.ts reads `refresh.onRecordChange` and NEVER `refresh.cron` (measured: the only `refresh` reads in that package are the two `onRecordChange` sites)',
// and #15028 (the envelope arm now pins the dialect — the note's last sentence).
358
385
id: 'cron-declared-unwired',
359
386
summary: 'cron slots on subsystems that were declared but never built — export schedules, flow schedule state, connector sync, cache warmup, DR backup/test',
'PARSE ONLY — `CronExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to the envelope; NO EVALUATOR FOUND for any of these five keys. Reader hunt, per key, walking out from each declaration (2026-09-04, `61821e54cf5`): `api/export.zod.ts:cronExpression` — the whole `ExportJobApiContracts` family has zero consumers and rest-server serves no `/api/v1/data/export` route, so `POST /api/v1/data/export/schedules` is a declared contract nothing implements; `IExportService` has no provider binding, which its own source already records. `automation/execution.zod.ts:cronExpression` — `ScheduleStateSchema` has no consumer outside packages/spec; the schedule TRIGGER that does work reads a flow start node `config.schedule` through trigger-schedule/schedule-trigger.ts `normalizeSchedule`, a different shape this key never reaches. `integration/connector.zod.ts:schedule` — `syncConfig` has no reader outside packages/spec. `system/cache.zod.ts:schedule` (CacheWarmup) and `system/disaster-recovery.zod.ts:schedule` (BackupConfig + the DR `testing` block) — neither schema has any consumer outside packages/spec',
note: 'EXPERIMENTAL — five declared cron slots with no runtime evaluator (ADR-0049 enforce-or-remove candidates; each wants its own look, and the card that surfaced them says so rather than proposing a sweep). ⚠️ TWO of these surfaces are declared TWICE: `api/export.zod.ts` `cronExpression` on `ScheduledExportSchema` and on `ScheduleExportRequestSchema`, and `system/disaster-recovery.zod.ts` `schedule` on `BackupConfigSchema` and on `DisasterRecoveryPlanSchema` (the DR `testing` block). Both pairs are genuinely the same surface twice, so one row is honest here — and now that each declaring position carries its OWN key, that judgement is written out as two `covers` entries instead of being assumed by a collapse. ⚠️ The `failPolicy` on this row is `compile-error` because the PARSE is the only thing that ever refuses one of these values; it is not a claim that cron SYNTAX is checked. It is not: `@objectstack/formula` cronEngine validates 5/6-field patterns and `@` aliases, and has ZERO consumers outside packages/formula — nothing routes these slots through it. The parse now DOES pin these slots to the cron dialect (the sibling finding on the dialect union is closed): the envelope arm of `CronExpressionInputSchema` accepts a `cron` envelope only and its bare-string arm refuses a blank string, each with one issue at the slot naming the fix — and it still judges no cron syntax, by position: no grammar is restated in spec; `croner` judges the pattern where a schedule is wired (`cron-job-schedule`).',
397
+
note: 'EXPERIMENTAL — five declared cron slots with no runtime evaluator (ADR-0049 enforce-or-remove candidates; each wants its own look, and the card that surfaced them says so rather than proposing a sweep). ⚠️ TWO of these surfaces are declared TWICE: `api/export.zod.ts` `cronExpression` on `ScheduledExportSchema` and on `ScheduleExportRequestSchema`, and `system/disaster-recovery.zod.ts` `schedule` on `BackupConfigSchema` and on `DisasterRecoveryPlanSchema` (the DR `testing` block). Both pairs are genuinely the same surface twice, so one row is honest here — and now that each declaring position carries its OWN key, that judgement is written out as two `covers` entries instead of being assumed by a collapse. ⚠️ The `failPolicy` on this row is `unevaluated`. It read `compile-error` until the vocabulary gained a member for "nothing evaluates this slot", and that value was the closest available rather than a true one: the PARSE is the only thing that ever refuses one of these values, which is a property every row in this ledger shares and says nothing about this one. It was never a claim that cron SYNTAX is checked. It is not: `@objectstack/formula` cronEngine validates 5/6-field patterns and `@` aliases, and has ZERO consumers outside packages/formula — nothing routes these slots through it. The parse now DOES pin these slots to the cron dialect (the sibling finding on the dialect union is closed): the envelope arm of `CronExpressionInputSchema` accepts a `cron` envelope only and its bare-string arm refuses a blank string, each with one issue at the slot naming the fix — and it still judges no cron syntax, by position: no grammar is restated in spec; `croner` judges the pattern where a schedule is wired (`cron-job-schedule`).',
'PARSE ONLY — `TemplateExpressionInputSchema` refuses a blank/non-string, non-envelope value and normalizes to `{dialect:"template",source}`; NO EVALUATOR FOUND. `PromptTemplateSchema` has no consumer outside packages/spec (measured 2026-09-04), so nothing interpolates the `{{var}}` holes and nothing checks that the declared `variables` match them',
'PARSE ONLY in this repo — `TemplateExpressionInputSchema` refuses a blank/non-string, non-envelope value. The KEY has a build-time reader that is NOT an evaluator: lint/validate-record-title.ts `validateRecordTitle` (wired into authoring-rules.ts, run by `os build` / `os lint` / the MCP authoring surface) reports every declaration as `title-format-retired`, an advisory WARNING steering the author to `nameField` — it reads that the key is present and never looks at the template text. The server-side title resolver deliberately does NOT read it: spec/src/data/display-name.ts `objectTitleCompleteness` / `resolveRecordDisplayName` resolve `nameField` then the `displayNameField` alias then a derivation, and ADR-0079 states the reason (render-only; the server can neither return nor query it)',
note: 'EXPERIMENTAL — declared policy conditions with no runtime evaluator yet (ADR-0056 D8 / ADR-0049 tracking).',
434
+
note: 'EXPERIMENTAL — declared policy conditions with no runtime evaluator yet (ADR-0056 D8 / ADR-0049 tracking). This row is why `unevaluated` was minted: it carried `fail-closed` — a RUNTIME refusal — while its own `enforcement` cell said `(no runtime consumer yet)`, so on a security-flavoured row the ledger read as a security guarantee over a slot nothing evaluates. `fail-closed` here was the one borrowing with a consequence past legibility, and spreading it to the other four unwired rows was refused for that reason.',
'no COMPILE rows in the ledger — the runtime-evaluator detector has nothing to be controlled against, so the assertions below prove nothing',
270
+
).toBeGreaterThan(0);
271
+
for(constsofcompileRows){
272
+
expect(
273
+
NAMES_RUNTIME_EVALUATOR.test(s.enforcement),
274
+
`${s.id}: the runtime-evaluator detector does not fire on a row that is required to name the canonical compiler — the DETECTOR is broken, not the row`,
// `enforced` means the platform enforces the surface; `unevaluated` means
280
+
// nothing reads it. Restricting this pin to `experimental` rows would
281
+
// leave `state: 'enforced'` as the escape hatch, so the contradiction is
282
+
// refused directly instead.
283
+
expect(
284
+
s.state,
285
+
`${s.id}: state 'enforced' and failPolicy 'unevaluated' contradict each other — nothing evaluates the slot, so nothing enforces it`,
286
+
).not.toBe('enforced');
287
+
288
+
expect(
289
+
DECLARES_NO_EVALUATOR.test(s.enforcement),
290
+
`${s.id}: failPolicy 'unevaluated' but the enforcement cell never states the absence it claims. Say it in the cell — 'NO EVALUATOR FOUND', 'PARSE ONLY', or 'no runtime consumer' — so the claim is reviewable rather than inferred from silence`,
291
+
).toBe(true);
292
+
293
+
expect(
294
+
NAMES_RUNTIME_EVALUATOR.test(s.enforcement),
295
+
`${s.id}: failPolicy 'unevaluated' says nothing evaluates this slot, but the enforcement cell names a runtime evaluator/compiler site. One of the two is wrong: if something evaluates it, classify it under the ADR-0058 D5 tier that describes what happens to a bad expression there`,
0 commit comments