+4
cmd/plcbundle/commands/common.go
+4
cmd/plcbundle/commands/common.go
···
35
35
GetDIDIndexStats() map[string]interface{}
36
36
GetDIDIndex() *didindex.Manager
37
37
BuildDIDIndex(ctx context.Context, progress func(int, int)) error
38
+
GetDIDOperations(ctx context.Context, did string, verbose bool) ([]plcclient.PLCOperation, error)
38
39
GetDIDOperationsWithLocations(ctx context.Context, did string, verbose bool) ([]bundle.PLCOperationWithLocation, error)
39
40
GetDIDOperationsFromMempool(did string) ([]plcclient.PLCOperation, error)
40
41
GetLatestDIDOperation(ctx context.Context, did string) (*plcclient.PLCOperation, error)
···
43
44
ResolveDID(ctx context.Context, did string) (*bundle.ResolveDIDResult, error)
44
45
RunSyncOnce(ctx context.Context, config *internalsync.SyncLoopConfig, verbose bool) (int, error)
45
46
RunSyncLoop(ctx context.Context, config *internalsync.SyncLoopConfig) error
47
+
GetBundleIndex() didindex.BundleIndexProvider
48
+
ScanDirectoryParallel(workers int, progressCallback func(current, total int, bytesProcessed int64)) (*bundle.DirectoryScanResult, error)
49
+
LoadBundleForDIDIndex(ctx context.Context, bundleNumber int) (*didindex.BundleData, error)
46
50
}
47
51
48
52
// PLCOperationWithLocation wraps operation with location info
+130
cmd/plcbundle/commands/did.go
+130
cmd/plcbundle/commands/did.go
···
978
978
979
979
return nil
980
980
}
981
+
982
+
// ============================================================================
983
+
// Shared Helper Functions (used by both DID and legacy index commands)
984
+
// ============================================================================
985
+
986
+
func outputLookupJSON(did string, opsWithLoc []PLCOperationWithLocation, mempoolOps []plcclient.PLCOperation, totalElapsed, lookupElapsed, mempoolElapsed time.Duration) error {
987
+
output := map[string]interface{}{
988
+
"found": true,
989
+
"did": did,
990
+
"timing": map[string]interface{}{
991
+
"total_ms": totalElapsed.Milliseconds(),
992
+
"lookup_ms": lookupElapsed.Milliseconds(),
993
+
"mempool_ms": mempoolElapsed.Milliseconds(),
994
+
},
995
+
"bundled": make([]map[string]interface{}, 0),
996
+
"mempool": make([]map[string]interface{}, 0),
997
+
}
998
+
999
+
for _, owl := range opsWithLoc {
1000
+
output["bundled"] = append(output["bundled"].([]map[string]interface{}), map[string]interface{}{
1001
+
"bundle": owl.Bundle,
1002
+
"position": owl.Position,
1003
+
"cid": owl.Operation.CID,
1004
+
"nullified": owl.Operation.IsNullified(),
1005
+
"created_at": owl.Operation.CreatedAt.Format(time.RFC3339Nano),
1006
+
})
1007
+
}
1008
+
1009
+
for _, op := range mempoolOps {
1010
+
output["mempool"] = append(output["mempool"].([]map[string]interface{}), map[string]interface{}{
1011
+
"cid": op.CID,
1012
+
"nullified": op.IsNullified(),
1013
+
"created_at": op.CreatedAt.Format(time.RFC3339Nano),
1014
+
})
1015
+
}
1016
+
1017
+
data, _ := json.MarshalIndent(output, "", " ")
1018
+
fmt.Println(string(data))
1019
+
1020
+
return nil
1021
+
}
1022
+
1023
+
func displayLookupResults(did string, opsWithLoc []PLCOperationWithLocation, mempoolOps []plcclient.PLCOperation, totalElapsed, lookupElapsed, mempoolElapsed time.Duration, verbose bool, stats map[string]interface{}) error {
1024
+
nullifiedCount := 0
1025
+
for _, owl := range opsWithLoc {
1026
+
if owl.Operation.IsNullified() {
1027
+
nullifiedCount++
1028
+
}
1029
+
}
1030
+
1031
+
totalOps := len(opsWithLoc) + len(mempoolOps)
1032
+
activeOps := len(opsWithLoc) - nullifiedCount + len(mempoolOps)
1033
+
1034
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
1035
+
fmt.Printf(" DID Lookup Results\n")
1036
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
1037
+
fmt.Printf("DID: %s\n\n", did)
1038
+
1039
+
fmt.Printf("Summary\n───────\n")
1040
+
fmt.Printf(" Total operations: %d\n", totalOps)
1041
+
fmt.Printf(" Active operations: %d\n", activeOps)
1042
+
if nullifiedCount > 0 {
1043
+
fmt.Printf(" Nullified: %d\n", nullifiedCount)
1044
+
}
1045
+
if len(opsWithLoc) > 0 {
1046
+
fmt.Printf(" Bundled: %d\n", len(opsWithLoc))
1047
+
}
1048
+
if len(mempoolOps) > 0 {
1049
+
fmt.Printf(" Mempool: %d\n", len(mempoolOps))
1050
+
}
1051
+
fmt.Printf("\n")
1052
+
1053
+
fmt.Printf("Performance\n───────────\n")
1054
+
fmt.Printf(" Index lookup: %s\n", lookupElapsed)
1055
+
fmt.Printf(" Mempool check: %s\n", mempoolElapsed)
1056
+
fmt.Printf(" Total time: %s\n\n", totalElapsed)
1057
+
1058
+
// Show operations
1059
+
if len(opsWithLoc) > 0 {
1060
+
fmt.Printf("Bundled Operations (%d total)\n", len(opsWithLoc))
1061
+
fmt.Printf("══════════════════════════════════════════════════════════════\n\n")
1062
+
1063
+
for i, owl := range opsWithLoc {
1064
+
op := owl.Operation
1065
+
status := "✓ Active"
1066
+
if op.IsNullified() {
1067
+
status = "✗ Nullified"
1068
+
}
1069
+
1070
+
fmt.Printf("Operation %d [Bundle %06d, Position %04d]\n", i+1, owl.Bundle, owl.Position)
1071
+
fmt.Printf(" CID: %s\n", op.CID)
1072
+
fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST"))
1073
+
fmt.Printf(" Status: %s\n", status)
1074
+
1075
+
if verbose && !op.IsNullified() {
1076
+
showOperationDetails(&op)
1077
+
}
1078
+
1079
+
fmt.Printf("\n")
1080
+
}
1081
+
}
1082
+
1083
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
1084
+
fmt.Printf("✓ Lookup complete in %s\n", totalElapsed)
1085
+
if stats["exists"].(bool) {
1086
+
fmt.Printf(" Method: DID index (fast)\n")
1087
+
} else {
1088
+
fmt.Printf(" Method: Full scan (slow)\n")
1089
+
}
1090
+
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
1091
+
1092
+
return nil
1093
+
}
1094
+
1095
+
func showOperationDetails(op *plcclient.PLCOperation) {
1096
+
if opData, err := op.GetOperationData(); err == nil && opData != nil {
1097
+
if opType, ok := opData["type"].(string); ok {
1098
+
fmt.Printf(" Type: %s\n", opType)
1099
+
}
1100
+
1101
+
if handle, ok := opData["handle"].(string); ok {
1102
+
fmt.Printf(" Handle: %s\n", handle)
1103
+
} else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 {
1104
+
if akaStr, ok := aka[0].(string); ok {
1105
+
handle := strings.TrimPrefix(akaStr, "at://")
1106
+
fmt.Printf(" Handle: %s\n", handle)
1107
+
}
1108
+
}
1109
+
}
1110
+
}
+291
-334
cmd/plcbundle/commands/index.go
+291
-334
cmd/plcbundle/commands/index.go
···
1
+
// repo/cmd/plcbundle/commands/index.go
1
2
package commands
2
3
3
4
import (
4
5
"context"
5
6
"fmt"
6
-
"os"
7
-
"strings"
8
7
"time"
9
-
10
-
flag "github.com/spf13/pflag"
11
8
12
9
"github.com/goccy/go-json"
10
+
"github.com/spf13/cobra"
13
11
"tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui"
14
-
"tangled.org/atscan.net/plcbundle/internal/plcclient"
15
12
)
16
13
17
-
// IndexCommand handles the index subcommand
18
-
func IndexCommand(args []string) error {
19
-
if len(args) < 1 {
20
-
printIndexUsage()
21
-
return fmt.Errorf("subcommand required")
14
+
func NewIndexCommand() *cobra.Command {
15
+
cmd := &cobra.Command{
16
+
Use: "index",
17
+
Short: "DID index management",
18
+
Long: `DID index management operations
19
+
20
+
Manage the DID position index which maps DIDs to their bundle locations.
21
+
This index enables fast O(1) DID lookups and is required for DID
22
+
resolution and query operations.`,
23
+
24
+
Example: ` # Build DID position index
25
+
plcbundle index build
26
+
27
+
# Repair DID index (rebuild from bundles)
28
+
plcbundle index repair
29
+
30
+
# Show DID index statistics
31
+
plcbundle index stats
32
+
33
+
# Verify DID index integrity
34
+
plcbundle index verify`,
22
35
}
23
36
24
-
subcommand := args[0]
37
+
cmd.AddCommand(newIndexBuildCommand())
38
+
cmd.AddCommand(newIndexRepairCommand())
39
+
cmd.AddCommand(newIndexStatsCommand())
40
+
cmd.AddCommand(newIndexVerifyCommand())
25
41
26
-
switch subcommand {
27
-
case "build":
28
-
return indexBuild(args[1:])
29
-
case "stats":
30
-
return indexStats(args[1:])
31
-
case "lookup":
32
-
return indexLookup(args[1:])
33
-
case "resolve":
34
-
return indexResolve(args[1:])
35
-
default:
36
-
printIndexUsage()
37
-
return fmt.Errorf("unknown index subcommand: %s", subcommand)
38
-
}
42
+
return cmd
39
43
}
40
44
41
-
func printIndexUsage() {
42
-
fmt.Printf(`Usage: plcbundle index <command> [options]
45
+
// ============================================================================
46
+
// INDEX BUILD - Build DID position index
47
+
// ============================================================================
48
+
49
+
func newIndexBuildCommand() *cobra.Command {
50
+
var force bool
51
+
52
+
cmd := &cobra.Command{
53
+
Use: "build",
54
+
Short: "Build DID position index",
55
+
Long: `Build DID position index from bundles
43
56
44
-
Commands:
45
-
build Build DID index from bundles
46
-
stats Show index statistics
47
-
lookup Lookup a specific DID
48
-
resolve Resolve DID to current document
57
+
Creates a sharded index mapping each DID to its bundle locations,
58
+
enabling fast O(1) DID lookups. Required for DID resolution.
59
+
60
+
The index is built incrementally and auto-updates as new bundles
61
+
are added. Use --force to rebuild from scratch.`,
49
62
50
-
Examples:
63
+
Example: ` # Build index
51
64
plcbundle index build
52
-
plcbundle index stats
53
-
plcbundle index lookup did:plc:524tuhdhh3m7li5gycdn6boe
54
-
plcbundle index resolve did:plc:524tuhdhh3m7li5gycdn6boe
55
-
`)
56
-
}
65
+
66
+
# Force rebuild from scratch
67
+
plcbundle index build --force`,
57
68
58
-
func indexBuild(args []string) error {
59
-
fs := flag.NewFlagSet("index build", flag.ExitOnError)
60
-
force := fs.Bool("force", false, "rebuild even if index exists")
69
+
RunE: func(cmd *cobra.Command, args []string) error {
70
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
71
+
if err != nil {
72
+
return err
73
+
}
74
+
defer mgr.Close()
61
75
62
-
if err := fs.Parse(args); err != nil {
63
-
return err
64
-
}
76
+
stats := mgr.GetDIDIndexStats()
77
+
if stats["exists"].(bool) && !force {
78
+
fmt.Printf("DID index already exists (use --force to rebuild)\n")
79
+
fmt.Printf("Directory: %s\n", dir)
80
+
fmt.Printf("Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
81
+
return nil
82
+
}
65
83
66
-
mgr, dir, err := getManager(nil)
67
-
if err != nil {
68
-
return err
69
-
}
70
-
defer mgr.Close()
84
+
index := mgr.GetIndex()
85
+
bundleCount := index.Count()
71
86
72
-
stats := mgr.GetDIDIndexStats()
73
-
if stats["exists"].(bool) && !*force {
74
-
fmt.Printf("DID index already exists (use --force to rebuild)\n")
75
-
fmt.Printf("Directory: %s\n", dir)
76
-
fmt.Printf("Total DIDs: %d\n", stats["total_dids"])
77
-
return nil
78
-
}
87
+
if bundleCount == 0 {
88
+
fmt.Printf("No bundles to index\n")
89
+
return nil
90
+
}
79
91
80
-
fmt.Printf("Building DID index in: %s\n", dir)
92
+
fmt.Printf("Building DID index in: %s\n", dir)
93
+
fmt.Printf("Indexing %d bundles...\n\n", bundleCount)
81
94
82
-
index := mgr.GetIndex()
83
-
bundleCount := index.Count()
95
+
progress := ui.NewProgressBar(bundleCount)
96
+
start := time.Now()
97
+
ctx := context.Background()
84
98
85
-
if bundleCount == 0 {
86
-
fmt.Printf("No bundles to index\n")
87
-
return nil
88
-
}
99
+
err = mgr.BuildDIDIndex(ctx, func(current, total int) {
100
+
progress.Set(current)
101
+
})
89
102
90
-
fmt.Printf("Indexing %d bundles...\n\n", bundleCount)
103
+
progress.Finish()
91
104
92
-
progress := ui.NewProgressBar(bundleCount)
93
-
start := time.Now()
94
-
ctx := context.Background()
105
+
if err != nil {
106
+
return fmt.Errorf("build failed: %w", err)
107
+
}
95
108
96
-
err = mgr.BuildDIDIndex(ctx, func(current, total int) {
97
-
progress.Set(current)
98
-
})
109
+
elapsed := time.Since(start)
110
+
stats = mgr.GetDIDIndexStats()
99
111
100
-
progress.Finish()
112
+
fmt.Printf("\n✓ DID index built in %s\n", elapsed.Round(time.Millisecond))
113
+
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
114
+
fmt.Printf(" Shards: %d\n", stats["shard_count"])
115
+
fmt.Printf(" Location: %s/.plcbundle/\n", dir)
101
116
102
-
if err != nil {
103
-
return fmt.Errorf("error building index: %w", err)
117
+
return nil
118
+
},
104
119
}
105
120
106
-
elapsed := time.Since(start)
107
-
stats = mgr.GetDIDIndexStats()
108
-
109
-
fmt.Printf("\n✓ DID index built in %s\n", elapsed.Round(time.Millisecond))
110
-
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
111
-
fmt.Printf(" Shards: %d\n", stats["shard_count"])
112
-
fmt.Printf(" Location: %s/.plcbundle/\n", dir)
121
+
cmd.Flags().BoolVar(&force, "force", false, "Rebuild even if index exists")
113
122
114
-
return nil
123
+
return cmd
115
124
}
116
125
117
-
func indexStats(args []string) error {
118
-
mgr, dir, err := getManager(nil)
119
-
if err != nil {
120
-
return err
121
-
}
122
-
defer mgr.Close()
126
+
// ============================================================================
127
+
// INDEX REPAIR - Repair/rebuild DID index
128
+
// ============================================================================
123
129
124
-
stats := mgr.GetDIDIndexStats()
130
+
func newIndexRepairCommand() *cobra.Command {
131
+
cmd := &cobra.Command{
132
+
Use: "repair",
133
+
Aliases: []string{"rebuild"},
134
+
Short: "Repair DID index",
135
+
Long: `Repair DID index by rebuilding from bundles
125
136
126
-
if !stats["exists"].(bool) {
127
-
fmt.Printf("DID index does not exist\n")
128
-
fmt.Printf("Run: plcbundle index build\n")
129
-
return nil
130
-
}
137
+
Rebuilds the DID index from scratch and verifies consistency.
138
+
Use this when:
139
+
• DID index is corrupted
140
+
• Index is out of sync with bundles
141
+
• After manual bundle operations
142
+
• Upgrade to new index version`,
131
143
132
-
indexedDIDs := stats["indexed_dids"].(int64)
133
-
mempoolDIDs := stats["mempool_dids"].(int64)
134
-
totalDIDs := stats["total_dids"].(int64)
144
+
Example: ` # Repair DID index
145
+
plcbundle index repair
135
146
136
-
fmt.Printf("\nDID Index Statistics\n")
137
-
fmt.Printf("════════════════════\n\n")
138
-
fmt.Printf(" Location: %s/.plcbundle/\n", dir)
147
+
# Verbose output
148
+
plcbundle index repair -v`,
139
149
140
-
if mempoolDIDs > 0 {
141
-
fmt.Printf(" Indexed DIDs: %s (in bundles)\n", formatNumber(int(indexedDIDs)))
142
-
fmt.Printf(" Mempool DIDs: %s (not yet bundled)\n", formatNumber(int(mempoolDIDs)))
143
-
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs)))
144
-
} else {
145
-
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs)))
146
-
}
150
+
RunE: func(cmd *cobra.Command, args []string) error {
151
+
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
147
152
148
-
fmt.Printf(" Shard count: %d\n", stats["shard_count"])
149
-
fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"])
150
-
fmt.Printf(" Updated: %s\n\n", stats["updated_at"].(time.Time).Format("2006-01-02 15:04:05"))
151
-
fmt.Printf(" Cached shards: %d / %d\n", stats["cached_shards"], stats["cache_limit"])
153
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
154
+
if err != nil {
155
+
return err
156
+
}
157
+
defer mgr.Close()
152
158
153
-
if cachedList, ok := stats["cache_order"].([]int); ok && len(cachedList) > 0 {
154
-
fmt.Printf(" Hot shards: ")
155
-
for i, shard := range cachedList {
156
-
if i > 0 {
157
-
fmt.Printf(", ")
159
+
stats := mgr.GetDIDIndexStats()
160
+
if !stats["exists"].(bool) {
161
+
fmt.Printf("DID index does not exist\n")
162
+
fmt.Printf("Use: plcbundle index build\n")
163
+
return nil
158
164
}
159
-
if i >= 10 {
160
-
fmt.Printf("... (+%d more)", len(cachedList)-10)
161
-
break
165
+
166
+
fmt.Printf("Repairing DID index in: %s\n\n", dir)
167
+
168
+
index := mgr.GetIndex()
169
+
bundleCount := index.Count()
170
+
171
+
if bundleCount == 0 {
172
+
fmt.Printf("No bundles to index\n")
173
+
return nil
162
174
}
163
-
fmt.Printf("%02x", shard)
164
-
}
165
-
fmt.Printf("\n")
166
-
}
167
175
168
-
fmt.Printf("\n")
169
-
return nil
170
-
}
176
+
fmt.Printf("Rebuilding index from %d bundles...\n\n", bundleCount)
171
177
172
-
func indexLookup(args []string) error {
173
-
fs := flag.NewFlagSet("index lookup", flag.ExitOnError)
174
-
verbose := fs.Bool("v", false, "verbose debug output")
175
-
showJSON := fs.Bool("json", false, "output as JSON")
178
+
var progress *ui.ProgressBar
179
+
if !verbose {
180
+
progress = ui.NewProgressBar(bundleCount)
181
+
}
176
182
177
-
if err := fs.Parse(args); err != nil {
178
-
return err
179
-
}
183
+
start := time.Now()
184
+
ctx := context.Background()
180
185
181
-
if fs.NArg() < 1 {
182
-
return fmt.Errorf("usage: plcbundle index lookup <did> [-v] [--json]")
183
-
}
186
+
err = mgr.BuildDIDIndex(ctx, func(current, total int) {
187
+
if progress != nil {
188
+
progress.Set(current)
189
+
} else if current%100 == 0 || current == total {
190
+
fmt.Printf("Progress: %d/%d (%.1f%%) \r",
191
+
current, total, float64(current)/float64(total)*100)
192
+
}
193
+
})
184
194
185
-
did := fs.Arg(0)
195
+
if progress != nil {
196
+
progress.Finish()
197
+
}
186
198
187
-
mgr, _, err := getManager(nil)
188
-
if err != nil {
189
-
return err
190
-
}
191
-
defer mgr.Close()
199
+
if err != nil {
200
+
return fmt.Errorf("repair failed: %w", err)
201
+
}
192
202
193
-
stats := mgr.GetDIDIndexStats()
194
-
if !stats["exists"].(bool) {
195
-
fmt.Fprintf(os.Stderr, "⚠️ DID index does not exist. Run: plcbundle index build\n")
196
-
fmt.Fprintf(os.Stderr, " Falling back to full scan (this will be slow)...\n\n")
197
-
}
203
+
// Verify consistency
204
+
fmt.Printf("\nVerifying consistency...\n")
205
+
if err := mgr.GetDIDIndex().VerifyAndRepairIndex(ctx, mgr); err != nil {
206
+
return fmt.Errorf("verification failed: %w", err)
207
+
}
198
208
199
-
if !*showJSON {
200
-
fmt.Printf("Looking up: %s\n", did)
201
-
if *verbose {
202
-
fmt.Printf("Verbose mode: enabled\n")
203
-
}
204
-
fmt.Printf("\n")
205
-
}
209
+
elapsed := time.Since(start)
210
+
stats = mgr.GetDIDIndexStats()
206
211
207
-
totalStart := time.Now()
208
-
ctx := context.Background()
212
+
fmt.Printf("\n✓ DID index repaired in %s\n", elapsed.Round(time.Millisecond))
213
+
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
214
+
fmt.Printf(" Shards: %d\n", stats["shard_count"])
215
+
fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"])
209
216
210
-
// Lookup operations
211
-
lookupStart := time.Now()
212
-
opsWithLoc, err := mgr.GetDIDOperationsWithLocations(ctx, did, *verbose)
213
-
if err != nil {
214
-
return err
217
+
return nil
218
+
},
215
219
}
216
-
lookupElapsed := time.Since(lookupStart)
217
220
218
-
// Check mempool
219
-
mempoolStart := time.Now()
220
-
mempoolOps, err := mgr.GetDIDOperationsFromMempool(did)
221
-
if err != nil {
222
-
return fmt.Errorf("error checking mempool: %w", err)
223
-
}
224
-
mempoolElapsed := time.Since(mempoolStart)
221
+
return cmd
222
+
}
225
223
226
-
totalElapsed := time.Since(totalStart)
224
+
// ============================================================================
225
+
// INDEX STATS - Show DID index statistics
226
+
// ============================================================================
227
227
228
-
if len(opsWithLoc) == 0 && len(mempoolOps) == 0 {
229
-
if *showJSON {
230
-
fmt.Println("{\"found\": false, \"operations\": []}")
231
-
} else {
232
-
fmt.Printf("DID not found (searched in %s)\n", totalElapsed)
233
-
}
234
-
return nil
235
-
}
228
+
func newIndexStatsCommand() *cobra.Command {
229
+
var showJSON bool
236
230
237
-
if *showJSON {
238
-
return outputLookupJSON(did, opsWithLoc, mempoolOps, totalElapsed, lookupElapsed, mempoolElapsed)
239
-
}
231
+
cmd := &cobra.Command{
232
+
Use: "stats",
233
+
Aliases: []string{"info"},
234
+
Short: "Show DID index statistics",
235
+
Long: `Show DID index statistics
240
236
241
-
return displayLookupResults(did, opsWithLoc, mempoolOps, totalElapsed, lookupElapsed, mempoolElapsed, *verbose, stats)
242
-
}
237
+
Displays DID index information including total DIDs indexed,
238
+
shard distribution, cache statistics, and coverage.`,
243
239
244
-
func indexResolve(args []string) error {
245
-
fs := flag.NewFlagSet("index resolve", flag.ExitOnError)
246
-
verbose := fs.BoolP("verbose", "v", false, "verbose timing breakdown")
240
+
Example: ` # Show statistics
241
+
plcbundle index stats
247
242
248
-
if err := fs.Parse(args); err != nil {
249
-
return err
250
-
}
243
+
# JSON output
244
+
plcbundle index stats --json`,
251
245
252
-
if fs.NArg() < 1 {
253
-
return fmt.Errorf("usage: plcbundle index resolve <did> [-v]")
254
-
}
246
+
RunE: func(cmd *cobra.Command, args []string) error {
247
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
248
+
if err != nil {
249
+
return err
250
+
}
251
+
defer mgr.Close()
255
252
256
-
did := fs.Arg(0)
253
+
stats := mgr.GetDIDIndexStats()
257
254
258
-
mgr, _, err := getManager(nil)
259
-
if err != nil {
260
-
return err
261
-
}
262
-
defer mgr.Close()
255
+
if showJSON {
256
+
data, _ := json.MarshalIndent(stats, "", " ")
257
+
fmt.Println(string(data))
258
+
return nil
259
+
}
263
260
264
-
ctx := context.Background()
265
-
fmt.Fprintf(os.Stderr, "Resolving: %s\n", did)
261
+
if !stats["exists"].(bool) {
262
+
fmt.Printf("DID index does not exist\n")
263
+
fmt.Printf("Run: plcbundle index build\n")
264
+
return nil
265
+
}
266
266
267
-
if *verbose {
268
-
mgr.GetDIDIndex().SetVerbose(true)
269
-
}
267
+
indexedDIDs := stats["indexed_dids"].(int64)
268
+
mempoolDIDs := stats["mempool_dids"].(int64)
269
+
totalDIDs := stats["total_dids"].(int64)
270
270
271
-
// Use unified resolution method with metrics
272
-
result, err := mgr.ResolveDID(ctx, did)
273
-
if err != nil {
274
-
return err
275
-
}
271
+
fmt.Printf("\nDID Index Statistics\n")
272
+
fmt.Printf("════════════════════\n\n")
273
+
fmt.Printf(" Location: %s/.plcbundle/\n", dir)
276
274
277
-
// Display timing metrics
278
-
if result.Source == "mempool" {
279
-
fmt.Fprintf(os.Stderr, "Mempool check: %s (✓ found in mempool)\n", result.MempoolTime)
280
-
fmt.Fprintf(os.Stderr, "Total: %s (resolved from mempool)\n\n", result.TotalTime)
281
-
} else {
282
-
fmt.Fprintf(os.Stderr, "Mempool check: %s (not found)\n", result.MempoolTime)
283
-
fmt.Fprintf(os.Stderr, "Index lookup: %s (shard access)\n", result.IndexTime)
284
-
fmt.Fprintf(os.Stderr, "Operation load: %s (bundle %d, pos %d)\n",
285
-
result.LoadOpTime, result.BundleNumber, result.Position)
286
-
fmt.Fprintf(os.Stderr, "Total: %s\n", result.TotalTime)
275
+
if mempoolDIDs > 0 {
276
+
fmt.Printf(" Indexed DIDs: %s (in bundles)\n", formatNumber(int(indexedDIDs)))
277
+
fmt.Printf(" Mempool DIDs: %s (not yet bundled)\n", formatNumber(int(mempoolDIDs)))
278
+
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs)))
279
+
} else {
280
+
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs)))
281
+
}
287
282
288
-
// Verbose timing breakdown
289
-
if *verbose {
290
-
fmt.Fprintf(os.Stderr, "\nTiming breakdown:\n")
291
-
fmt.Fprintf(os.Stderr, " Mempool: %6s (%5.1f%%)\n",
292
-
result.MempoolTime, float64(result.MempoolTime)/float64(result.TotalTime)*100)
293
-
fmt.Fprintf(os.Stderr, " Index: %6s (%5.1f%%)\n",
294
-
result.IndexTime, float64(result.IndexTime)/float64(result.TotalTime)*100)
295
-
fmt.Fprintf(os.Stderr, " Load op: %6s (%5.1f%%)\n",
296
-
result.LoadOpTime, float64(result.LoadOpTime)/float64(result.TotalTime)*100)
297
-
}
298
-
fmt.Fprintf(os.Stderr, "\n")
299
-
}
283
+
fmt.Printf(" Shard count: %d\n", stats["shard_count"])
284
+
fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"])
285
+
fmt.Printf(" Updated: %s\n\n", stats["updated_at"].(time.Time).Format("2006-01-02 15:04:05"))
300
286
301
-
// Output document to stdout
302
-
data, _ := json.MarshalIndent(result.Document, "", " ")
303
-
fmt.Println(string(data))
287
+
fmt.Printf(" Cached shards: %d / %d\n", stats["cached_shards"], stats["cache_limit"])
304
288
305
-
return nil
306
-
}
289
+
if cachedList, ok := stats["cache_order"].([]int); ok && len(cachedList) > 0 {
290
+
fmt.Printf(" Hot shards: ")
291
+
for i, shard := range cachedList {
292
+
if i > 0 {
293
+
fmt.Printf(", ")
294
+
}
295
+
if i >= 10 {
296
+
fmt.Printf("... (+%d more)", len(cachedList)-10)
297
+
break
298
+
}
299
+
fmt.Printf("%02x", shard)
300
+
}
301
+
fmt.Printf("\n")
302
+
}
307
303
308
-
func outputLookupJSON(did string, opsWithLoc []PLCOperationWithLocation, mempoolOps []plcclient.PLCOperation, totalElapsed, lookupElapsed, mempoolElapsed time.Duration) error {
309
-
output := map[string]interface{}{
310
-
"found": true,
311
-
"did": did,
312
-
"timing": map[string]interface{}{
313
-
"total_ms": totalElapsed.Milliseconds(),
314
-
"lookup_ms": lookupElapsed.Milliseconds(),
315
-
"mempool_ms": mempoolElapsed.Milliseconds(),
304
+
fmt.Printf("\n")
305
+
return nil
316
306
},
317
-
"bundled": make([]map[string]interface{}, 0),
318
-
"mempool": make([]map[string]interface{}, 0),
319
307
}
320
308
321
-
for _, owl := range opsWithLoc {
322
-
output["bundled"] = append(output["bundled"].([]map[string]interface{}), map[string]interface{}{
323
-
"bundle": owl.Bundle,
324
-
"position": owl.Position,
325
-
"cid": owl.Operation.CID,
326
-
"nullified": owl.Operation.IsNullified(),
327
-
"created_at": owl.Operation.CreatedAt.Format(time.RFC3339Nano),
328
-
})
329
-
}
309
+
cmd.Flags().BoolVar(&showJSON, "json", false, "Output as JSON")
330
310
331
-
for _, op := range mempoolOps {
332
-
output["mempool"] = append(output["mempool"].([]map[string]interface{}), map[string]interface{}{
333
-
"cid": op.CID,
334
-
"nullified": op.IsNullified(),
335
-
"created_at": op.CreatedAt.Format(time.RFC3339Nano),
336
-
})
337
-
}
338
-
339
-
data, _ := json.MarshalIndent(output, "", " ")
340
-
fmt.Println(string(data))
341
-
342
-
return nil
311
+
return cmd
343
312
}
344
313
345
-
func displayLookupResults(did string, opsWithLoc []PLCOperationWithLocation, mempoolOps []plcclient.PLCOperation, totalElapsed, lookupElapsed, mempoolElapsed time.Duration, verbose bool, stats map[string]interface{}) error {
346
-
nullifiedCount := 0
347
-
for _, owl := range opsWithLoc {
348
-
if owl.Operation.IsNullified() {
349
-
nullifiedCount++
350
-
}
351
-
}
314
+
// ============================================================================
315
+
// INDEX VERIFY - Verify DID index integrity
316
+
// ============================================================================
352
317
353
-
totalOps := len(opsWithLoc) + len(mempoolOps)
354
-
activeOps := len(opsWithLoc) - nullifiedCount + len(mempoolOps)
318
+
func newIndexVerifyCommand() *cobra.Command {
319
+
var verbose bool
355
320
356
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
357
-
fmt.Printf(" DID Lookup Results\n")
358
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n\n")
359
-
fmt.Printf("DID: %s\n\n", did)
321
+
cmd := &cobra.Command{
322
+
Use: "verify",
323
+
Aliases: []string{"check"},
324
+
Short: "Verify DID index integrity",
325
+
Long: `Verify DID index integrity and consistency
360
326
361
-
fmt.Printf("Summary\n───────\n")
362
-
fmt.Printf(" Total operations: %d\n", totalOps)
363
-
fmt.Printf(" Active operations: %d\n", activeOps)
364
-
if nullifiedCount > 0 {
365
-
fmt.Printf(" Nullified: %d\n", nullifiedCount)
366
-
}
367
-
if len(opsWithLoc) > 0 {
368
-
fmt.Printf(" Bundled: %d\n", len(opsWithLoc))
369
-
}
370
-
if len(mempoolOps) > 0 {
371
-
fmt.Printf(" Mempool: %d\n", len(mempoolOps))
372
-
}
373
-
fmt.Printf("\n")
327
+
Checks the DID index for consistency with bundles:
328
+
• Index version is current
329
+
• All bundles are indexed
330
+
• Shard files are valid
331
+
• No corruption detected
374
332
375
-
fmt.Printf("Performance\n───────────\n")
376
-
fmt.Printf(" Index lookup: %s\n", lookupElapsed)
377
-
fmt.Printf(" Mempool check: %s\n", mempoolElapsed)
378
-
fmt.Printf(" Total time: %s\n\n", totalElapsed)
333
+
Automatically repairs minor issues.`,
379
334
380
-
// Show operations
381
-
if len(opsWithLoc) > 0 {
382
-
fmt.Printf("Bundled Operations (%d total)\n", len(opsWithLoc))
383
-
fmt.Printf("══════════════════════════════════════════════════════════════\n\n")
335
+
Example: ` # Verify DID index
336
+
plcbundle index verify
384
337
385
-
for i, owl := range opsWithLoc {
386
-
op := owl.Operation
387
-
status := "✓ Active"
388
-
if op.IsNullified() {
389
-
status = "✗ Nullified"
338
+
# Verbose output
339
+
plcbundle index verify -v`,
340
+
341
+
RunE: func(cmd *cobra.Command, args []string) error {
342
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
343
+
if err != nil {
344
+
return err
390
345
}
346
+
defer mgr.Close()
391
347
392
-
fmt.Printf("Operation %d [Bundle %06d, Position %04d]\n", i+1, owl.Bundle, owl.Position)
393
-
fmt.Printf(" CID: %s\n", op.CID)
394
-
fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST"))
395
-
fmt.Printf(" Status: %s\n", status)
348
+
stats := mgr.GetDIDIndexStats()
396
349
397
-
if verbose && !op.IsNullified() {
398
-
showOperationDetails(&op)
350
+
if !stats["exists"].(bool) {
351
+
fmt.Printf("DID index does not exist\n")
352
+
fmt.Printf("Run: plcbundle index build\n")
353
+
return nil
399
354
}
400
355
401
-
fmt.Printf("\n")
402
-
}
403
-
}
356
+
fmt.Printf("Verifying DID index in: %s\n\n", dir)
404
357
405
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
406
-
fmt.Printf("✓ Lookup complete in %s\n", totalElapsed)
407
-
if stats["exists"].(bool) {
408
-
fmt.Printf(" Method: DID index (fast)\n")
409
-
} else {
410
-
fmt.Printf(" Method: Full scan (slow)\n")
411
-
}
412
-
fmt.Printf("═══════════════════════════════════════════════════════════════\n")
358
+
ctx := context.Background()
413
359
414
-
return nil
415
-
}
360
+
if verbose {
361
+
fmt.Printf("Index version: %d\n", mgr.GetDIDIndex().GetConfig().Version)
362
+
fmt.Printf("Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
363
+
fmt.Printf("Shards: %d\n", stats["shard_count"])
364
+
fmt.Printf("Last bundle: %06d\n\n", stats["last_bundle"])
365
+
}
416
366
417
-
func showOperationDetails(op *plcclient.PLCOperation) {
418
-
if opData, err := op.GetOperationData(); err == nil && opData != nil {
419
-
if opType, ok := opData["type"].(string); ok {
420
-
fmt.Printf(" Type: %s\n", opType)
421
-
}
367
+
fmt.Printf("Checking consistency with bundles...\n")
422
368
423
-
if handle, ok := opData["handle"].(string); ok {
424
-
fmt.Printf(" Handle: %s\n", handle)
425
-
} else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 {
426
-
if akaStr, ok := aka[0].(string); ok {
427
-
handle := strings.TrimPrefix(akaStr, "at://")
428
-
fmt.Printf(" Handle: %s\n", handle)
369
+
if err := mgr.GetDIDIndex().VerifyAndRepairIndex(ctx, mgr); err != nil {
370
+
fmt.Printf("\n✗ DID index verification failed\n")
371
+
fmt.Printf(" Error: %v\n", err)
372
+
return fmt.Errorf("verification failed: %w", err)
429
373
}
430
-
}
374
+
375
+
stats = mgr.GetDIDIndexStats()
376
+
377
+
fmt.Printf("\n✓ DID index is valid\n")
378
+
fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64))))
379
+
fmt.Printf(" Shards: %d\n", stats["shard_count"])
380
+
fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"])
381
+
382
+
return nil
383
+
},
431
384
}
385
+
386
+
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
387
+
388
+
return cmd
432
389
}
+1
-1
cmd/plcbundle/main.go
+1
-1
cmd/plcbundle/main.go