Skip to content

Commit 0d7b4a2

Browse files
Roo Coderuvnet
andcommitted
feat(solid): SPARQL PATCH, Type Index discovery, and agent memory in Pods
Phase 2: SPARQL PATCH for ontology mutations (ADR-028) - JssOntologyService: patchOntology(), patchOntologyN3(), addOntologyTriple(), removeOntologyTriple(), updateOntologyTriple() - Supports SPARQL Update and N3 Patch with solid:where concurrency Phase 3: Type Index for discovery (ADR-029) - SolidPodService: ensurePublicTypeIndex(), registerViewInTypeIndex(), registerAgentInTypeIndex(), discoverSharedViews(), discoverAgents() - TypeRegistration, TypeIndexDocument, DiscoveredView, DiscoveredAgent interfaces Phase 4: Agent memory in Pods (ADR-030) - SolidPodService: storeAgentMemory(), listAgentMemories(), getAgentMemory(), deleteAgentMemory(), setAgentMemoryAccess() - Per-agent containers with WAC access control - JSON-LD DigitalDocument format for memory entries Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 2d00d42 commit 0d7b4a2

5 files changed

Lines changed: 1061 additions & 0 deletions

File tree

client/src/features/ontology/services/JssOntologyService.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ export interface FetchOptions {
6969
timeout?: number;
7070
}
7171

72+
export interface PatchResult {
73+
success: boolean;
74+
status: number;
75+
statusText: string;
76+
}
77+
78+
/**
79+
* Represents an RDF term that can be serialized into SPARQL or N3.
80+
* Use angle brackets for IRIs, quotes for literals, prefixed names as-is.
81+
*/
82+
export interface RdfTerm {
83+
value: string;
84+
/** 'iri' wraps in <>, 'literal' wraps in "", 'prefixed' emits as-is */
85+
type: 'iri' | 'literal' | 'prefixed';
86+
}
87+
7288
// --- Service Implementation ---
7389

7490
class JssOntologyService {
@@ -640,6 +656,151 @@ class JssOntologyService {
640656
return Array.isArray(type) ? type : [type];
641657
}
642658

659+
// --- SPARQL PATCH Mutations ---
660+
661+
/**
662+
* Send a SPARQL Update PATCH to the ontology resource.
663+
* Uses Content-Type: application/sparql-update as per Solid Protocol.
664+
*/
665+
public async patchOntology(sparqlUpdate: string): Promise<PatchResult> {
666+
const url = this.getOntologyUrl();
667+
668+
try {
669+
const response = await this.fetchWithAuth(url, {
670+
method: 'PATCH',
671+
headers: {
672+
'Content-Type': 'application/sparql-update',
673+
},
674+
body: sparqlUpdate,
675+
});
676+
677+
if (response.ok) {
678+
this.invalidateCache();
679+
}
680+
681+
if (debugState.isEnabled()) {
682+
logger.info('SPARQL PATCH sent', {
683+
status: response.status,
684+
bodyLength: sparqlUpdate.length,
685+
});
686+
}
687+
688+
return {
689+
success: response.ok,
690+
status: response.status,
691+
statusText: response.statusText,
692+
};
693+
} catch (error) {
694+
logger.error('SPARQL PATCH failed', createErrorMetadata(error));
695+
throw error;
696+
}
697+
}
698+
699+
/**
700+
* Send an N3 Patch to the ontology resource.
701+
* Uses Content-Type: text/n3 for optimistic concurrency via solid:where clauses.
702+
*
703+
* N3 Patch format (Solid Protocol):
704+
* @prefix solid: <http://www.w3.org/ns/solid/terms#>.
705+
* _:patch a solid:InsertDeletePatch;
706+
* solid:where { ?cond ... };
707+
* solid:deletes { ?old ... };
708+
* solid:inserts { ?new ... }.
709+
*/
710+
public async patchOntologyN3(n3Patch: string): Promise<PatchResult> {
711+
const url = this.getOntologyUrl();
712+
713+
try {
714+
const response = await this.fetchWithAuth(url, {
715+
method: 'PATCH',
716+
headers: {
717+
'Content-Type': 'text/n3',
718+
},
719+
body: n3Patch,
720+
});
721+
722+
if (response.ok) {
723+
this.invalidateCache();
724+
}
725+
726+
if (debugState.isEnabled()) {
727+
logger.info('N3 PATCH sent', {
728+
status: response.status,
729+
bodyLength: n3Patch.length,
730+
});
731+
}
732+
733+
return {
734+
success: response.ok,
735+
status: response.status,
736+
statusText: response.statusText,
737+
};
738+
} catch (error) {
739+
logger.error('N3 PATCH failed', createErrorMetadata(error));
740+
throw error;
741+
}
742+
}
743+
744+
// --- Triple Mutation Helpers ---
745+
746+
/**
747+
* Add a single triple to the ontology via SPARQL INSERT DATA.
748+
*/
749+
public async addOntologyTriple(
750+
subject: RdfTerm,
751+
predicate: RdfTerm,
752+
object: RdfTerm
753+
): Promise<PatchResult> {
754+
const sparql = `INSERT DATA {\n ${this.serializeTerm(subject)} ${this.serializeTerm(predicate)} ${this.serializeTerm(object)} .\n}`;
755+
return this.patchOntology(sparql);
756+
}
757+
758+
/**
759+
* Remove a single triple from the ontology via SPARQL DELETE DATA.
760+
*/
761+
public async removeOntologyTriple(
762+
subject: RdfTerm,
763+
predicate: RdfTerm,
764+
object: RdfTerm
765+
): Promise<PatchResult> {
766+
const sparql = `DELETE DATA {\n ${this.serializeTerm(subject)} ${this.serializeTerm(predicate)} ${this.serializeTerm(object)} .\n}`;
767+
return this.patchOntology(sparql);
768+
}
769+
770+
/**
771+
* Update a triple's object value via SPARQL DELETE/INSERT WHERE.
772+
* Atomically removes the old value and inserts the new one.
773+
*/
774+
public async updateOntologyTriple(
775+
subject: RdfTerm,
776+
predicate: RdfTerm,
777+
oldValue: RdfTerm,
778+
newValue: RdfTerm
779+
): Promise<PatchResult> {
780+
const s = this.serializeTerm(subject);
781+
const p = this.serializeTerm(predicate);
782+
const sparql = [
783+
`DELETE { ${s} ${p} ${this.serializeTerm(oldValue)} . }`,
784+
`INSERT { ${s} ${p} ${this.serializeTerm(newValue)} . }`,
785+
`WHERE { ${s} ${p} ${this.serializeTerm(oldValue)} . }`,
786+
].join('\n');
787+
return this.patchOntology(sparql);
788+
}
789+
790+
/**
791+
* Serialize an RdfTerm into its SPARQL string representation.
792+
*/
793+
private serializeTerm(term: RdfTerm): string {
794+
switch (term.type) {
795+
case 'iri':
796+
return `<${term.value}>`;
797+
case 'literal':
798+
return `"${term.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
799+
case 'prefixed':
800+
return term.value;
801+
}
802+
}
803+
643804
// --- Public Getters ---
644805

645806
public isConnected(): boolean {

0 commit comments

Comments
 (0)