Skip to content

Only Alberto's own decorator can answer whether a checkpoint store can enumerate #155

Description

@VDBBjorn

ICheckpointInventory is an optional capability: a checkpoint store that does not implement it opts out of the orphaned-checkpoint check, and OrphanCheckpointHostedService receives null.

A decorator cannot answer that question statically. CachingCheckpointStore always implements the interface, but can only really enumerate when the store it wraps does. A plain cast would therefore always succeed and hand back a decorator that cannot answer — a false all-clear under OrphanCheckpointPolicy.Strict, where "no orphans found" is indistinguishable from "we could not look".

Alberto worked around this with an internal AsInventory gateway plus a concrete-type test at the single resolution site:

sp.GetKeyedService<ICheckpointStore>(serviceKey) is CachingCheckpointStore caching
    ? caching.AsInventory
    : sp.GetKeyedService<ICheckpointStore>(serviceKey) as ICheckpointInventory,

That makes the seam shallow: to use ICheckpointInventory correctly you must know which concrete decorator types exist, and only Alberto's own internal type can answer the capability question. A third-party decorator over a non-enumerable inner store falls into the as branch, is treated as a live inventory, returns [], and silently clears a check it never performed.

The interface includes its error modes. "I might not be able to answer" is one, and it was missing.

Fix

Add bool CanEnumerate => true; as a default interface member. CachingCheckpointStore overrides it to _inner is ICheckpointInventory; the resolution site collapses to a single call that names no concrete type:

sp.GetKeyedService<ICheckpointStore>(serviceKey) is ICheckpointInventory { CanEnumerate: true } inv ? inv : null,

AsInventory is deleted — the deletion test passes, it was internal with exactly one call site. The NotSupportedException inside ListProcessorIdsAsync stays as a backstop for a caller that ignores CanEnumerate, since returning an empty list there would recreate the same false all-clear.

Public API

Purely additive. One new entry in PublicAPI.Unshipped.txt; PublicAPI.Shipped.txt untouched and no *REMOVED* entries. The DIM default means InMemoryCheckpointStore and PostgresCheckpointStore need no change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions