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