# Unresolved

{% tabs %}
{% tab title="End users" %}
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](/blueprint-architecture/orchestrator/blackbox.md)
  {% endtab %}

{% tab title="Power users" %}
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:

1. A provider returns explicit unresolved items:

* applier sees `result["unresolved"]` as a list
* it calls `record_unresolved(dst, feature, unresolved_items, hint="apply:*:provider_unresolved")`

2. A provider can’t confirm anything (pessimistic fallback):

* `confirmed == 0` and attempted > 0
* applier records all attempted items as unresolved with `hint="apply:*:fallback_unresolved"`

3. Provider is down:

* pipelines skip writes and record planned items as unresolved with hints like:
  * `provider_down:add`
  * `provider_down:remove`

4. 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:

```json
{
  "tmdb:278": { "at": 1738100100, "hint": "provider_down:add" },
  "imdb:tt1234567": { "at": 1738100000, "hint": "apply:add:failed" }
}
```

Keys are canonical keys (string). Values record:

* `at` epoch
* `hint` short 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.json`
* reader 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:

1. Change loader to read `*.unresolved.pending.json` too, or
2. Add 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](/blueprint-architecture/orchestrator/applier.md)
* Failure cooldown for repeated flappers: [Blackbox](/blueprint-architecture/orchestrator/blackbox.md)
* Safety mechanisms overview: [Guardrails](/blueprint-architecture/orchestrator/guardrails.md)
* File locations and naming: [State](/blueprint-architecture/orchestrator/state.md), [Scope](/blueprint-architecture/orchestrator/scope.md)
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.crosswatch.app/blueprint-architecture/orchestrator/unresolved.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
