@@ 261,11 261,19 @@ func run(log *slog.Logger) error {
return err
}
+ surf, err := newSurfaces(conf, cfg, svc, version)
+ if err != nil {
+ return err
+ }
+ defer surf.Close()
+
+ // After the surfaces, because the push notifier reindexes through the same
+ // index they read from — one bleve writer, held here.
hookSrv, err := hooks.NewServer(hooks.Options{
Backend: svc,
Socket: hooks.SocketPath(cfg.Repos),
Log: log,
- OnPush: pushNotifier(log),
+ OnPush: pushNotifier(log, svc, surf.index),
})
if err != nil {
return err
@@ 274,12 282,6 @@ func run(log *slog.Logger) error {
return err
}
- surf, err := newSurfaces(conf, cfg, svc, version)
- if err != nil {
- return err
- }
- defer surf.Close()
-
// server.New parses -b/-d/-m/-p and runs crypto.InitCrypto(conf), whose
// two required keys validateConfig already checked, so it cannot fatal
// here for a reason we have not already reported.
@@ 520,16 522,61 @@ func mountWeb(router chi.Router, _ ini.File, s *surfaces) {
// index rev stamp are Phase 2's, because bleve and the stamp arrive together:
// moving the stamp now, with no index behind it, would assert that the index
// is current and remove the reconciler's only way of noticing that it is not.
-func pushNotifier(log *slog.Logger) hooks.PushNotifier {
- return func(_ context.Context, space core.SpaceRef, updates []hooks.RefUpdate) error {
+func pushNotifier(log *slog.Logger, svc *service.Service, index *search.Index) hooks.PushNotifier {
+ return func(ctx context.Context, space core.SpaceRef, updates []hooks.RefUpdate) error {
refs := make([]string, 0, len(updates))
for _, u := range updates {
refs = append(refs, u.String())
}
- log.Info("push landed; reindex pending",
- "space", space.String(),
- "refs", refs,
- "note", "indexing lands in Phase 2; the reconciler reports the staleness until then")
+
+ // A push that touches only proposal branches changes nothing the index
+ // holds: the index carries the approved revision, and proposal content
+ // is deliberately not searchable — surfacing unreviewed text in search
+ // is the same leak as serving it from the read plane.
+ approved := false
+ for _, u := range updates {
+ if !strings.HasPrefix(u.Ref, "refs/heads/"+core.ProposalPrefix) {
+ approved = true
+ break
+ }
+ }
+ if !approved {
+ log.Info("push landed; no reindex needed", "space", space.String(), "refs", refs)
+ return nil
+ }
+
+ sp, err := svc.OpenSpace(ctx, space)
+ if err != nil {
+ return fmt.Errorf("open %s to reindex: %w", space, err)
+ }
+ rev, err := svc.ResolveRev(ctx, sp, service.ApprovedRev)
+ if err != nil {
+ return fmt.Errorf("resolve the approved head of %s: %w", space, err)
+ }
+ arc, bodies, err := svc.Archive(ctx, sp, service.ApprovedRev)
+ if err != nil {
+ return fmt.Errorf("read %s at %s: %w", space, rev, err)
+ }
+ docs, err := search.Extract(arc, bodies)
+ if err != nil {
+ return fmt.Errorf("project %s for indexing: %w", space, err)
+ }
+ stats, err := index.RebuildSpace(ctx, space, docs)
+ if err != nil {
+ return fmt.Errorf("reindex %s: %w", space, err)
+ }
+
+ // The stamp goes last and only on success. Written earlier it would
+ // assert the index reflects a revision it does not, which is precisely
+ // the staleness the reconciler exists to detect — and it would detect
+ // nothing.
+ if _, err := svc.Store().SetIndexStamp(ctx, sp.ID, rev); err != nil {
+ return fmt.Errorf("stamp the index for %s at %s: %w", space, rev, err)
+ }
+
+ log.Info("push landed; space reindexed",
+ "space", space.String(), "refs", refs, "rev", rev,
+ "indexed", stats.Indexed, "deleted", stats.Deleted, "took", stats.Took.String())
return nil
}
}