mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +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();
|
String actualSlug = existingByCanonical.getSlug();
|
||||||
pageService.updatePageByAi(kbId, actualSlug, content, pageSummary, rawId);
|
pageService.updatePageByAi(kbId, actualSlug, content, pageSummary, rawId);
|
||||||
pageService.mergeSourceLineage(existingByCanonical.getId(), rawId, raw.getTitle());
|
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",
|
log.info("[Wiki] Phase B create slug='{}' canonical-matches existing '{}', updated",
|
||||||
slug, actualSlug);
|
slug, actualSlug);
|
||||||
return false;
|
return false;
|
||||||
@ -1446,7 +1446,7 @@ public class WikiProcessingService {
|
|||||||
if (winner != null) {
|
if (winner != null) {
|
||||||
pageService.updatePageByAi(kbId, winnerSlug, content, pageSummary, rawId);
|
pageService.updatePageByAi(kbId, winnerSlug, content, pageSummary, rawId);
|
||||||
pageService.mergeSourceLineage(winner.getId(), rawId, raw.getTitle());
|
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",
|
log.info("[Wiki] Phase B create slug='{}' lost slug-claim race to '{}', updated",
|
||||||
slug, winnerSlug);
|
slug, winnerSlug);
|
||||||
return false;
|
return false;
|
||||||
@ -1462,7 +1462,7 @@ public class WikiProcessingService {
|
|||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
||||||
pageService.mergeSourceLineage(existing.getId(), rawId, raw.getTitle());
|
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);
|
log.info("[Wiki] Phase B create page slug='{}' done (updated existing)", slug);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1471,7 +1471,7 @@ public class WikiProcessingService {
|
|||||||
try {
|
try {
|
||||||
WikiPageEntity created = pageService.createPage(kbId, slug, title, content, pageSummary, sourceRawIds, pageType);
|
WikiPageEntity created = pageService.createPage(kbId, slug, title, content, pageSummary, sourceRawIds, pageType);
|
||||||
pageService.mergeSourceLineage(created.getId(), rawId, raw.getTitle());
|
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);
|
log.info("[Wiki] Phase B create page slug='{}' done (created)", slug);
|
||||||
citationService.buildCitationsAsync(created.getId(), kbId);
|
citationService.buildCitationsAsync(created.getId(), kbId);
|
||||||
return true;
|
return true;
|
||||||
@ -1480,7 +1480,7 @@ public class WikiProcessingService {
|
|||||||
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
pageService.updatePageByAi(kbId, slug, content, pageSummary, rawId);
|
||||||
WikiPageEntity raced = pageService.getBySlug(kbId, slug);
|
WikiPageEntity raced = pageService.getBySlug(kbId, slug);
|
||||||
if (raced != null) {
|
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);
|
log.info("[Wiki] Phase B create page slug='{}' lost INSERT race -> updated existing", slug);
|
||||||
return false;
|
return false;
|
||||||
@ -1500,7 +1500,7 @@ public class WikiProcessingService {
|
|||||||
* the same metadata and trigger handling as a clean create.
|
* the same metadata and trigger handling as a clean create.
|
||||||
*/
|
*/
|
||||||
private void afterPagePersisted(Long pageId, Long kbId, String pageType,
|
private void afterPagePersisted(Long pageId, Long kbId, String pageType,
|
||||||
JsonNode metadataNode, JsonNode dependsOnNode) {
|
JsonNode metadataNode, JsonNode dependsOnNode, boolean isUpdate) {
|
||||||
applyValidatedMetadata(pageId, kbId, pageType, metadataNode);
|
applyValidatedMetadata(pageId, kbId, pageType, metadataNode);
|
||||||
deriveKnowledgeLayer(pageId, kbId, pageType);
|
deriveKnowledgeLayer(pageId, kbId, pageType);
|
||||||
applyDependencies(pageId, kbId, pageType, dependsOnNode);
|
applyDependencies(pageId, kbId, pageType, dependsOnNode);
|
||||||
@ -1510,6 +1510,13 @@ public class WikiProcessingService {
|
|||||||
if (eventPublisher != null && pageType != null && !pageType.isBlank()) {
|
if (eventPublisher != null && pageType != null && !pageType.isBlank()) {
|
||||||
eventPublisher.publishEvent(new vip.mate.wiki.event.WikiPageCreatedEvent(kbId, pageType));
|
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. */
|
/** 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