Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f96dcdb
Apply Operations types
Sotatek-Patrick-Vu Apr 2, 2026
5cb9cc4
fix CompletedDelegationOperationResponseDeserializer
Sotatek-Patrick-Vu Apr 3, 2026
42c2f7f
improve general OP types, EventResult
Sotatek-Patrick-Vu Apr 3, 2026
9ee5fdb
fix EventResult for inception/rotation/interaction using a custom gro…
Sotatek-Patrick-Vu Apr 8, 2026
a32b3bd
fix op depends, use Operation type instead of Object, Map
Sotatek-Patrick-Vu Apr 9, 2026
63b91f3
fix review comments: improve deleting op and it's depends
Sotatek-Patrick-Vu Apr 10, 2026
8e742a8
Add sealed interface for DelegatorOperationDepends
Sotatek-Patrick-Vu Apr 10, 2026
3583fce
fix sealed interfaces
Sotatek-Patrick-Vu Apr 10, 2026
ae74657
improve EventResult
Sotatek-Patrick-Vu Apr 14, 2026
e3e1c7b
improve operation metadata depends
Sotatek-Patrick-Vu Apr 14, 2026
27b2259
improve types in some identifier actions
Sotatek-Patrick-Vu Apr 15, 2026
a0f3b9d
define KelOperation in spec
Sotatek-Patrick-Vu Apr 16, 2026
9512378
fix test
Sotatek-Patrick-Vu Apr 16, 2026
13e3042
improve operations
Sotatek-Patrick-Vu Apr 17, 2026
15a26ff
using KelOperation instead of DelegatorDependsOperation
Sotatek-Patrick-Vu Apr 17, 2026
a61c611
fix OperationsTest
Sotatek-Patrick-Vu Apr 24, 2026
e598a7a
add util waitForCompleted + use specific ops instead of general Opera…
Sotatek-Patrick-Vu Apr 29, 2026
13fa0fc
resolve review comments: RegistryResult is inconsistent with EventResult
Sotatek-Patrick-Vu May 8, 2026
1a4d3eb
specify types for MultisigUtils
Sotatek-Patrick-Vu May 8, 2026
487aee3
more specific types for some op tests
Sotatek-Patrick-Vu May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,96 @@ tasks.register("prepareOpenApiSpec") {

tasks.named("openApiGenerate") {
dependsOn("prepareOpenApiSpec")

doLast {
def ops = [
"Challenge", "Credential", "Delegation", "Delegator",
"Done", "EndRole", "Exchange", "Group",
"LocScheme", "OOBI", "Query", "Registry",
"Submit", "Witness"
]
def kelOps = ["Done", "Witness", "Submit", "Group", "Delegation"] as Set
def modelDir = file("src/main/java/org/cardanofoundation/signify/generated/keria/model")
def dualExtendsOps = ["Group", "Witness", "Done"]

ops.each { op ->
// Detect metadata type from the Pending variant
def pendingFile = new File(modelDir, "Pending${op}Operation.java")
if (!pendingFile.exists()) {
logger.warn("Pending${op}Operation.java not found, skipping ${op}")
return
}
def metadataMatcher = (pendingFile.text =~ /private\s+(\w+)\s+metadata;/)
def metadataType = metadataMatcher ? metadataMatcher[0][1] : null

// Add 'implements XxxOperation' to Pending/Completed/Failed classes
// Also add @JsonDeserialize to break inheritance from Operation's custom deserializer
["Pending", "Completed", "Failed"].each { state ->
def f = new File(modelDir, "${state}${op}Operation.java")
if (f.exists()) {
def className = "${state}${op}Operation"
f.text = f.text.replace(
"public class ${className} {",
"@com.fasterxml.jackson.databind.annotation.JsonDeserialize\npublic final class ${className} implements ${op}Operation {"
)
}
}

// Replace the generated merged oneOf class with a sealed interface
def target = new File(modelDir, "${op}Operation.java")
def metadataMethod = metadataType ? "\n ${metadataType} getMetadata();" : ""
def extendsClause = kelOps.contains(op)
? (dualExtendsOps.contains(op) ? "extends KelOperation" : "extends KelOperation")
: "extends Operation"
target.text = """
package org.cardanofoundation.signify.generated.keria.model;

public sealed interface ${op}Operation ${extendsClause} permits
Pending${op}Operation,
Completed${op}Operation,
Failed${op}Operation {
${metadataMethod}
}
"""
}

// Generate the general Operation sealed interface (union of all operation types)
def operationFile = new File(modelDir, "Operation.java")
operationFile.text = """
package org.cardanofoundation.signify.generated.keria.model;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.cardanofoundation.signify.app.coring.OperationDeserializer;

@JsonDeserialize(using = OperationDeserializer.class)
public sealed interface Operation permits
ChallengeOperation,
CredentialOperation,
DelegatorOperation,
EndRoleOperation,
ExchangeOperation,
KelOperation,
LocSchemeOperation,
OOBIOperation,
QueryOperation,
RegistryOperation {

String getName();
}
"""

// Generate KelOperation — a sealed group interface for all KEL event operation types.
Comment thread
iFergal marked this conversation as resolved.
def kelPermits = kelOps.sort().collect { op -> " ${op}Operation" }.join(",\n")
def kelOperationFile = new File(modelDir, "KelOperation.java")
kelOperationFile.text = """\
package org.cardanofoundation.signify.generated.keria.model;

/**
* Marker interface for operations that result from appending a KEL event.
*/
public sealed interface KelOperation extends Operation permits
${kelPermits} {
}
"""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.Getter;
import org.cardanofoundation.signify.app.clienting.SignifyClient;
import org.cardanofoundation.signify.app.coring.Operation;
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.generated.keria.model.ChallengeOperation;
import org.cardanofoundation.signify.generated.keria.model.Challenge;
import org.cardanofoundation.signify.generated.keria.model.Contact;
import org.cardanofoundation.signify.generated.keria.model.Exn;
Expand Down Expand Up @@ -91,14 +91,14 @@ public Exn respond(String name, String recipient, List<String> words) throws IOE
* @return The long-running operation
* @throws Exception if the fetch operation fails
*/
public Operation<?> verify(String source, List<String> words) throws LibsodiumException, IOException, InterruptedException {
public ChallengeOperation verify(String source, List<String> words) throws LibsodiumException, IOException, InterruptedException {
String path = "/challenges_verify/" + source;
String method = "POST";
Map<String, Object> data = new LinkedHashMap<>();
data.put("words", words);

HttpResponse<String> response = this.client.fetch(path, method, data);
return Utils.fromJson(response.body(), new TypeReference<Operation<?>>() {});
return Utils.fromJson(response.body(), ChallengeOperation.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.cardanofoundation.signify.app.aiding.EventResult;
import org.cardanofoundation.signify.app.aiding.InteractionResponse;
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.generated.keria.model.DelegatorOperation;

import java.io.IOException;
import java.net.http.HttpResponse;
Expand All @@ -30,7 +32,7 @@ public Delegations(SignifyClient client) {
* @return The delegated approval result
* @throws Exception if the fetch operation fails
*/
public EventResult approve(String name, Object data) throws LibsodiumException, DigestException, IOException, InterruptedException {
public EventResult<DelegatorOperation> approve(String name, Object data) throws LibsodiumException, DigestException, IOException, InterruptedException {
InteractionResponse interactionResponse = this.client
.identifiers()
.createInteract(name, data);
Expand All @@ -40,10 +42,11 @@ public EventResult approve(String name, Object data) throws LibsodiumException,
"POST",
interactionResponse.jsondata()
);
return new EventResult(interactionResponse.serder(), interactionResponse.sigs(), res);
DelegatorOperation op = Utils.fromJson(res.body(), DelegatorOperation.class);
return new EventResult<>(interactionResponse.serder(), interactionResponse.sigs(), op);
}

public EventResult approve(String name) throws LibsodiumException, DigestException, IOException, InterruptedException {
public EventResult<DelegatorOperation> approve(String name) throws LibsodiumException, DigestException, IOException, InterruptedException {
return this.approve(name, null);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import java.util.List;

import org.cardanofoundation.signify.cesr.Serder;
import org.cardanofoundation.signify.generated.keria.model.Operation;

public record EventResult(Serder serder, List<String> sigs, HttpResponse<String> response) {

public Object op() {
return response.body();
public record EventResult<T extends Operation>(Serder serder, List<String> sigs, T opInstance) {
public T op() {
return opInstance;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@

package org.cardanofoundation.signify.app.aiding;
import org.cardanofoundation.signify.generated.keria.model.PendingGroupOperation;
import org.cardanofoundation.signify.generated.keria.model.PendingWitnessOperation;
import org.cardanofoundation.signify.generated.keria.model.PendingDelegationOperation;
import org.cardanofoundation.signify.generated.keria.model.PendingDoneOperation;

import com.fasterxml.jackson.core.type.TypeReference;
import org.cardanofoundation.signify.cesr.Keeping;
Expand Down Expand Up @@ -27,10 +32,12 @@
import java.util.*;
import java.util.concurrent.ExecutionException;
import org.cardanofoundation.signify.generated.keria.model.EndrolesAidPostRequest;
import org.cardanofoundation.signify.generated.keria.model.EndRoleOperation;
import org.cardanofoundation.signify.generated.keria.model.GroupMember;
import org.cardanofoundation.signify.generated.keria.model.HabState;
import org.cardanofoundation.signify.generated.keria.model.KelOperation;
import org.cardanofoundation.signify.generated.keria.model.Operation;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecordKt;

import static org.cardanofoundation.signify.cesr.util.CoreUtil.Versionage;
import static org.cardanofoundation.signify.core.Httping.parseRangeHeaders;
Expand Down Expand Up @@ -129,7 +136,7 @@ public HabState update(String name, IdentifierInfo info) throws InterruptedExcep
* @param kargs Optional parameters to create the identifier
* @return An EventResult to the inception result
*/
public EventResult create(String name, CreateIdentifierArgs kargs) throws InterruptedException, DigestException, IOException, LibsodiumException {
public EventResult<KelOperation> create(String name, CreateIdentifierArgs kargs) throws InterruptedException, DigestException, IOException, LibsodiumException {
// Assuming kargs is an instance of a class with appropriate getters
Algos algo = kargs.getAlgo() == null ? Algos.salty : kargs.getAlgo();

Expand Down Expand Up @@ -275,7 +282,8 @@ public EventResult create(String name, CreateIdentifierArgs kargs) throws Interr
this.client.setPidx(this.client.getPidx() + 1);

HttpResponse<String> response = this.client.fetch("/identifiers", "POST", jsondata);
return new EventResult(serder, sigs, response);
KelOperation kelOp = Utils.fromJson(response.body(), KelOperation.class);
return new EventResult<KelOperation>(serder, sigs, kelOp);
}


Expand All @@ -290,7 +298,7 @@ public EventResult create(String name, CreateIdentifierArgs kargs) throws Interr
* @return An EventResult to the result of the authorization
* @throws LibsodiumException if there is an error in the cryptographic operations
*/
public EventResult addEndRole(String name, String role, String eid, String stamp) throws InterruptedException, DigestException, IOException, LibsodiumException {
public EventResult<EndRoleOperation> addEndRole(String name, String role, String eid, String stamp) throws InterruptedException, DigestException, IOException, LibsodiumException {
HabState hab = this.get(name)
.orElseThrow(() -> new IllegalArgumentException("Identifier not found: " + name));
String pre = hab.getPrefix();
Expand All @@ -310,7 +318,8 @@ public EventResult addEndRole(String name, String role, String eid, String stamp
"POST",
endrolesAidPostRequest
);
return new EventResult(rpy, sigs, res);
EndRoleOperation op = Utils.fromJson(res.body(), EndRoleOperation.class);
return new EventResult<>(rpy, sigs, op);
}

/**
Expand All @@ -335,14 +344,15 @@ private Serder makeEndRole(String pre, String role, String eid, String stamp) th
return Eventing.reply(route, data, stamp, null, Serials.JSON);
}

public EventResult interact(String name, Object data) throws InterruptedException, DigestException, IOException, LibsodiumException {
public EventResult<KelOperation> interact(String name, Object data) throws InterruptedException, DigestException, IOException, LibsodiumException {
InteractionResponse interactionResponse = this.createInteract(name, data);
HttpResponse<String> response = this.client.fetch(
"/identifiers/" + name + "/events",
"POST",
interactionResponse.jsondata()
);
return new EventResult(interactionResponse.serder(), interactionResponse.sigs(), response);
KelOperation kelOp = Utils.fromJson(response.body(), KelOperation.class);
return new EventResult<KelOperation>(interactionResponse.serder(), interactionResponse.sigs(), kelOp);
}

public InteractionResponse createInteract(String name, Object data) throws InterruptedException, DigestException, IOException, LibsodiumException {
Expand All @@ -358,10 +368,13 @@ public InteractionResponse createInteract(String name, Object data) throws Inter
data = Collections.singletonList(data);
}

@SuppressWarnings("unchecked")
List<Object> dataList = (List<Object>) data;

InteractArgs interactArgs = InteractArgs.builder()
.pre(pre)
.sn(BigInteger.valueOf(sn + 1))
.data((List<Object>) data)
.data(dataList)
.dig(dig)
.build();
Serder serder = Eventing.interact(interactArgs);
Expand All @@ -376,11 +389,11 @@ public InteractionResponse createInteract(String name, Object data) throws Inter
return new InteractionResponse(serder, sigs.signatures(), jsondata);
}

public EventResult rotate(String name) throws ExecutionException, InterruptedException, DigestException, IOException, LibsodiumException {
public EventResult<KelOperation> rotate(String name) throws ExecutionException, InterruptedException, DigestException, IOException, LibsodiumException {
return this.rotate(name, RotateIdentifierArgs.builder().build());
}

public EventResult rotate(String name, RotateIdentifierArgs kargs) throws InterruptedException, DigestException, IOException, LibsodiumException {
public EventResult<KelOperation> rotate(String name, RotateIdentifierArgs kargs) throws InterruptedException, DigestException, IOException, LibsodiumException {
boolean transferable = kargs.getTransferable() != null ? kargs.getTransferable() : true;
String ncode = kargs.getNcode() != null ? kargs.getNcode() : MatterCodex.Ed25519_Seed.getValue();
int ncount = kargs.getNcount() != null ? kargs.getNcount() : 1;
Expand Down Expand Up @@ -464,7 +477,8 @@ public EventResult rotate(String name, RotateIdentifierArgs kargs) throws Interr
jsondata
);

return new EventResult(serder, sigs, res);
KelOperation kelOp = Utils.fromJson(res.body(), KelOperation.class);
return new EventResult<KelOperation>(serder, sigs, kelOp);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.generated.keria.model.KeyStateRecord;
import org.cardanofoundation.signify.generated.keria.model.QueryOperation;

import java.io.IOException;
import java.net.HttpURLConnection;
Expand Down Expand Up @@ -67,7 +68,7 @@ public List<KeyStateRecord> list(List<String> pres) throws LibsodiumException, I
* @return A map representing the long-running operation
* @throws Exception if the fetch operation fails
*/
public Object query(String pre, String sn, Object anchor) throws LibsodiumException, IOException, InterruptedException {
public QueryOperation query(String pre, String sn, Object anchor) throws LibsodiumException, IOException, InterruptedException {
String path = "/queries";
Map<String, Object> data = new LinkedHashMap<>();
data.put("pre", pre);
Expand All @@ -79,10 +80,10 @@ public Object query(String pre, String sn, Object anchor) throws LibsodiumExcept
}
String method = "POST";
HttpResponse<String> res = this.client.fetch(path, method, data);
return Utils.fromJson(res.body(), Object.class);
return Utils.fromJson(res.body(), QueryOperation.class);
}

public Object query(String pre, String sn) throws Exception {
public QueryOperation query(String pre, String sn) throws LibsodiumException, IOException, InterruptedException {
return query(pre, sn, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.cardanofoundation.signify.cesr.exceptions.LibsodiumException;
import org.cardanofoundation.signify.cesr.util.Utils;
import org.cardanofoundation.signify.generated.keria.model.OOBI;
import org.cardanofoundation.signify.generated.keria.model.OOBIOperation;

public class Oobis {
private final SignifyClient client;
Expand Down Expand Up @@ -53,7 +54,7 @@ public Optional<OOBI> get(String name, String role) throws IOException, Interrup
* @throws JsonProcessingException if there is an error processing the JSON
* @throws LibsodiumException if there is an error in the cryptographic operations
*/
public Object resolve(String oobi, String alias) throws IOException, InterruptedException, LibsodiumException {
public OOBIOperation resolve(String oobi, String alias) throws IOException, InterruptedException, LibsodiumException {
String path = "/oobis";
String method = "POST";

Expand All @@ -63,6 +64,6 @@ public Object resolve(String oobi, String alias) throws IOException, Interrupted
data.put("oobialias", alias);
}
HttpResponse<String> response = this.client.fetch(path, method, data);
return Utils.fromJson(response.body(), Object.class);
return Utils.fromJson(response.body(), OOBIOperation.class);
}
}
Loading