Reviving 25 Dead Rules — Execution Log (2026-07-11)
Root Cause
Among the rule engine's 76 built-in rules, approximately 25 rules have conditions that always evaluate to false (dead rules), causing capabilities like inspect/optimize/repair to idle. Two root cause categories:
Root Cause A: Double JSON-encoded condition (string trap)
The condition for repair-003/004/005/006, optimize-002/003/004 is written as JSON.stringify({type:'...'}) (a string).
In the engine's _evaluateCondition, const cond = rule.condition yields a string, cond.type=undefined → no type branch is entered → falls to default: return false.
These types are actually supported by the engine (has_non_standard_unit/has_price_anomaly/has_invalid_reference/has_bom_structure, etc.), but are killed by the string wrapper.
Root Cause B: condition.type not supported by the engine, and no condition_script fallback
inspect-001~006 (field_empty/field_pattern/field_value/multi_field_empty/field_empty_check/staleness_check),
valuate-001/002 (asset_count_check/income_data_check),
project-validate-001/002/003 (field_check),
validate-001/002 (type_check/length_check),
create_post project-001 (check_relationships),
repair-007~012 (has_price_change/has_missing_vendor_fields/has_low_stock/has_low_score/has_overdue_order/has_duplicate_candidates),
collect-001~005 (source_match/auto_catalog)
These types do not exist in the explicit branches of _evaluateCondition → fallback to false. Moreover, the original rules only have condition.type and no condition_script (the engine prioritizes the condition_script execution path), completely dead.
Root Cause C (Amplifier): _syncBuiltinRules INSERT missing the condition_script column
The INSERT column list in _syncBuiltinRules does not include condition_script; getRules parses row.condition_script via _safeParseJson, which is always null → even manually written condition_script gets REPLACED with NULL. The single source of truth, the definition change in _loadBuiltinRules(), is the correct fix path.
Fix actions (all in server/core/rule-engine.js)
- Add condition_script column to _syncBuiltinRules INSERT: add condition_script to the column list, add a corresponding ? to VALUES, and add rule.condition_script || null to stmt.run. Otherwise, any manually written condition_script will be cleared upon restart.
- Level 1 (Remove JSON.stringify, 4+3 items): Change condition: JSON.stringify({type:...}) to condition: {type:...} (object) for repair-003/004/005/006, optimize-002/003/004, allowing engine branches to match directly.
- Level 2 (Add condition_script, ~22 items): Add condition_script to all rules where the engine does not support the 'type' field, in the form (ctx,data)=>{...; return true/false;} (signature matches runSafeScript in _evaluateCondition).
- Apply to inspect-001~006, valuate-001/002, project-validate-001/002/003, validate-001/002, create_post project-001, repair-007~012, collect-001~005.
- For rules with existing action_script (repair/project-validate/create_post/inspect-005/006/valuate/collect-005), the condition_script is only responsible for 'whether it matches'; if matched, the existing action_script executes.
- For rules with only condition.type and no action_script (inspect-001/002/003/004, validate-001/002): after condition_script matches, proceed with the default message from prompt_template/block/warn.
Verification Results (HTTP End-to-End After Clean Restart)
| Capability | Before Fix | After Fix |
|------|--------|--------|
| inspect (Part) | 0 issues | 618 issues (200 instance inspection, all 6 rules active) |
| optimize (Part) | 0 (engine.db interference) | 4 rule suggestions, source=rule_engine |
| validate (Part) | 6 rules | 6 rules stable, blocked=False |
| repair (Part) | Closed loop (previously verified) | Closed loop (repairs_applied=1) |
| repair (Vendor) | — | Short-circuited by relationship-resolver misjudging status='new' (separate bug, see below) |
| collect/valuate (main path covered by service) / create_post(project) | Backup path dead | Revived by adding condition_script to rule (Project-specific validation attributes in VALIDATE scope) |
Independent script verification (createDatabaseFresh + _evaluateCondition): All 6 inspect rules cond_script? true, inspect-001 returns matched=true when item_number is missing.
Secondary review (2026-07-11 evening) — In-depth verification of Vendor / Project / collect / valuate
Major clarification: The previous "repair Vendor short-circuit" was due to an expired test ID, not a code bug.
- The previously used Vendor-001=6E0C9F7E2B6C4E74B2074F8B6F0EA6F8 simply does not exist in SCSAI (SCSAI returned 'No items of type 'Vendor' found').
- Therefore, _resolveSelectedData could not fetch it → context.data had no id → entered resolver.resolveExistence → mistakenly judged as new → short-circuit.
- After switching to a real Vendor ID (e.g., B1B97215420F412AA41F1D349668948F), the repair process completed fully: executed=True, issues_found=1, repairs_applied=2, source=rule_engine.
- Conclusion: The repair Vendor rules (repair-003/008/010, etc.) are now active and correct; the previous issue was due to test data.
Project-specific rules have been reactivated (they belong to the VALIDATE scope, not a separate scope).
- Clarification: The scope for builtin-project-validate-001/002/003 is RULE_SCOPES.VALIDATE + item_type_name='Project'. There is no separate project_validate scope (this was an error in previous documentation).
- Validation test using a real Project (CBB360E3...): Returns 9 entries, including builtin-project-validate-001/002/003 (matched=False, as the real object is compliant) and generic rules (generic-required-check / generic-date-logic matched=True). This confirms the Project-specific rules are being executed.
- builtin-project-create-post-001 (scope=create_post, item_type=Project) engine verification matched=false (create_post path not consumed, but rule remains active).
collect / valuate rule engine fallback path has been revived (main path overridden by service).
- collect main path: data_collection_service (returns if service exists, does not enter rule engine). However, engine fallback path verification: execute('collect') → executed=true, 4 results, builtin-collect-001 matched=true (determined by source_match, hit). collect-001~005 all cond_script=true.
- valuate main path: asset_valuator service. Engine fallback path: execute('valuate') → executed=true, builtin-valuate-001 matched=true. valuate-001/002 all cond_script=true.
- Note: The "seemingly dead" behavior of collect/valuate is due to the main path service short-circuit; the rules themselves have been revived. To make them go through the rule engine, you need to lower the service priority or add a forceRuleEngine switch in the collect/valuate methods of capability-runtime.
compare / identify / generate follow LLM fallback (routing design, not a dead rule).
- The main path for these three capabilities' Dispatcher is LLM (source=llm_router), with the rule engine as backup. Their built-in rules have been revived, but the main path does not consume them. This is a design choice, not within the scope of this fix.
Final quantification (all capabilities after fix)
| Capability | Scope Rules Alive | Real Object Validation |
|---|---|---|
| inspect | 6 rules with cond_script=true | HTTP: 618 issues (200 instances) |
| optimize | 4 rules (JSON.stringify removed) | HTTP: 4 suggestions |
| validate | 6 general + 3 Project-specific rules | HTTP: 6 real Part rules, 9 real Project rules |
| repair | Part/Vendor closed-loop + 10 rules | HTTP: Part closed-loop, Vendor (real ID) closed-loop |
| collect | 5 rules with cond_script=true | Engine: executed=true, collect-001 hit |
| evaluate | 2 entries with cond_script=true | Engine: executed=true, evaluate-001 hit |
| create_post | project-001 cond_script=true | Engine alive |
| compare/identify/generate | Rule alive | Main path uses LLM (design) |
Modify file
- server/core/rule-engine.js (INSERT column in _syncBuiltinRules + 25 rules condition_script/remove JSON.stringify)
- No new files added, temporary debug scripts have been cleaned up.
Key learnings
- The rule engine's "single source of truth" is the JS definition in _loadBuiltinRules(), not the DB file. Any fixes must be made here + ensure the INSERT columns in _syncBuiltinRules align with the defined fields.
- condition must be an object, not a JSON.stringify string; otherwise, .type cannot be parsed by _evaluateCondition.
- The engine now supports condition_script taking precedence over the type branch: unsupported types can be revived using condition_script: (ctx,data)=>bool, without modifying the engine's branching logic.
- Port being occupied by an old process can create the illusion that "code changes are made but the old process still runs": before verification, always confirm via netstat | findstr :3006 that the port belongs only to the target process.
BossAgents