Skip to content

Commit 3148214

Browse files
committed
test(objectql): pin the unevaluable-rule log line per severity, at the evaluator and on an engine write
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ2vytxktPaEAmkEavY9KP
1 parent e24bc37 commit 3148214

3 files changed

Lines changed: 45 additions & 2 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql): an advisory validation rule that cannot be evaluated logs one line saying the write proceeded, not two saying it was rejected (#24782)
6+
7+
Clause-②: no (log text only: no response, status, code, outcome or export moves)
8+
9+
A `severity: 'warning'` or `'info'` validation rule whose predicate cannot be evaluated never blocks the write. Only `error` rules fail closed (#4649). The write answers 2xx and the row persists. The server log said the opposite, twice. The predicate evaluator logged "— write rejected: a rule that cannot be evaluated fails closed, it is never skipped" before the rule's severity was consulted. Then the advisory per-write line repeated the unevaluable violation's own message, which also ends "— write rejected.".
10+
11+
Such a rule now logs ONE line: `Validation rule 'NAME' (warning): the rule could not be evaluated (FAULT) — write proceeded: the rule is advisory, so it was not applied to this write`. This covers a `script` / `cross_field` condition, a `conditional`'s `when`, and the relationship-traversal refusal. On the seed / boot path the same text folds into that load's advisory summary line, which keeps its shape.
12+
13+
Unchanged: an unevaluable `error` rule is refused with the same `400 VALIDATION_FAILED` body, and its log line still says the write was rejected and the rule fails closed. Which rules block is unchanged. An unevaluable rule still stays out of a write answer's `warnings`. `requiredWhen`, `readonlyWhen` and the option `visibleWhen` gate still refuse on every severity.

‎packages/objectql/src/validation/advisory-write-answer.test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,33 @@ describe('[#22726] P2 — the preview appends the same hits to the row\'s `warni
482482
]);
483483
});
484484
});
485+
486+
describe('[#24782] an unevaluable advisory rule: the write succeeds, and the log says so once', () => {
487+
beforeEach(async () => {
488+
await boot();
489+
engine.registry.registerObject({
490+
name: 'adv_unevaluable',
491+
label: 'Probe',
492+
fields: { title: { name: 'title', label: 'Title', type: 'text' } },
493+
validations: [{
494+
type: 'script' as const,
495+
name: 'qa_unevaluable_warn',
496+
severity: 'warning' as const,
497+
message: 'x',
498+
condition: 'int(record.title) > 1',
499+
}],
500+
} as never, 'com.objectstack.test.24782');
501+
});
502+
503+
it('createData stores the row, answers no `warnings`, and logs one advisory line', async () => {
504+
const res: any = await protocol.createData({ object: 'adv_unevaluable', data: { title: 'abc' } });
505+
506+
expect(storeFor('adv_unevaluable').get(res.id)).toMatchObject({ title: 'abc' });
507+
// #22726: an unevaluable rule has no verdict, so it is not a hit.
508+
expect(Object.keys(res)).not.toContain('warnings');
509+
const ruleLines = warns.filter((l) => l.includes('qa_unevaluable_warn'));
510+
expect(ruleLines).toHaveLength(1);
511+
expect(ruleLines[0]).toMatch(/^Validation rule 'qa_unevaluable_warn' \(warning\): the rule could not be evaluated \(.+\) — write proceeded/);
512+
expect(ruleLines[0]).not.toMatch(/write rejected|fails closed/);
513+
});
514+
});

‎packages/objectql/src/validation/rule-unevaluable-advisory-log.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ describe('[#24782] CONTROL — an unevaluable `error` rule is refused, and its l
172172
expect(fields[0]!.message).toMatch(/^Validation rule 'qa_unevaluable' could not be evaluated \(.+\) — write rejected\./);
173173
expect(warns).toHaveLength(1);
174174
expect(warns[0]).toMatch(
175-
/^Validation rule 'qa_unevaluable' predicate failed to evaluate \(.+\) — write rejected: a rule that cannot be evaluated fails closed, it is never skipped$/,
175+
/^Validation rule 'qa_unevaluable' predicate failed to evaluate \(.+\) — write rejected: a rule that cannot be evaluated fails closed, it is never skipped$/s,
176176
);
177177
});
178178

@@ -181,7 +181,7 @@ describe('[#24782] CONTROL — an unevaluable `error` rule is refused, and its l
181181

182182
expect(refusalOf(thrown)[0]).toMatchObject({ constraint: { rule: 'qa_unevaluable_when', reason: 'unevaluable' } });
183183
expect(warns).toHaveLength(1);
184-
expect(warns[0]).toMatch(/^Validation rule 'qa_unevaluable_when' when-predicate failed to evaluate \(.+\) — write rejected: a rule that cannot be evaluated fails closed/);
184+
expect(warns[0]).toMatch(/^Validation rule 'qa_unevaluable_when' when-predicate failed to evaluate \(.+\) — write rejected: a rule that cannot be evaluated fails closed/s);
185185
});
186186

187187
it('the relationship-traversal refusal', () => {

0 commit comments

Comments
 (0)