• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-query / 24658059155

20 Apr 2026 09:06AM UTC coverage: 60.573% (-0.2%) from 60.755%
24658059155

push

github

web-flow
Merge pull request #72 from knowledgepixels/fix/space-registry-sync-and-feature-flags

fix/feat: synchronise SpaceRegistry and add trust/spaces feature flags

293 of 538 branches covered (54.46%)

Branch coverage included in aggregate %.

827 of 1311 relevant lines covered (63.08%)

9.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.42
src/main/java/com/knowledgepixels/query/SpacesAdminStore.java
1
package com.knowledgepixels.query;
2

3
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
4
import org.eclipse.rdf4j.model.IRI;
5
import org.eclipse.rdf4j.model.ValueFactory;
6
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
7
import org.eclipse.rdf4j.query.QueryLanguage;
8
import org.eclipse.rdf4j.query.TupleQueryResult;
9
import org.eclipse.rdf4j.repository.RepositoryConnection;
10
import org.nanopub.vocabulary.NPA;
11
import org.slf4j.Logger;
12
import org.slf4j.LoggerFactory;
13

14
import com.knowledgepixels.query.vocabulary.GEN;
15
import com.knowledgepixels.query.vocabulary.NPAS;
16

17
import net.trustyuri.TrustyUriUtils;
18

19
/**
20
 * Admin-repo persistence for {@link SpaceRegistry}'s known {@code (spaceRef, spaceIri)}
21
 * pairs. Mirrors the pattern used by {@link TrustStateLoader} for trust-state pointer
22
 * persistence: pure I/O wrapper around {@link TripleStore}, with a {@link #bootstrap}
23
 * call run once at startup and a {@link #persistSpace} call invoked whenever
24
 * {@link NanopubLoader#detectAndRegisterSpaces} adds a new space.
25
 *
26
 * <p>Persisted shape (in the admin repo's {@link NPA#GRAPH}):
27
 * <pre>{@code
28
 *   <npas:spaceRef> npa:hasSpaceIri <spaceIRI> .
29
 * }</pre>
30
 *
31
 * <p>Role properties and the source-nanopub reverse index are <em>not</em> persisted
32
 * — they are re-derived as nanopubs flow through the loader. See
33
 * {@code doc/plan-space-repositories.md}.
34
 */
35
public class SpacesAdminStore {
36

37
    private static final Logger log = LoggerFactory.getLogger(SpacesAdminStore.class);
9✔
38

39
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
6✔
40

41
    /**
42
     * Predicate linking a space ref (subject in the {@code npas:} namespace) to its
43
     * Space IRI. Defined locally rather than in a vocab class because it's strictly
44
     * internal to space-registry persistence.
45
     */
46
    static final IRI NPA_HAS_SPACE_IRI = vf.createIRI(NPA.NAMESPACE, "hasSpaceIri");
18✔
47

48
    private SpacesAdminStore() {
49
    }
50

51
    /**
52
     * Loads any persisted {@code (spaceRef, spaceIri)} pairs from the admin repo and
53
     * registers them in the given registry. Intended to run once at startup.
54
     *
55
     * <p>Safe to call on a fresh deployment (admin repo may be empty — registers
56
     * nothing). Any failure is logged at INFO; the registry is left empty and the
57
     * loader will rebuild it as nanopubs flow through.
58
     *
59
     * @param registry the registry to seed
60
     */
61
    public static void bootstrap(SpaceRegistry registry) {
62
        if (!FeatureFlags.spacesEnabled()) return;
×
63
        try (RepositoryConnection conn = TripleStore.get().getRepoConnection(TripleStore.ADMIN_REPO)) {
×
64
            String query = String.format("""
×
65
                    SELECT ?ref ?iri WHERE {
66
                      GRAPH <%s> {
67
                        ?ref <%s> ?iri .
68
                      }
69
                    }
70
                    """,
71
                    NPA.GRAPH, NPA_HAS_SPACE_IRI);
72
            int loaded = 0;
×
73
            try (TupleQueryResult result =
×
74
                         conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate()) {
×
75
                while (result.hasNext()) {
×
76
                    var binding = result.next();
×
77
                    IRI refIri = (IRI) binding.getValue("ref");
×
78
                    IRI spaceIri = (IRI) binding.getValue("iri");
×
79
                    String iriStr = refIri.stringValue();
×
80
                    if (!iriStr.startsWith(NPAS.NAMESPACE)) {
×
81
                        log.warn("Skipping persisted space ref with unexpected IRI: {}", iriStr);
×
82
                        continue;
×
83
                    }
84
                    String spaceRef = iriStr.substring(NPAS.NAMESPACE.length());
×
85
                    String rootNanopubId = registry.getRootNanopubId(spaceRef);
×
86
                    registry.registerSpace(rootNanopubId, spaceIri);
×
87
                    loaded++;
×
88
                }
×
89
            }
90
            if (loaded > 0) {
×
91
                log.info("Spaces bootstrap: seeded {} space(s) from admin repo", loaded);
×
92
            }
93
        } catch (Exception ex) {
×
94
            log.info("Spaces bootstrap: failed to read persisted spaces: {}", ex.toString());
×
95
        }
×
96
    }
×
97

98
    /**
99
     * Re-derives the known-spaces set by scanning the existing {@code type_<HASH>}
100
     * repo for {@link GEN#SPACE}. For each {@code <spaceIri> gen:hasRootDefinition
101
     * <rootUri>} triple found in any nanopub assertion there, registers the space
102
     * in {@link SpaceRegistry} and persists it via {@link #persistSpace} (so the
103
     * admin-repo state catches up).
104
     *
105
     * <p>Intended to run once at startup, after {@link #bootstrap}. Idempotent:
106
     * spaces already known to the registry are detected via the {@code wasNew}
107
     * signal and skipped. Lets existing deployments pick up space-defining
108
     * nanopubs that were loaded before this code shipped, without a fresh DB.
109
     *
110
     * <p>If the type repo doesn't exist (no {@code gen:Space}-typed nanopub has
111
     * ever been loaded), this is a no-op. Failures are logged at INFO and don't
112
     * propagate; the loader will eventually re-derive state as new nanopubs flow.
113
     *
114
     * @param registry the registry to seed
115
     */
116
    public static void scanExistingSpaces(SpaceRegistry registry) {
117
        if (!FeatureFlags.spacesEnabled()) return;
×
118
        String typeRepo = "type_" + Utils.createHash(GEN.SPACE);
×
119
        if (!TripleStore.get().getRepositoryNames().contains(typeRepo)) {
×
120
            log.info("Spaces scan: no {} repo yet — skipping", typeRepo);
×
121
            return;
×
122
        }
123
        try (RepositoryConnection conn = TripleStore.get().getRepoConnection(typeRepo)) {
×
124
            // The type_<hash(gen:Space)> repo holds only gen:Space-typed nanopubs,
125
            // so any hasRootDefinition triple in any assertion graph here belongs
126
            // to a Space-defining nanopub. Walking np -> assertion graph keeps the
127
            // result tied to its source nanopub for logging and provenance.
128
            String query = """
×
129
                    PREFIX np:  <http://www.nanopub.org/nschema#>
130
                    PREFIX gen: <https://w3id.org/kpxl/gen/terms/>
131
                    SELECT ?np ?spaceIri ?rootUri WHERE {
132
                      GRAPH ?head {
133
                        ?np a np:Nanopublication ;
134
                            np:hasAssertion ?assertion .
135
                      }
136
                      GRAPH ?assertion {
137
                        ?spaceIri gen:hasRootDefinition ?rootUri .
138
                      }
139
                    }
140
                    """;
141
            int seen = 0;
×
142
            int registered = 0;
×
143
            try (TupleQueryResult result =
×
144
                         conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate()) {
×
145
                while (result.hasNext()) {
×
146
                    seen++;
×
147
                    var binding = result.next();
×
148
                    if (!(binding.getValue("spaceIri") instanceof IRI spaceIri)) continue;
×
149
                    if (!(binding.getValue("rootUri") instanceof IRI rootUri)) continue;
×
150
                    String rootNanopubId = TrustyUriUtils.getArtifactCode(rootUri.stringValue());
×
151
                    if (rootNanopubId == null || rootNanopubId.isEmpty()) {
×
152
                        log.warn("Spaces scan: ignoring {} — gen:hasRootDefinition target is not a trusty URI: {}",
×
153
                                spaceIri, rootUri);
154
                        continue;
×
155
                    }
156
                    SpaceRegistry.Registration reg = registry.registerSpace(rootNanopubId, spaceIri);
×
157
                    if (reg.wasNew()) {
×
158
                        persistSpace(rootNanopubId, spaceIri);
×
159
                        registered++;
×
160
                    }
161
                }
×
162
            }
163
            log.info("Spaces scan: examined {} hasRootDefinition triple(s); newly registered {} space(s)",
×
164
                    seen, registered);
×
165
        } catch (Exception ex) {
×
166
            log.info("Spaces scan: failed to scan {}: {}", typeRepo, ex.toString());
×
167
        }
×
168
    }
×
169

170
    /**
171
     * Persists a newly-registered {@code (spaceRef, spaceIri)} pair into the admin
172
     * repo. Idempotent: re-persisting the same pair is harmless (the SPARQL INSERT
173
     * just re-asserts an existing triple).
174
     *
175
     * @param rootNanopubId artifact code of the root nanopub
176
     * @param spaceIri      the Space IRI
177
     */
178
    public static void persistSpace(String rootNanopubId, IRI spaceIri) {
179
        if (!FeatureFlags.spacesEnabled()) return;
6!
180
        String spaceRef = rootNanopubId + "_" + Utils.createHash(spaceIri);
15✔
181
        IRI refIri = NPAS.forSpaceRef(spaceRef);
9✔
182
        try (RepositoryConnection conn = TripleStore.get().getRepoConnection(TripleStore.ADMIN_REPO)) {
×
183
            conn.begin(IsolationLevels.SERIALIZABLE);
×
184
            conn.add(refIri, NPA_HAS_SPACE_IRI, spaceIri, NPA.GRAPH);
×
185
            conn.commit();
×
186
        } catch (Exception ex) {
3✔
187
            log.info("Failed to persist space {}: {}", spaceRef, ex.toString());
18✔
188
        }
×
189
    }
3✔
190

191
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc