# Release Channel Resolution Safety and Withdrawn-Artifact Watch (`kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch`) Actor

Resolve each declared range or channel tag the way a package manager does, and report when the resolved version is withdrawn, holds no live file, or moved to another version since the last run. One row for each target, with the resolved version, the rule

- **URL**: https://apify.com/kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 run\_starteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Release Channel Resolution Safety and Withdrawn-Artifact Watch

Know what your consumers install today.

This Actor resolves each declared range or channel tag the way a package
manager does. It then reports when the resolved version is withdrawn, holds no
live file, or points to a different version than in the last run.

A yanked version, a deleted file set or a moved channel tag makes the install
different from the intent. Nothing changes in the consumer repository, so a
scheduled run is the only signal. Without this watch, you learn about the
change from a bug report.

### What one run does

1. It reads each target, for example `npm:express@^4.0.0` or `npm:react@next`.
2. It reads the metadata document of each package once, from the public
   registry of the ecosystem.
3. It selects the version that the declaration resolves to:
   - a **channel tag** names the version that the registry states. The
     registry, and not the version order, decides where a tag points;
   - a **range** selects the highest version that satisfies it, inside the
     candidate set that the `includePrerelease` setting allows.
4. It reads the state of the resolved version: is it withdrawn (yanked or
   deprecated), and how many live files does it hold.
5. It gives the highest safe version: the highest version that satisfies the
   declaration, is not withdrawn, and holds at least one live file.
6. It compares the resolved version with the snapshot of the last run, which
   it holds in a named key-value store, and it reports a retargeted tag.
7. It writes one dataset row for each target, and one run-summary record.

### Supported ecosystems

| Ecosystem | Write it as | Registry | Withdrawn means | Live files |
| --- | --- | --- | --- | --- |
| npm | `npm:express@^4.0.0` | registry.npmjs.org | the version carries a `deprecated` message | the version holds a tarball. An unpublished version holds none |
| PyPI | `pypi:requests@>=2.31,<3` | pypi.org | every file of the release is yanked | the files of the release that are not yanked |
| crates.io | `cargo:serde@^1.0` | crates.io | the version is yanked | one file for a version that is not yanked |

`crates` and `crates.io` are other names for `cargo`. A target with no range
or tag, for example `npm:express`, takes the `latest` tag.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `targets` | array of text | four example targets | 1 to 300 targets, each one `ecosystem:name@range_or_tag`. One entry may hold several lines |
| `includePrerelease` | boolean | `false` | Repeat here the resolver setting of your consumers. When it is off, a prerelease is no candidate for a range |
| `failOn` | array of text | all three conditions | The conditions that make a target unsafe: `withdrawn`, `no_live_files`, `tag_retargeted` |
| `stateStoreName` | text | `release-channel-resolution-state` | The named key-value store that holds the last resolved version of each target |
| `requestTimeoutSeconds` | integer | `30` | The timeout of each registry request |
| `concurrency` | integer | `4` | The largest number of packages read at the same time |

A run with an empty input uses these defaults and succeeds.

### Output

One row for each target, plus one run-summary record.

| Field | What it holds |
| --- | --- |
| `target`, `ecosystem`, `package`, `range_or_tag`, `include_prerelease` | The declaration, as the run read it |
| `resolution_status` | `resolved`, `package_not_found`, `registry_error`, `tag_not_found`, `unsupported_range` or `no_satisfying_version` |
| `resolved_version` | The version that the declaration selects today |
| `resolution_rule` | The rule that selected it |
| `withdrawn`, `withdrawn_reason` | The withdrawn state of the resolved version, and the reason that the registry states |
| `live_file_count` | The number of live files of the resolved version |
| `highest_safe_version`, `safer_version_available` | The highest version that satisfies the declaration, is not withdrawn and holds live files |
| `tag_retargeted`, `previous_version`, `previous_seen_at`, `retarget_direction` | The comparison with the last run |
| `resolution_changed`, `first_run` | A range that selects a new version is a change, and not a retarget. The first run of a target writes the snapshot and reports no retarget |
| `verdict`, `failure_reason`, `conditions` | The verdict of the target |
| `error`, `note` | What stopped the resolution, and what the buyer must know about it |

#### Verdicts

- `safe`: the target resolved, and the resolved version holds no condition.
- `unsafe`: the resolved version holds a condition that your `failOn` list
  names.
- `degraded`: the target did not resolve, or it holds a condition that your
  `failOn` list does not name. You see the finding, and the gate stays yours.

A verdict is a result, and not a fault of the Actor. A run with an unsafe
target succeeds, and it carries the verdict in the status message. Use the
dataset, and not the run status, for your gate.

### The state store, and why the Actor needs it

A retarget is visible only against an earlier observation. The Actor keeps one
record for each target in the **named** key-value store that
`stateStoreName` names. A named store stays after the run; the store of the
run does not.

- The first run of a target writes the snapshot and reports no retarget.
- A later run compares the resolved version with the stored one.
- Use one store name for each watched target list, so that two schedules do
  not overwrite each other.

A channel tag moves whenever the publisher releases, so `tag_retargeted`
reports every move of a tag. That is what a channel watch shows. Take
`tag_retargeted` out of `failOn` if you want only the withdrawn state and the
file state to make an unsafe verdict.

### Pay-per-event pricing

| Event | Unit | What counts as one event |
| --- | --- | --- |
| `run_started` | one run | One event for each run. It is charged before the first request, so a run that stops early still pays for the work it started |
| `target_resolved` | one resolution target evaluated | One event for each target that the run read from a registry. A target that the input got wrong, and a target that the charge limit stopped, are not charged. A target whose registry answer failed is charged, because the request was made |
| `channel_snapshot_stored` | one target snapshot | One event for each snapshot written back to the named store. Only a target that resolved writes a snapshot |

The run never reads more targets than its maximum total charge allows. A
target above that limit gets a `target_not_evaluated` row, and it keeps its
stored snapshot for the next run.

### Version rules, and what this Actor does not do

- npm and crates.io use the semantic version rules. PyPI uses the PEP 440
  rules. Each rule set has its own tests.
- The range forms that the Actor reads are `^`, `~`, `>=`, `>`, `<=`, `<`,
  `=`, an exact version, an x-range (`1.x`), a hyphen range (`1.2.3 - 2.3.4`),
  `*` and an or-list (`^1.0.0 || ^2.0.0`) for npm and Cargo; and `==`, `!=`,
  `>=`, `>`, `<=`, `<`, `~=`, `===` and a wildcard (`==4.*`) for PyPI.
- A comparator admits a prerelease by the numbers alone. npm hides a
  prerelease from a range that does not name one; this Actor lets
  `includePrerelease` decide which versions are candidates, because that
  setting repeats what your resolver does.
- A declaration that the Actor cannot read, for example `workspace:*` or a Git
  URL, gets the `unsupported_range` status and the degraded verdict. It is not
  a failed run.
- The Actor makes HTTP requests to three fixed registry hosts only. It uses no
  browser, no proxy, no key and no paid API.

### Limits

- At most 300 targets in one run.
- At most 12 MB for one metadata document.
- At most 8 packages read at the same time.

### Schedule it

Run it once a day, or before each deployment. The publisher changes a channel
tag and a yank state without any change in your repository, so only a
scheduled run finds it.

# Actor input Schema

## `targets` (type: `array`):

1 to 300 resolution targets, each one in the form ecosystem:name@range\_or\_tag, for example npm:express@^4.0.0, npm:react@next, pypi:requests@>=2.31,<3 or cargo:serde@^1.0. Supported ecosystems: npm, pypi and cargo (crates.io). A target with no range or tag takes the latest tag. One entry may hold several lines. An entry this Actor cannot read gets its own row with the invalid\_target verdict; it does not stop the run.

## `includePrerelease` (type: `boolean`):

Repeat here the resolver setting that your consumers use. When it is off, a prerelease, for example 4.2.0a1 or 5.0.0-beta.1, is no candidate for a range. A channel tag always names the version that the registry states, because the registry, and not the version order, decides where a tag points.

## `failOn` (type: `array`):

A resolved version that holds one of these conditions gets the unsafe verdict. A condition that is found but is not named here gives the degraded verdict, so you see the finding either way. An empty list reports every condition without an unsafe verdict. Note that a channel tag moves whenever the publisher releases, so tag\_retargeted reports every move of a tag, which is what a channel watch shows.

## `stateStoreName` (type: `string`):

The name of the named key-value store that holds the last resolved version of each target. Use one name for each watched target list, so that two schedules do not overwrite each other. Letters, digits and hyphens only, at most 63 characters. The first run of a target writes the snapshot and reports no retarget.

## `requestTimeoutSeconds` (type: `integer`):

Timeout for each registry request. One package needs one request, so the whole run takes at most this value times the number of packages divided by the concurrency.

## `concurrency` (type: `integer`):

The largest number of packages read at the same time. A public registry refuses a burst, so a small number is safer.

## Actor input object example

```json
{
  "targets": [
    "npm:express@^4.0.0",
    "npm:request@latest",
    "pypi:requests@>=2.31,<3",
    "cargo:serde@^1.0"
  ],
  "includePrerelease": false,
  "failOn": [
    "withdrawn",
    "no_live_files",
    "tag_retargeted"
  ],
  "stateStoreName": "release-channel-resolution-state",
  "requestTimeoutSeconds": 30,
  "concurrency": 4
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row for each resolution target, with the resolved version, the rule that selected it, the withdrawn state, the live file count, the highest safe version and the retarget fields, plus one run-summary record with the counts per verdict.

## `unsafeTargets` (type: `string`):

The rows whose verdict is unsafe: the resolved version holds a condition that the failOn list names.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "targets": [
        "npm:express@^4.0.0",
        "npm:request@latest",
        "pypi:requests@>=2.31,<3",
        "cargo:serde@^1.0"
    ],
    "includePrerelease": false,
    "failOn": [
        "withdrawn",
        "no_live_files",
        "tag_retargeted"
    ],
    "stateStoreName": "release-channel-resolution-state",
    "requestTimeoutSeconds": 30,
    "concurrency": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "targets": [
        "npm:express@^4.0.0",
        "npm:request@latest",
        "pypi:requests@>=2.31,<3",
        "cargo:serde@^1.0",
    ],
    "includePrerelease": False,
    "failOn": [
        "withdrawn",
        "no_live_files",
        "tag_retargeted",
    ],
    "stateStoreName": "release-channel-resolution-state",
    "requestTimeoutSeconds": 30,
    "concurrency": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "targets": [
    "npm:express@^4.0.0",
    "npm:request@latest",
    "pypi:requests@>=2.31,<3",
    "cargo:serde@^1.0"
  ],
  "includePrerelease": false,
  "failOn": [
    "withdrawn",
    "no_live_files",
    "tag_retargeted"
  ],
  "stateStoreName": "release-channel-resolution-state",
  "requestTimeoutSeconds": 30,
  "concurrency": 4
}' |
apify call kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/release-channel-resolution-safety-and-withdrawn-artifact-watch"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/2SvVDp19qzQB7huYi/builds/cmuP4Kkx9kxsPdpxF/openapi.json
