feat(provide): add `ipfs provide once` and support Interval=0 mode (#11321)
* feat(provide): add ipfs provide once for ad-hoc announcements Adds an experimental subcommand that submits provider records for the given CIDs through the provider system right away, without waiting for the next reprovide cycle. Use -r to walk the DAG and announce every reachable block. Designed against the sweep provider (the default since v0.39): StartProviding queues to the burst-provide workers, which publish records to the DHT efficiently. Works with the legacy provider too, though it queues into the slower serial worker pool. CIDs must already exist in the local blockstore. Re-announcement on the regular schedule is governed by Provide.Strategy and Provide.DHT.Interval; this command does not change either. * refactor(routing): deprecate ipfs routing provide Marks `ipfs routing provide` as deprecated and points users at the new `ipfs provide once`. The command keeps its existing Run, Encoders, and flags so existing scripts continue to work; only the status flag and helptext change. * docs(routing): clarify when ipfs routing reprovide applies Tightens the helptext and the sweep-mode error message so the constraint is obvious: this command only triggers a cycle on the legacy provider, and points users at 'ipfs provide stat --all' for monitoring the default sweep schedule. * docs: tighten provide helptext and update routing-provide references Updates docs/config.md and docs/experimental-features.md to reference 'ipfs provide once' instead of 'ipfs routing provide'. Tightens the helptext for 'ipfs provide clear' and the 'ipfs provide stat' overview: drops headings around short paragraphs, prefers active voice, and notes that the sweep provider is the default. * docs: changelog entry for ipfs provide once * docs: use 'provide system' wording consistently * test(provide): cover --recursive and multi-CID paths for provide once Adds two subtests under runProviderSuite (run for both Legacy and Sweep): - --recursive walks the DAG and announces every chunk of a 2 MiB file added with --pin=false under Provide.Strategy=roots, so the auto- provide path stays out of the way. - multiple CIDs in a single invocation succeed and the text encoder reports 'queued 3 CID(s) for immediate provide'. * feat(provide): stream cids and per-cid output for ipfs provide once Each CID flows through the command independently, so stdin can be piped without buffering and consumers see results as they happen. - Run reads CIDs from argv and then from BodyArgs (stdin scanner) one at a time, calling StartProviding per CID. - With -r, the dag.Walk visit callback emits per visited block; the walk cancels its context on the first announce error to stop fetching. - A typed ProvideOnceEvent (one per queued CID) replaces the prior batch result. JSON output streams {"Queued":"<cid>"} per line. - Text output via PostRun: when stderr is a tty, the running count is redrawn on a single line; otherwise a final count is printed. The text encoder still works for HTTP/RPC consumers (one CID per line). - Adds tests for stdin streaming and --enc=json one-event-per-line. * feat(provide): dedupe across all roots and recursive walks Previously the cid set was scoped per root, so a CID shared by two arguments or by two recursive DAG walks was announced twice. Move the set out to the Run scope so each unique CID is announced exactly once per invocation, regardless of how many times it shows up in argv, stdin, or the DAG walks. For -r, hitting an already-seen CID also stops descent into that subtree, avoiding redundant block fetches when DAGs overlap. * style(provide): rename useTTY to isTTY in PostRun * refactor(provide): align ipfs provide once with kubo cmds-lib idioms - Use the existing argumentIterator helper from cid.go to read argv followed by stdin, replacing the inlined two-loop variant. - Document why PostRun forks on encoder type (TTY redraw needs to bypass the encoder; json/xml must keep streaming through it). - Log an ERROR for unexpected response types instead of dropping them silently, mirroring the defensive pattern in cat.go's PostRun. * docs(routing): document streaming limitations of routing provide Spell out what 'ipfs routing provide' does worse than 'ipfs provide once' so users on the deprecation path know why to switch: input buffering, no per-cid output, no dedup across recursive roots, and the sync dht lookup that defeats sweep batching. * docs(changelog): rewrite ipfs provide once entry around user impact Recasts the highlight to lead with what the user can now do, not what the code does internally. Adds a one-line example showing the streaming stdin path that the previous version did not surface, and replaces "namespace" plumbing language with the actual capabilities (running count, json-per-line, single announcement per shared block under -r). * feat(provide): use boxo BloomTracker for cross-input dedup Swaps the cid.Set used by 'ipfs provide once' for the autoscaling boxo BloomTracker, the same dedup mechanism that powers Provide.Strategy=+unique. Run executes on the daemon, not the cli, so this caps daemon memory under hostile or accidental input: a user piping 100M cids previously would have grown the daemon's set to ~7 gb of resident memory; with the bloom chain it plateaus around 700 mb at the default fp rate, and under 100 mb up to 10m unique cids. The trade-off is a small false-positive rate (~1 in 4.75m, the kubo default) that can cause an occasional cid to be silently skipped. For ad-hoc providing this is acceptable; the regular reprovide cycle will pick up anything matched by Provide.Strategy on the next pass. * docs(changelog): use ipfs refs as the provide once example * style(provide): goimports import order * docs(provide): soften dedup wording, comment re.Emit gate, cover Provide.Enabled=false - Change "exactly once per invocation" to acknowledge the bloom false-positive rate now that the dedup is probabilistic. - Add a comment to the text branch of PostRun warning future readers not to call re.Emit there, since the encoder would race with the TTY counter. - Add a runProviderSuite subtest that exercises Provide.Enabled=false through the new code path (the existing routing-provide test only covers the deprecated alias's Run). * docs(changelog): clarify provide once use case and add second example - Note that provide once is also for fine-tuned control over which CIDs get announced when, alongside the regular reprovide schedule. - Add a second example using ipfs pin ls so users see the pattern for replaying their pinset alongside the dag-walk pattern. * feat(provide): error on ipfs provide once with Provide.DHT.Interval=0 When Provide.DHT.Interval=0, kubo wires NoopProvider via OnlineProviders -> OfflineProviders, so StartProviding silently no-ops and the cid never gets announced. provide once was returning success without any DHT publish: a footgun. Add an explicit precondition check that mirrors the routing reprovide error path. Decoupling the wiring so ad-hoc provide works under Interval=0 is tracked separately. * chore(deps): pin go-libp2p-kad-dht to PR #1246 head Pulls in the WithReprovideInterval(0) burst-only mode from https://github.com/libp2p/go-libp2p-kad-dht/pull/1246 so the kubo side of the Provide.DHT.Interval=0 decoupling can be developed against it. * chore(deps): re-pin go-libp2p-kad-dht to PR #1246 head Updates to the latest commit on the upstream branch (817031b) which also relaxes the dual SweepingProvider's reprovide-interval validator to accept 0, on top of the single-provider relaxation in the previous pseudo-version. * feat(provide): decouple Provide.DHT.Interval=0 from the master kill-switch Provide.Enabled is now the only switch that fully turns off the provide system. Provide.DHT.Interval=0 disables only the periodic reprovide schedule; new CIDs still announce via fast-provide-root and 'ipfs provide once'. - groups.go: drop the Interval=0 factor from isProviderEnabled. The real provider (sweep or legacy) is now wired even when Interval=0. - provider.go: skip the keystore sync goroutine in no-schedule mode. The ticker would panic on a zero interval, and with no schedule the keystore has no reader. - cmdenv/env.go: drop the fast-provide-root short-circuit on Interval=0. Provide.Enabled=false is now the only short-circuit. - commands/provide.go: drop the temporary 'cannot provide: Provide.DHT.Interval is 0' error from 'ipfs provide once'. - test/cli: replace the 'Reprovide.Interval=0 disables announcement of new CID too' test (premise is now false) with one asserting that Interval=0 + Enabled=true keeps announcing. Convert the provide-once + Interval=0 test from error path to success path. Tighten the legacy 'Manual Reprovide trigger' test to focus on the error contract. Requires upstream go-libp2p-kad-dht support for WithReprovideInterval(0) (kept under PR #1246). * feat(config): require explicit Provide.Enabled when Provide.DHT.Interval=0 Provide.DHT.Interval=0 used to disable the entire provide system as a side effect. After the decoupling it disables only the periodic reprovide schedule, while new CIDs still announce via fast-provide-root and 'ipfs provide once'. To prevent silent semantic drift on upgrade, the daemon now refuses to start when Interval is explicitly set to 0 unless Provide.Enabled is also set explicitly: - Provide.Enabled=false fully disables providing (the old behaviour). - Provide.Enabled=true keeps ad-hoc providing while skipping the periodic reprovide schedule. The error message names both options so operators can pick the one that matches their intent without reading the changelog. * docs: explain new Provide.DHT.Interval=0 semantic Updates docs/config.md and the v0.42 changelog: Interval=0 now disables only the periodic reprovide schedule, and the daemon refuses to start without an explicit Provide.Enabled in that configuration. Calls out both upgrade paths (Provide.Enabled=false to fully disable, or =true to keep ad-hoc providing). * chore(deps): re-pin go-libp2p-kad-dht to amended PR #1246 head Picks up the timeOffset/timeBetween zero-guards so SweepingProvider.Stats() no longer panics with reprovideInterval=0. Required for 'ipfs provide stat' to work in no-schedule mode. * test(provide): align test expectations with new no-schedule semantic - core/commands/commands_test.go: register /provide/once in the expected command list. - test/cli/provide_stats_test.go: 'ipfs provide stat' with Provide.DHT.Interval=0 now returns valid stats (with the schedule timing fields zeroed) instead of erroring out. Update the assertion to match. * chore(deps): re-pin go-libp2p-kad-dht to amended PR #1246 head Picks up the scheduleEnabled() consistency cleanup so timeOffset and timeBetween match the rest of the upstream gates. * chore(deps): re-pin go-libp2p-kad-dht to PR #1246 merge on master picks up the three follow-up commits guillaumemichel pushed before merging libp2p/go-libp2p-kad-dht#1246: - refactor: simplify StartProvide() - refactor: minimize change diff - fix: don't remove from keystore on StopProviding() * fix(provide): use ProvideOnce in `ipfs provide once` `ipfs provide once` was calling StartProviding, which in sweep mode persists keys to the keystore and adds them to the periodic reprovide schedule. that contradicts the command's name and help text. switch to ProvideOnce so the command publishes once and leaves the schedule untouched. for the legacy provider StartProviding already wraps ProvideOnce, so legacy behaviour is unchanged. also tighten the help text to state plainly that the schedule is not modified. * fix(provider): keep keystore inert when Provide.DHT.Interval=0 In no-schedule mode the keystore has no reader (no reprovide loop) and no writer (kad-dht's burst path skips Put/Delete). Until now we still opened on-disk leveldb/pebble files for it: wasted disk and noise on upgrade/downgrade. Switch the keystore to an in-memory map in no-schedule mode and make destroyDs a no-op. Also purge any pre-existing keystore directory once at startup so users who toggle from schedule to no-schedule reclaim disk. Replace the literal `reprovideInterval == 0` check at the second call site with the named noScheduleMode flag for consistency.