···85 }
8687 // Initialize operations handler
88- ops, err := storage.NewOperations(config.Logger)
89 if err != nil {
90 return nil, fmt.Errorf("failed to initialize operations: %w", err)
91 }
···471 // Get hostname
472 hostname, _ := os.Hostname()
473474- // ✅ Create BundleInfo
475 bundleInfo := &storage.BundleInfo{
476 BundleNumber: bundle.BundleNumber,
477 Origin: origin,
···483484 m.logger.Printf("DEBUG: Calling operations.SaveBundle with bundle=%d", bundleInfo.BundleNumber)
485486- // ✅ Save to disk with 3 parameters
487 uncompressedHash, compressedHash, uncompressedSize, compressedSize, err := m.operations.SaveBundle(path, bundle.Operations, bundleInfo)
488 if err != nil {
489 m.logger.Printf("DEBUG: SaveBundle FAILED: %v", err)
···1560 if err := plcclient.ValidateDIDFormat(input); err != nil {
1561 return "", 0, err
1562 }
1563- return input, 0, nil // ✅ No resolution needed
1564 }
15651566 // Support did:web too
···85 }
8687 // Initialize operations handler
88+ ops, err := storage.NewOperations(config.Logger, config.Verbose)
89 if err != nil {
90 return nil, fmt.Errorf("failed to initialize operations: %w", err)
91 }
···471 // Get hostname
472 hostname, _ := os.Hostname()
473474+ // Create BundleInfo
475 bundleInfo := &storage.BundleInfo{
476 BundleNumber: bundle.BundleNumber,
477 Origin: origin,
···483484 m.logger.Printf("DEBUG: Calling operations.SaveBundle with bundle=%d", bundleInfo.BundleNumber)
485486+ // Save to disk with 3 parameters
487 uncompressedHash, compressedHash, uncompressedSize, compressedSize, err := m.operations.SaveBundle(path, bundle.Operations, bundleInfo)
488 if err != nil {
489 m.logger.Printf("DEBUG: SaveBundle FAILED: %v", err)
···1560 if err := plcclient.ValidateDIDFormat(input); err != nil {
1561 return "", 0, err
1562 }
1563+ return input, 0, nil // No resolution needed
1564 }
15651566 // Support did:web too
+1-1
bundle/metadata.go
···131 }
132 defer file.Close()
133134- // ✅ Use abstracted reader from storage package
135 reader, err := storage.NewStreamingReader(file)
136 if err != nil {
137 return 0, 0, time.Time{}, time.Time{}, fmt.Errorf("failed to create reader: %w", err)
···131 }
132 defer file.Close()
133134+ // Use abstracted reader from storage package
135 reader, err := storage.NewStreamingReader(file)
136 if err != nil {
137 return 0, 0, time.Time{}, time.Time{}, fmt.Errorf("failed to create reader: %w", err)
+1-1
bundle/scanner.go
···205 for num := range jobs {
206 path := filepath.Join(m.config.BundleDir, fmt.Sprintf("%06d.jsonl.zst", num))
207208- // ✅ NEW: Stream metadata WITHOUT loading all operations
209 meta, err := m.CalculateMetadataStreaming(num, path)
210 if err != nil {
211 results <- bundleResult{index: num, err: err}
···205 for num := range jobs {
206 path := filepath.Join(m.config.BundleDir, fmt.Sprintf("%06d.jsonl.zst", num))
207208+ // Stream metadata WITHOUT loading all operations
209 meta, err := m.CalculateMetadataStreaming(num, path)
210 if err != nil {
211 results <- bundleResult{index: num, err: err}
+7-5
cmd/plcbundle/commands/common.go
···123 config := bundle.DefaultConfig(absDir)
124 config.AutoInit = opts.AutoInit
125126- // Set verbose from command if available
127 if opts.Cmd != nil {
128- if verbose, err := opts.Cmd.Root().PersistentFlags().GetBool("verbose"); err == nil {
129- config.Verbose = verbose
130- }
00131 }
132133 // Create PLC client if URL provided
···146 // Set handle resolver URL from flag or option
147 handleResolverURL := opts.HandleResolverURL
148 if handleResolverURL == "" && opts.Cmd != nil {
149- handleResolverURL, _ = opts.Cmd.Root().PersistentFlags().GetString("handle-resolver") // ✅ Fixed flag name
150 }
151 // Only override default if explicitly provided
152 if handleResolverURL != "" {
···123 config := bundle.DefaultConfig(absDir)
124 config.AutoInit = opts.AutoInit
125126+ // Check BOTH global AND local verbose flags
127 if opts.Cmd != nil {
128+ globalVerbose, _ := opts.Cmd.Root().PersistentFlags().GetBool("verbose")
129+ localVerbose, _ := opts.Cmd.Flags().GetBool("verbose")
130+131+ // Use OR logic: verbose if EITHER flag is set
132+ config.Verbose = globalVerbose || localVerbose
133 }
134135 // Create PLC client if URL provided
···148 // Set handle resolver URL from flag or option
149 handleResolverURL := opts.HandleResolverURL
150 if handleResolverURL == "" && opts.Cmd != nil {
151+ handleResolverURL, _ = opts.Cmd.Root().PersistentFlags().GetString("handle-resolver")
152 }
153 // Only override default if explicitly provided
154 if handleResolverURL != "" {
+10-8
cmd/plcbundle/commands/inspect.go
···255 result.FileSize = info.Size()
256257 // Check for frame index
258- ops := &storage.Operations{}
0259 if _, err := ops.ExtractBundleMetadata(bundlePath); err == nil {
260 result.HasFrameIndex = true // Has embedded index
261 } else {
···274 fmt.Fprintf(os.Stderr, "Reading embedded metadata...\n")
275 metaStart := time.Now()
276277- ops := &storage.Operations{}
0278 meta, err := ops.ExtractBundleMetadata(bundlePath)
279 if err != nil {
280 if opts.verbose {
···329 fmt.Fprintf(os.Stderr, "Verifying cryptographic hashes...\n")
330 verifyStart := time.Now()
331332- // ✅ Pass cmd parameter
333 result.ContentHashValid, result.CompressedHashValid, result.MetadataValid =
334 verifyCrypto(cmd, bundlePath, result.Metadata, bundleNum, opts.verbose)
335···352// ============================================================================
353354func analyzeBundle(path string, opts inspectOptions) (*bundleAnalysis, error) {
355- ops := &storage.Operations{}
356 operations, err := ops.LoadBundle(path)
357 if err != nil {
358 return nil, err
···852}
853854func verifyCrypto(cmd *cobra.Command, path string, meta *storage.BundleMetadata, bundleNum int, verbose bool) (contentValid, compressedValid, metadataValid bool) {
855- ops := &storage.Operations{}
856857 // Calculate actual hashes from file
858 compHash, compSize, contentHash, contentSize, err := ops.CalculateFileHashes(path)
···867 compressedValid = true
868 metadataValid = true
869870- // ✅ Verify against embedded metadata if available
871 if meta != nil {
872 // Check content hash (this is in the metadata)
873 if meta.ContentHash != "" && meta.ContentHash != contentHash {
···884 metadataValid = true
885 }
886887- // ✅ Note: We don't check compressed hash/size because they're not in metadata
888 // (The file IS the compressed data, so it's redundant)
889890 if verbose {
···895 }
896 }
897898- // ✅ Also verify against repository index if bundle number is known
899 if bundleNum > 0 {
900 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
901 if err == nil {
···255 result.FileSize = info.Size()
256257 // Check for frame index
258+ ops, _ := storage.NewOperations(nil, opts.verbose)
259+260 if _, err := ops.ExtractBundleMetadata(bundlePath); err == nil {
261 result.HasFrameIndex = true // Has embedded index
262 } else {
···275 fmt.Fprintf(os.Stderr, "Reading embedded metadata...\n")
276 metaStart := time.Now()
277278+ ops, _ := storage.NewOperations(nil, opts.verbose)
279+280 meta, err := ops.ExtractBundleMetadata(bundlePath)
281 if err != nil {
282 if opts.verbose {
···331 fmt.Fprintf(os.Stderr, "Verifying cryptographic hashes...\n")
332 verifyStart := time.Now()
333334+ // Pass cmd parameter
335 result.ContentHashValid, result.CompressedHashValid, result.MetadataValid =
336 verifyCrypto(cmd, bundlePath, result.Metadata, bundleNum, opts.verbose)
337···354// ============================================================================
355356func analyzeBundle(path string, opts inspectOptions) (*bundleAnalysis, error) {
357+ ops, _ := storage.NewOperations(nil, opts.verbose)
358 operations, err := ops.LoadBundle(path)
359 if err != nil {
360 return nil, err
···854}
855856func verifyCrypto(cmd *cobra.Command, path string, meta *storage.BundleMetadata, bundleNum int, verbose bool) (contentValid, compressedValid, metadataValid bool) {
857+ ops, _ := storage.NewOperations(nil, verbose)
858859 // Calculate actual hashes from file
860 compHash, compSize, contentHash, contentSize, err := ops.CalculateFileHashes(path)
···869 compressedValid = true
870 metadataValid = true
871872+ // Verify against embedded metadata if available
873 if meta != nil {
874 // Check content hash (this is in the metadata)
875 if meta.ContentHash != "" && meta.ContentHash != contentHash {
···886 metadataValid = true
887 }
888889+ // Note: We don't check compressed hash/size because they're not in metadata
890 // (The file IS the compressed data, so it's redundant)
891892 if verbose {
···897 }
898 }
899900+ // Also verify against repository index if bundle number is known
901 if bundleNum > 0 {
902 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
903 if err == nil {
+3-10
cmd/plcbundle/commands/migrate.go
···148 hashChanges := make([]int, 0, len(needsMigration))
149150 for i, bundleNum := range needsMigration {
151- // ✅ Pass version to migrateBundle
152 if err := migrateBundle(dir, bundleNum, index, version, opts.verbose); err != nil {
153 failed++
154 if firstError == nil {
···172 progress.Finish()
173 elapsed := time.Since(start)
174175- // ✅ Update index with new compressed hashes
176 if len(hashChanges) > 0 {
177 fmt.Printf("\nUpdating bundle index...\n")
178 updateStart := time.Now()
···224 fmt.Printf(" Index updated: %d entries\n", len(hashChanges))
225 fmt.Printf(" Speed: %.1f bundles/sec\n\n", float64(success)/elapsed.Seconds())
226227- fmt.Printf("✨ New bundle format features:\n")
228- fmt.Printf(" • Embedded metadata (JSON in skippable frame)\n")
229- fmt.Printf(" • Frame offsets for instant random access\n")
230- fmt.Printf(" • Multi-frame compression (100 ops/frame)\n")
231- fmt.Printf(" • Self-contained (no .idx files)\n")
232- fmt.Printf(" • Provenance tracking (version, origin, creator)\n")
233- fmt.Printf(" • Compatible with standard zstd tools\n")
234 } else {
235 fmt.Printf("⚠️ Migration completed with errors\n")
236 fmt.Printf(" Success: %d bundles\n", success)
···274 // 4. Get hostname (optional)
275 hostname, _ := os.Hostname()
276277- // 5. ✅ Create BundleInfo for new format
278 bundleInfo := &storage.BundleInfo{
279 BundleNumber: meta.BundleNumber,
280 Origin: index.Origin, // From index
···148 hashChanges := make([]int, 0, len(needsMigration))
149150 for i, bundleNum := range needsMigration {
151+ // Pass version to migrateBundle
152 if err := migrateBundle(dir, bundleNum, index, version, opts.verbose); err != nil {
153 failed++
154 if firstError == nil {
···172 progress.Finish()
173 elapsed := time.Since(start)
174175+ // Update index with new compressed hashes
176 if len(hashChanges) > 0 {
177 fmt.Printf("\nUpdating bundle index...\n")
178 updateStart := time.Now()
···224 fmt.Printf(" Index updated: %d entries\n", len(hashChanges))
225 fmt.Printf(" Speed: %.1f bundles/sec\n\n", float64(success)/elapsed.Seconds())
2260000000227 } else {
228 fmt.Printf("⚠️ Migration completed with errors\n")
229 fmt.Printf(" Success: %d bundles\n", success)
···267 // 4. Get hostname (optional)
268 hostname, _ := os.Hostname()
269270+ // 5. Create BundleInfo for new format
271 bundleInfo := &storage.BundleInfo{
272 BundleNumber: meta.BundleNumber,
273 Origin: index.Origin, // From index