Skip to content
bitzorcas
中EN

Guide

Unified Search, Conflict Checking, and Result Security

Understand actual Query/Filter/Rule inputs, default fields, tenant boundaries, StoredFields exposure, authorization, conflict terms, exclusion, evidence, and human review.

Last updated

Unified search and conflict checking both call ISearchEngine.SearchAsync, but their business meaning is different. Unified search returns navigation candidates; a conflict result feeds a legal intake decision. Neither Lucene hit is source-resource authorization or a final business conclusion.

1. Input actually built by UnifiedSearch

The handler sets only IndexKey, current TenantId, Skip/Take, and—when present—the same Keyword under Name, Subject, and Content. It does not set resource types, culture, sort, highlights, field projection, DataScope, owner filters, or an authorization callback.

Current unified-search input
var input = new SearchPerformInput
{
IndexKey = request.IndexKey,
// TenantId comes only from the authenticated context.
TenantId = currentUser.User.TenantId,
Query =
{
Skip = request.Skip,
Take = request.Take
}
};
if (!string.IsNullOrWhiteSpace(request.Keyword))
{
// Every scenario uses the same three hard-coded field names.
foreach (var field in new[] { "Name", "Subject", "Content" })
input.Query.SearchFieldWordsDict[field] = [request.Keyword];
}

Claims that language and resource type are mandatory filters are not supported by source.

2. Hit projection and StoredFields

UnifiedSearchResult
{
"items": [
{
"businessId": "case-0042",
"businessType": "Case",
"matchKeywords": "Hailan Technology",
"hitLevel": 8,
"storedFields": {
"Name": "Hailan Technology Co., Ltd.",
"Subject": "Equity acquisition",
"InternalNote": "returned if a producer stored it"
}
}
],
"total": 1
}

SearchHitItemProjection copies every StoredFields value via ToString() and maps null to empty. There is no per-index allowlist, type schema, sensitivity, maximum length, or masker. Missing BusinessId/BusinessType/MatchKeywords also become empty strings, concealing index-quality failures.

Define a versioned output schema per IndexKey: indexable fields, returnable fields, sensitivity, maximum size, and masking. Keep summaries minimal; fetch details from the owning endpoint.

3. Missing candidate authorization

The previous manual showed authorizer.FilterAsync(actor,candidates). No such call exists. Effective authorization checks only search.search.view, not whether the caller may see a specific Case/Document/Ticket, its department, participants, confidentiality, or individual fields.

Search query

Option A: ACL/DataScope tokens in index

Option B: oversample candidates

Owner batch authorization

authorized page

Index ACLs are fast but revocation must propagate without gaps. Post-filtering is authoritative but requires oversampling, batch checks, and fill-after-filter paging. Sensitive scenarios often combine a coarse index filter with owner final authorization.

4. Total can leak unauthorized volume

Current Total is the raw engine count. If Items are filtered later while Total remains raw, callers learn how many hidden records exist. Define authorized Total semantics: expensive exact total, approximate/lower-bound, cursor plus hasMore, or no total. Hiding details does not neutralize a tenant-wide count.

5. Conflict term construction

For every PartyInfo, nonblank Name, ForeignName, FormerName, and CreditCode enter an ordinal HashSet; the dictionary key is party.Name.

Target conflict-term normalization
// Normalize for matching while preserving the original values as controlled evidence.
var normalized = PartySearchTerms.Create(
name: party.Name,
foreignName: party.ForeignName,
formerName: party.FormerName,
creditCode: NormalizeCreditCode(party.CreditCode));
// A stable unique category key prevents duplicate or blank names from overwriting a party.
categoryKeywordMap[normalized.CategoryKey] = normalized.Terms;
// Audit a digest and rule version, not full person/organization identifiers in logs.
audit.RecordInputDigest(normalized.Digest, rules.Version);

Current code does not trim/normalize/case-fold or validate a credit code. Duplicate names overwrite; a blank Name with another term produces a blank key. Party role is not represented.

6. Conflict query input

The query fixes IndexKey=conflict, uses the current TenantId, selects PreProcessRoleOpposing, supplies the category map, and optionally excludes SourceBusinessId. It does not explicitly set Skip/Take, rule version, threshold, ordering, time budget, or maximum party/term count. HasConflict is simply matches.Count > 0.

7. A potential match is not a final conflict

Name, abbreviation, pinyin, and weak-word matching create false positives. Index delay or missing related parties creates false negatives. A complete workflow separates PotentialMatch, ReviewedConflict, Cleared, Waived/Approved, and Blocked.

Current Search has no review/record state, and GetConflictRecord always returns pending-module NotFound. HasConflict=true should be interpreted only as potential match.

8. Evidence required for conflict checking

Persist at least:

  • record, tenant, matter/case, requester;
  • encrypted/redacted original evidence plus canonical digest;
  • index version, watermark, and provider version;
  • rule/analyzer/segment-dictionary versions;
  • matched owner id, field, and score;
  • reviewer, decision, reason, and override approver;
  • created/reviewed/expired times;
  • rerun lineage and superseded conclusions.

The lightweight ConflictRecordDto with only HasConflict and MatchCount is not enough to explain a result.

9. ExcludeCaseId boundary

Current code sets SourceBusinessId and ExcludeSourceBusinessId=true. Verify that it excludes only the active Case, uses the same canonical ID as the index, ignores blank values, cannot be abused to suppress a real match, is audited, and does not over-exclude when a source ID is missing. Prefer deriving it from the server-side matter context rather than trusting arbitrary client input.

10. Data minimization

Names, former names, credit codes, and case subjects are sensitive. Do not put request/response bodies into normal logs, full terms into tracing tags, or unrestricted values into StoredFields. Audit access and purpose, bind retention to case policy, encrypt approved diagnostic snapshots, and keep metrics to term/hit counts, duration, and rule version.

11. Required scenarios

  1. Current TenantId overrides client TenantId.
  2. Incorrect TenantId at ingestion is detected/reconciled.
  3. Unified search cannot bypass conflict permission.
  4. Owner authorization, DataScope, and revocation delay.
  5. StoredFields allowlist/masking/large values.
  6. Authorized paging and Total semantics.
  7. Duplicate/blank/Unicode names and credit codes.
  8. ExcludeCaseId manipulation.
  9. Rule and dictionary version pinning/rerun.
  10. Provider failure never becomes “no conflict.”
  11. Stale watermark is visible and fail-closed where required.
  12. Review, override, and immutable audit evidence.

12. Review commands

Terminal window
# StoredFields are copied wholesale and no owner authorization/DataScope is called.
rg -n "StoredFields|SearchHitItemProjection|DataScope|Authoriz.*Filter" \
src/Platform/Search -g '*.cs'
# Inspect term construction, trusted tenant, exclusion, and Boolean conclusion.
rg -n "BuildCategoryKeywordMap|trustedTenantId|ExcludeSourceBusinessId|HasConflict" \
src/Platform/Search -g '*.cs'
# The target should add record storage, review, override, and audit tests.
rg -n "ConflictRecord|ConflictReview|ConflictOverride|PreProcessRoleOpposing" \
src tests -g '*.cs'

Back to Search · HTTP and authorization · Testing and GA

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%