mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(wiki): propagate staleness when a fact page is updated during ingest
This commit is contained in:
parent
55cf2a8458
commit
ea0ace1e49
@ -0,0 +1,11 @@
|
||||
package vip.mate.wiki.event;
|
||||
|
||||
/**
|
||||
* Published after a fact-layer page is updated during ingest (already
|
||||
* committed). Consumed asynchronously to mark the experience pages that depend
|
||||
* on it as stale, without blocking ingest.
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
public record WikiFactPageUpdatedEvent(Long kbId, Long factPageId, String reason) {
|
||||
}
|
||||
@ -1429,7 +1429,7 @@ public class WikiProcessingService {
|
||||
String actualSlug = existingByCanonical.getSlug();
|
||||
pageService.updatePageByAi(kbId, actualSlug, content, pageSummary, rawId);
|
||||
pageService.mergeSourceLineage(existingByCanonical.getId(), rawId, raw.getTitle());
|
||||
afterPagePersisted(existingByCanonical.getId(), kbId, pageType, metadataNode, dependsOnNode);
|
||||
afterPagePersisted(existingByCanonical.getId(), kbId, pageType, metadataNode, dependsOnNode, true);
|
||||
log.info("[Wiki] Phase B create slug='{}' canonical-matches existing '{}', updated",
|
||||
slug, actualSlug);
|
||||
return false;
|
||||
@ -1446,7 +1446,7 @@ public class WikiProcessingService {
|
||||
if (winner != null) {
|
||||
pageService.updatePageByAi(kbId, winnerSlug, content, pageSummary, rawId);
|
||||
pageService.mergeSourceLineage(winner.getId(), rawId, raw.getTitle());
|
||||
afterPagePersisted(winner.getId(), kbId, pageType, metadataNode, dependsOnNode);
|
||||
afterPagePersisted(winner.getId(), kbId, pageType, metadataNode, dependsOnNode, true);
|
||||
log.info("[Wiki] Phase B create slug='{}' lost slug-claim race to '{}', updated",
|
||||
slug, winnerSlug);
|
||||
return false;
|
||||
@ -1462,7 +1462,7 @@ public class WikiProcessingService {
|
||||
if (existing != null) {
|
||||
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
||||
pageService.mergeSourceLineage(existing.getId(), rawId, raw.getTitle());
|
||||
afterPagePersisted(existing.getId(), kbId, pageType, metadataNode, dependsOnNode);
|
||||
afterPagePersisted(existing.getId(), kbId, pageType, metadataNode, dependsOnNode, true);
|
||||
log.info("[Wiki] Phase B create page slug='{}' done (updated existing)", slug);
|
||||
return false;
|
||||
}
|
||||
@ -1471,7 +1471,7 @@ public class WikiProcessingService {
|
||||
try {
|
||||
WikiPageEntity created = pageService.createPage(kbId, slug, title, content, pageSummary, sourceRawIds, pageType);
|
||||
pageService.mergeSourceLineage(created.getId(), rawId, raw.getTitle());
|
||||
afterPagePersisted(created.getId(), kbId, pageType, metadataNode, dependsOnNode);
|
||||
afterPagePersisted(created.getId(), kbId, pageType, metadataNode, dependsOnNode, false);
|
||||
log.info("[Wiki] Phase B create page slug='{}' done (created)", slug);
|
||||
citationService.buildCitationsAsync(created.getId(), kbId);
|
||||
return true;
|
||||
@ -1480,7 +1480,7 @@ public class WikiProcessingService {
|
||||
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
||||
WikiPageEntity raced = pageService.getBySlug(kbId, slug);
|
||||
if (raced != null) {
|
||||
afterPagePersisted(raced.getId(), kbId, pageType, metadataNode, dependsOnNode);
|
||||
afterPagePersisted(raced.getId(), kbId, pageType, metadataNode, dependsOnNode, true);
|
||||
}
|
||||
log.info("[Wiki] Phase B create page slug='{}' lost INSERT race -> updated existing", slug);
|
||||
return false;
|
||||
@ -1500,7 +1500,7 @@ public class WikiProcessingService {
|
||||
* the same metadata and trigger handling as a clean create.
|
||||
*/
|
||||
private void afterPagePersisted(Long pageId, Long kbId, String pageType,
|
||||
JsonNode metadataNode, JsonNode dependsOnNode) {
|
||||
JsonNode metadataNode, JsonNode dependsOnNode, boolean isUpdate) {
|
||||
applyValidatedMetadata(pageId, kbId, pageType, metadataNode);
|
||||
deriveKnowledgeLayer(pageId, kbId, pageType);
|
||||
applyDependencies(pageId, kbId, pageType, dependsOnNode);
|
||||
@ -1510,6 +1510,13 @@ public class WikiProcessingService {
|
||||
if (eventPublisher != null && pageType != null && !pageType.isBlank()) {
|
||||
eventPublisher.publishEvent(new vip.mate.wiki.event.WikiPageCreatedEvent(kbId, pageType));
|
||||
}
|
||||
// When an existing fact page is updated, propagate staleness to the
|
||||
// experience pages depending on it (async, off the ingest thread).
|
||||
if (isUpdate && eventPublisher != null && dependencyService != null
|
||||
&& pageTypeProfileService != null && !pageTypeProfileService.isExperience(kbId, pageType)) {
|
||||
eventPublisher.publishEvent(new vip.mate.wiki.event.WikiFactPageUpdatedEvent(
|
||||
kbId, pageId, "fact page updated during ingest"));
|
||||
}
|
||||
}
|
||||
|
||||
/** Stamp the page's knowledge layer (fact/experience) derived from its pageType profile. */
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package vip.mate.wiki.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import vip.mate.wiki.event.WikiFactPageUpdatedEvent;
|
||||
|
||||
/**
|
||||
* Propagates staleness asynchronously when a fact-layer page is updated: every
|
||||
* experience page depending on it is marked stale. Runs off the ingest thread
|
||||
* so a fan-out over many dependents never blocks ingest; the fact page is
|
||||
* already committed when the event fires. Marking is idempotent, so repeated
|
||||
* events for the same page are harmless.
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WikiStalePropagationListener {
|
||||
|
||||
private final WikiDependencyService dependencyService;
|
||||
|
||||
public WikiStalePropagationListener(WikiDependencyService dependencyService) {
|
||||
this.dependencyService = dependencyService;
|
||||
}
|
||||
|
||||
@Async
|
||||
@EventListener
|
||||
public void onFactPageUpdated(WikiFactPageUpdatedEvent event) {
|
||||
try {
|
||||
dependencyService.markDependentsStale(event.kbId(), event.factPageId(), event.reason());
|
||||
} catch (Exception e) {
|
||||
log.warn("[WikiStale] propagation failed for fact page {}: {}", event.factPageId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package vip.mate.wiki.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import vip.mate.wiki.event.WikiFactPageUpdatedEvent;
|
||||
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WikiStalePropagationListener}: it forwards a fact-page
|
||||
* update to the dependency engine and never lets a failure escape (so a broken
|
||||
* propagation cannot disturb ingest).
|
||||
*/
|
||||
class WikiStalePropagationListenerTest {
|
||||
|
||||
@Test
|
||||
void forwardsToMarkDependentsStale() {
|
||||
WikiDependencyService dep = mock(WikiDependencyService.class);
|
||||
when(dep.markDependentsStale(7L, 100L, "r")).thenReturn(2);
|
||||
|
||||
new WikiStalePropagationListener(dep)
|
||||
.onFactPageUpdated(new WikiFactPageUpdatedEvent(7L, 100L, "r"));
|
||||
|
||||
verify(dep).markDependentsStale(7L, 100L, "r");
|
||||
}
|
||||
|
||||
@Test
|
||||
void swallowsFailure() {
|
||||
WikiDependencyService dep = mock(WikiDependencyService.class);
|
||||
doThrow(new RuntimeException("boom")).when(dep).markDependentsStale(7L, 100L, "r");
|
||||
|
||||
// Must not throw.
|
||||
new WikiStalePropagationListener(dep)
|
||||
.onFactPageUpdated(new WikiFactPageUpdatedEvent(7L, 100L, "r"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user