Files
motr-enclave/internal/keybase/actions_invocation.go

258 lines
6.4 KiB
Go

package keybase
import (
"context"
"fmt"
)
// =============================================================================
// INVOCATION ACTIONS (UCAN v1.0.0-rc.1)
// =============================================================================
// InvocationResult represents an invocation in API responses.
type InvocationResult struct {
ID int64 `json:"id"`
CID string `json:"cid"`
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud,omitempty"`
Command string `json:"cmd"`
Proofs string `json:"prf"`
Expiration string `json:"exp,omitempty"`
IssuedAt string `json:"iat,omitempty"`
ExecutedAt string `json:"executed_at,omitempty"`
ResultCID string `json:"result_cid,omitempty"`
CreatedAt string `json:"created_at"`
}
// StoreInvocationParams contains parameters for storing an invocation.
type StoreInvocationParams struct {
CID string `json:"cid"`
Envelope []byte `json:"envelope"`
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud,omitempty"`
Command string `json:"cmd"`
Proofs string `json:"prf"`
Expiration string `json:"exp,omitempty"`
IssuedAt string `json:"iat,omitempty"`
}
// StoreInvocation stores a new UCAN invocation envelope.
func (am *ActionManager) StoreInvocation(ctx context.Context, params StoreInvocationParams) (*InvocationResult, error) {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return nil, fmt.Errorf("DID not initialized")
}
var aud, exp, iat *string
if params.Audience != "" {
aud = &params.Audience
}
if params.Expiration != "" {
exp = &params.Expiration
}
if params.IssuedAt != "" {
iat = &params.IssuedAt
}
inv, err := am.kb.queries.CreateInvocation(ctx, CreateInvocationParams{
DidID: am.kb.didID,
Cid: params.CID,
Envelope: params.Envelope,
Iss: params.Issuer,
Sub: params.Subject,
Aud: aud,
Cmd: params.Command,
Prf: params.Proofs,
Exp: exp,
Iat: iat,
})
if err != nil {
return nil, fmt.Errorf("create invocation: %w", err)
}
return invocationToResult(inv), nil
}
// GetInvocationByCID retrieves an invocation by its CID.
func (am *ActionManager) GetInvocationByCID(ctx context.Context, cid string) (*InvocationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
inv, err := am.kb.queries.GetInvocationByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("get invocation: %w", err)
}
return invocationToResult(inv), nil
}
// GetInvocationEnvelope retrieves the raw CBOR envelope for an invocation.
func (am *ActionManager) GetInvocationEnvelope(ctx context.Context, cid string) ([]byte, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
envelope, err := am.kb.queries.GetInvocationEnvelopeByCID(ctx, cid)
if err != nil {
return nil, fmt.Errorf("get invocation envelope: %w", err)
}
return envelope, nil
}
// ListInvocations returns recent invocations for the current DID.
func (am *ActionManager) ListInvocations(ctx context.Context, limit int64) ([]InvocationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []InvocationResult{}, nil
}
if limit <= 0 {
limit = 50
}
invocations, err := am.kb.queries.ListInvocationsByDID(ctx, ListInvocationsByDIDParams{
DidID: am.kb.didID,
Limit: limit,
})
if err != nil {
return nil, fmt.Errorf("list invocations: %w", err)
}
results := make([]InvocationResult, len(invocations))
for i, inv := range invocations {
results[i] = *invocationToResult(inv)
}
return results, nil
}
// ListInvocationsByCommand returns invocations for a specific command.
func (am *ActionManager) ListInvocationsByCommand(ctx context.Context, cmd string, limit int64) ([]InvocationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []InvocationResult{}, nil
}
if limit <= 0 {
limit = 50
}
invocations, err := am.kb.queries.ListInvocationsForCommand(ctx, ListInvocationsForCommandParams{
DidID: am.kb.didID,
Cmd: cmd,
Limit: limit,
})
if err != nil {
return nil, fmt.Errorf("list invocations for command: %w", err)
}
results := make([]InvocationResult, len(invocations))
for i, inv := range invocations {
results[i] = *invocationToResult(inv)
}
return results, nil
}
// ListPendingInvocations returns invocations that haven't been executed yet.
func (am *ActionManager) ListPendingInvocations(ctx context.Context) ([]InvocationResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []InvocationResult{}, nil
}
invocations, err := am.kb.queries.ListPendingInvocations(ctx, am.kb.didID)
if err != nil {
return nil, fmt.Errorf("list pending invocations: %w", err)
}
results := make([]InvocationResult, len(invocations))
for i, inv := range invocations {
results[i] = *invocationToResult(inv)
}
return results, nil
}
// MarkInvocationExecuted marks an invocation as executed with an optional result CID.
func (am *ActionManager) MarkInvocationExecuted(ctx context.Context, cid string, resultCID string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
var result *string
if resultCID != "" {
result = &resultCID
}
err := am.kb.queries.MarkInvocationExecuted(ctx, MarkInvocationExecutedParams{
ResultCid: result,
Cid: cid,
})
if err != nil {
return fmt.Errorf("mark invocation executed: %w", err)
}
return nil
}
// CleanOldInvocations removes invocations older than 90 days.
func (am *ActionManager) CleanOldInvocations(ctx context.Context) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if err := am.kb.queries.CleanOldInvocations(ctx); err != nil {
return fmt.Errorf("clean old invocations: %w", err)
}
return nil
}
// invocationToResult converts a UcanInvocation to InvocationResult.
func invocationToResult(inv UcanInvocation) *InvocationResult {
audience := ""
if inv.Aud != nil {
audience = *inv.Aud
}
expiration := ""
if inv.Exp != nil {
expiration = *inv.Exp
}
issuedAt := ""
if inv.Iat != nil {
issuedAt = *inv.Iat
}
executedAt := ""
if inv.ExecutedAt != nil {
executedAt = *inv.ExecutedAt
}
resultCID := ""
if inv.ResultCid != nil {
resultCID = *inv.ResultCid
}
return &InvocationResult{
ID: inv.ID,
CID: inv.Cid,
Issuer: inv.Iss,
Subject: inv.Sub,
Audience: audience,
Command: inv.Cmd,
Proofs: inv.Prf,
Expiration: expiration,
IssuedAt: issuedAt,
ExecutedAt: executedAt,
ResultCID: resultCID,
CreatedAt: inv.CreatedAt,
}
}