PHP SDK: Sources: Workflow Inputs and Price Comparison
$ctx->sources() reads inputs pinned by a daily workflow. The workspace, actor permissions, site grants, input freshness and mail expiry are checked again on every read. Cloud workflows currently require technical configuration; there is no visual workflow builder.
input
input(string $key): array
A records input returns records, run_id, collected_at. A mail input returns the checked message, body and attachment metadata. New mail does not replace the pinned message. A newer crawl invalidates the pinned records instead of mixing runs. Deleted or expired mail cannot be read even after the workflow has been queued.
$catalog = $ctx->sources()->input('catalog');
$mail = $ctx->sources()->input('supplier');
$ctx->log('Catalog rows: '.count($catalog['records']));
Manual, trial and replay runs cannot forge input access through parameters. A retry or resume may keep the original pins only with the same actor, source site and scenario version and still-valid permissions.
document
document(string $key, string $id): Document
Read a safe attachment belonging to the pinned mail input. Parsing runs inside the system. File contents are not sent to an external model. The shared limit is eight document reads per run.
$mail = $ctx->sources()->input('supplier');
$document = $ctx->sources()->document('supplier', $mail['attachments'][0]['id']);
$ctx->log($document->format());
comparePrices
comparePrices(array $offers): array
Compare two to ten named sources, each with up to 5000 records containing string sku, numeric price with at most two fractional digits and explicit currency, such as RUB. SKU matching is exact. Negative or invalid prices, repeated SKUs within a source and mixed currencies are rejected.
$rows = $ctx->sources()->comparePrices([
'catalog' => [['sku' => '001', 'price' => 1250, 'currency' => 'RUB']],
'supplier' => [['sku' => '001', 'price' => 1200, 'currency' => 'RUB']],
]);
foreach ($rows as $row) {
$ctx->emit($row, $row['sku']);
}
Output: sku, currency, prices, complete, best_source, best_price. Missing pairs have complete=false and no winner or minimum. Equal prices retain all winners. Delivery charges, stock, tax and different package sizes must be normalized before comparison.
compare
The internal PriceComparison class performs this same calculation. Scenarios should call sources()->comparePrices(). Comparison uses ordinary local code and no AI tokens.
Trial runs
Before publication, run a normal trial with the workflow_preview parameter. Inputs contain only your examples: catalog: {type: "records", records: [...]} and supplier: {type: "mail", attachments: [{id: "sample", name: "prices.csv", content: "sku;price;currency\n001;1200;RUB"}]}. Use base64 instead of content for binary files. A complete fixture is available at resources/workflows/daily-prices.preview.json. Trials cannot access real mail or stored records. A successful trial with emitted results provides normal publication proof. AI generation is billed normally; validated cache hits consume no AI tokens.