fix: audit only newly created durable search definitions

This commit is contained in:
2026-09-07 18:11:06 +02:00
parent 8dfd429a61
commit 82f7475b91
+30 -7
View File
@@ -37,19 +37,18 @@ const ENVELOPE_MAPPINGS: Record<string, unknown> = {
export class SearchService {
private readonly definitions = new Map<string, SearchIndexDefinition>();
private readonly lineage?: LineageEmitter;
private readonly aud0?: Aud0Client;
private readonly definitionRepository?: SearchDefinitionRepository;
constructor(
private readonly adapter: OpenSearchAdapter,
opts?: { aud0Client?: Aud0Client; definitionRepository?: SearchDefinitionRepository },
) {
this.aud0 = opts?.aud0Client;
this.lineage = opts?.aud0Client ? new LineageEmitter(opts.aud0Client) : undefined;
this.definitionRepository = opts?.definitionRepository;
}
// ---------------------------------------------------------------------
// Index definitions
// ---------------------------------------------------------------------
private validateDefinition(def: SearchIndexDefinition): void {
if (!def.index_name?.trim() || !/^[a-z0-9][a-z0-9_-]{0,62}$/i.test(def.index_name)) {
throw new SiaxError("INVALID_REQUEST", "0pt.search", "index_name must be 1-63 letters, digits, _ or -");
@@ -92,12 +91,38 @@ export class SearchService {
return copy;
}
/** Production registration: versioned durable write followed by cache refresh. */
/** Production registration: durable versioned write, one stable audit event, then cache refresh. */
async registerIndexDefinitionDurable(def: SearchIndexDefinition, actorId?: string): Promise<SearchIndexDefinition> {
this.validateDefinition(def);
if (!this.definitionRepository) return this.registerIndexDefinition(def);
const stored = await this.definitionRepository.save(def, actorId);
return this.registerIndexDefinition(stored);
const registered = this.registerIndexDefinition(stored.definition);
if (stored.created && this.aud0) {
const id = `search-definition:${registered.index_name}:v${registered.version}`;
await this.aud0.ingestEvent({
event_id: `evt_0pt_${id}`,
event_type: "0pt.search_definition_registered",
tenant_id: "t_platform",
project_id: "p_platform",
actor_id: stored.created_by ?? actorId ?? "system",
app_id: "0pt",
capability_id: "0pt.search.definition.register.v1",
resource_type: "search_index_definition",
resource_id: `${registered.index_name}@${registered.version}`,
risk_level: "L2",
result: "success",
payload: {
index_name: registered.index_name,
version: registered.version,
fields: Object.keys(registered.fields).sort(),
default_search_fields: registered.default_search_fields,
},
created_at: stored.created_at,
occurred_at: stored.created_at,
idempotency_key: id,
});
}
return registered;
}
getIndexDefinition(indexName: string): SearchIndexDefinition {
@@ -269,8 +294,6 @@ export class SearchService {
await flush();
await this.adapter.refresh(physical);
} catch (error) {
// Best-effort cleanup of an incomplete generation. Preserve the original
// failure even if cleanup itself fails.
await this.adapter.deleteIndex(physical).catch(() => undefined);
throw error;
}