Unresolved
Records per-item apply failures so the orchestrator can stop retrying known-bad items.
Unresolved means “this item failed to apply”.
CrossWatch records it so it can stop retrying it every run.
When you’ll hit unresolved
An item can’t be represented on the destination.
IDs are missing or mismatched.
The destination is down.
What to do
Fix the match or IDs.
Then clear unresolved files for that provider/feature.
Related:
Persistent cooldown for repeat failures: Blackbox
Unresolved is the orchestrator’s “this item didn’t apply, stop hammering it” mechanism.
It’s conceptually similar to blackbox, but item-specific and meant to be more immediate: a key that fails a write gets parked as unresolved so the next run won’t attempt it again (until cleared).
Code: cw_platform/orchestrator/_unresolved.py
Used by: _pairs_oneway.py, _pairs_twoway.py, _applier.py, _pairs_blocklist.py
When an item becomes unresolved
Unresolved records are written when:
A provider returns explicit unresolved items:
applier sees
result["unresolved"]as a listit calls
record_unresolved(dst, feature, unresolved_items, hint="apply:*:provider_unresolved")
A provider can’t confirm anything (pessimistic fallback):
confirmed == 0and attempted > 0applier records all attempted items as unresolved with
hint="apply:*:fallback_unresolved"
Provider is down:
pipelines skip writes and record planned items as unresolved with hints like:
provider_down:addprovider_down:remove
Pipelines decide a write batch partially failed:
if effective confirmed keys are fewer than attempted and failures are attributable to specific keys, they may record those keys unresolved with
hint="apply:add:failed"
Where unresolved is stored
Unresolved uses scoped files under /config/.cw_state/.
The file that gets written today
record_unresolved(...) writes:
/config/.cw_state/{dst}_{feature}.{SCOPE}.unresolved.pending.json
Value shape:
Keys are canonical keys (string). Values record:
atepochhintshort reason string
How unresolved is supposed to be used
The orchestrator wants unresolved to act as a blocklist for planned adds.
Reader:
load_unresolved_keys(dst, feature, cross_features=True)
Expected filename pattern:
/config/.cw_state/{dst}_{feature}.{SCOPE}.unresolved.json
Return:
set of unresolved keys (strings)
Then the pipeline filters:
planned adds that match unresolved keys are removed from plan
removals are usually not blocked by unresolved in current wiring
Cross-feature unresolved
When cross_features=True, it will load unresolved keys from:
any feature for that destination This is defensive: if an item fails because it can’t be represented on that provider at all, it shouldn’t be retried for another feature either.
Wiring caveat (important)
Today:
writer produces
*.unresolved.pending.jsonreader looks for
*.unresolved.json
If you do not have a “promotion” step that:
merges pending into active unresolved
and writes
*.unresolved.json
…then unresolved blocking can be weaker than intended.
In practice, some runs may still skip unresolved because other parts of the pipeline treat pending as unresolved indirectly, but it’s not guaranteed.
If you want unresolved to work consistently, implement one of:
Change loader to read
*.unresolved.pending.jsontoo, orAdd a post-run promotion step: pending → unresolved.
Matching logic
Unresolved blocking is key-based:
canonical key string equality
It does not (currently) match by ID tokens or title tokens like tombstones/blackbox filters do.
So unresolved works best when:
canonical keys are stable across runs
provider indices include IDs (so canonical_key is stable)
TTL / decay
Unlike tombstones and blackbox, unresolved does not enforce a TTL in the current module.
Meaning:
unresolved entries persist until you clear the file or replace it via promotion logic.
If you want TTL, you can:
prune entries older than N days during promotion.
Manual operations
Inspect unresolved
Inside container:
ls /config/.cw_state/*unresolved*open the scoped file:
{dst}_{feature}.{scope}.unresolved.pending.json
Clear unresolved for a destination+feature
Delete the file(s) for that dst/feature:
/config/.cw_state/{dst}_{feature}.*.unresolved*.json
Clear one key
Edit the JSON and remove that key entry.
Interaction with other guardrails
Unresolved is included in
apply_blocklist(...)for non-watchlist adds.It’s also consulted directly in one-way/two-way pipelines to filter adds.
Unresolved is different from blackbox:
unresolved is “this item failed right now”
blackbox is “this item is a repeated flapper, cool it for a while”
Watchlist typically uses PhantomGuard rather than unresolved/blackbox for add flapping.
Related pages
Where unresolved gets recorded: Applier
Failure cooldown for repeated flappers: Blackbox
Safety mechanisms overview: Guardrails
Last updated