Rule Engine Validation Result Truncation Bug Fix (Late 2026-07-11 / Early…

Rule Engine Validation Result Truncation Bug Fix (Late 2026-07-11 / Early 07-12)

Issue Source

A previous review uncovered an anomaly: For the same item_type='Project', validation using selected_ids (real objects) returned 9 items with blocked=False, but using directly passed empty Project data returned 4 items with blocked=True, and all 4 items had matched=False — a contradiction (blocked but unable to see which rule caused the block).

Two Actual Bugs

Bug 1: When block returns early, the blocking rule is not merged into results (incomplete results)

Location: The execute() loop in server/core/rule-engine.js.

Original Logic:

if (rule.action_type === 'block' && isBlocked) {
  return { executed: true, blocked: true, block_reason: actionResult.message, results, ... };
}

results 此时只含之前已评估且 matched=false 的规则,触发拦截的当前规则 resultItem 未 push。

Consequence: HTTP returns blocked=True, but the blocking rule with matched=true is not visible in the results; block_reason points to a rule that does not exist in the results → creating the paradox of 'all false yet blocked'.

Fix:

  • Before returning, results.push(resultItem);
  • Added a blocking_rule: rule.id field, allowing the frontend to clearly identify which rule caused the block.

Bug 2: The validate function's default first_match strategy hides subsequent, more severe block violations

The default conflict_strategy for execute is 'first_match'. After sorting rules by priority, P1(warn) rules are placed before P0(block) rules (because priority||2 sets P0=0 to 2, making P1=1 smaller). If a warn rule is matched=true first, first_match breaks immediately, causing subsequent mandatory validation rules at the block level (e.g., project-validate-001/003) to never be evaluated → hard violations are missed in validation results.

Consequence: Mandatory field omissions that should be highlighted in red in the validation report are silently discarded.

Fix: The default strategy is now differentiated by scope:

  • validate / inspect → merge (evaluate all rules, collect all violations);
  • others (repair/optimize, etc.) → retain first_match (apply the first automatic fix upon match, aligning with original intent).

The block branch still serves as a termination condition (return upon encountering the first block), ensuring determinism.

Verification (direct engine testing + HTTP end-to-end)

  1. Empty Project (data-only): Now blocked=True, blocking_rule=builtin-project-validate-001, and this rule appears in results with matched=True, resolving the paradox.
  2. Real Project (selected_ids): blocked=False, 9 results, only generic rules triggered and passed=True (not blocked), correct.
  3. Full capability regression: validate Part (6 items, not blocked)/repair Part (1 item written back)/optimize (4 items)/inspect (618 items) all normal, no regression.

Additional confirmation (not a bug).

  • builtin-project-validate-003 requires scheduling_method to be non-empty: the scheduling_method in the real SCSAI Project is a valid ID reference, a truly mandatory field; the previous 'OK Project' test data missed scheduling_method, triggering the block—engine behavior is correct.
  • The discrepancy between HTTP validate and direct engine test result sets was originally thought to be 'missing project rules,' but was actually caused by Bug2's first_match breaking early; now unified.

Modified files.

  • server/core/rule-engine.js: execute() in two places (block returns push resultItem + blocking_rule; default conflict_strategy scoped).

MemoryService recordTrace fails silently (discovered and fixed in the same cycle)

Issue

server.err logs 'MemoryService recordTrace failed: Cannot read properties of null (reading 'prepare')' for every request.

capability-dispatcher calls memoryService.extractAndStoreFacts in a fire-and-forget manner; its failure is swallowed by a try/catch,

resulting in long-term silent loss of all memory writes (trace/fact/profile) and no observable signals beyond any effective alerts.

Root Cause

  1. dbAdapter.init() is asynchronous and does not flip any synchronous switch (dbAdapter._initialized remains undefined).

The old _getDb returns null when _initialized is falsey.

  1. sqliteCompat.createDatabase returns a 'deferred proxy' when sql.js is not ready; the proxy's prepare throws an error if _realDb is not populated.

The old _getDb synchronously called prepare(...).run() and threw immediately, but the error was swallowed → this._db remained null forever.

  1. recordTrace was originally synchronous, and extractAndStoreFacts did not await it (it was turned into a Promise first),

causing subsequent db.prepare to crash when db was still null.

Fix (server/services/memory-service.js):

  • _getDb() changed to return only the ready instance (returns null if not ready, no longer swallows errors to create a DB).
  • Added new async _ensureDb(): after awaiting dbAdapter.init(), call dbAdapter.createDatabaseFresh(DB_PATH) (same as the rule engine, available within server), then _initTables().
  • recordTrace changed to async, awaits this._ensureDb() if no db exists.
  • extractAndStoreFacts changed to async and awaits recordTrace; safely returns if db is missing.
  • Preheat memoryService._ensureDb() before module export to make read interfaces (getProfile/getFacts/getTraces) ready.

Validation

  • After restart, initiate a validate request. The memory_traces table in boss_analytics.db is automatically created and one record is written

(source=web, action=validate), and the recordTrace error no longer appears in server.err.

Summary of modified files

  • server/core/rule-engine.js: Added rule_id in two places in execute() and in executeOptimize.
  • server/services/memory-service.js: Modified _getDb/_ensureDb/recordTrace/extractAndStoreFacts + module preheating.
  • server/core/health-inspector.js: Changed _getDb to use createDatabaseFresh (to avoid failure in table creation due to delayed proxy returned by createDatabase lacking .run()).

Incidental fixes / confirmations

1. optimize response missing rule_id

executeOptimize's optimizations.push only includes rule: rule.name, missing rule_id → frontend/audit cannot see which rule was applied.

Added rule_id: rule.id in server/core/rule-engine.js; restart verified return includes rule_id=builtin-optimize-00x.

2. HealthInspector table creation failed (this._db.run is not a function).

health-inspector.js uses compat.createDatabase(dbPath), which returns a 'delayed proxy' (only prepare/exec, no run) when sql.js is not ready,

causing _ensureTables' CREATE TABLE ... .run() to throw error, and health check to fail silently. Changed to compat.createDatabaseFresh(dbPath) (real instance,

same as rule engine/memory service). After restart, server.err no longer shows table creation failure / save result failure.

3. Other observations (not bugs).

  • identify initially returned empty: due to test parameter misalignment (passed query instead of query_type+filters). Changed to query_type=list, then source=rule_engine returned normally.
  • [RuleEngine] SCSAI private library query failed: Unknown error: Only cold‑start one‑time SCSAI query failed, not systemic.
  • Configuration module failed to load, using default configuration: Cannot find module './cron‑tasks': Configuration fallback warning, harmless.

4. Six‑capability end‑to‑end stability verification (real Part, after restart)

validate / inspect / repair(auto_apply) / optimize(auto_apply real write‑back to SCSAI) / identify / generate all success=OK, no runtime errors.

optimize auto_apply=true actually wrote back to SCSAI successfully (SCSAI_response.success=true, name/description rewritten).

Status

  • All repairs completed, server restart verified: six capabilities stable, memory writes restored, health‑check table built normally, no recordTrace/create‑table/prepare errors.
  • No residual standalone bugs.
← 返回案例列表

Try 左帮右臂 Industrial Agent — your digital workforce

Available as a WeChat mini program, web app (ylxt.chat) and desktop client. Start from whichever fits your workflow.

🌐 Web App 💻 Desktop Download 📞 Call 189-8606-6876
WeChat mini program QR
Scan for the WeChat mini program
Share:
🤖 Try Now →
🤖
🎁