Cloud Backends¶
rfgen keeps cloud SDKs out of its core import path. Cloud storage,
credentials, and managed batch annotation use optional extras; distributed
generation uses PySpark through the shipped executor registry.
For environment-specific setup, see Reference / Compute Environment.
Execution model¶
LocalExecutor runs synchronously on one host. PySpark is the only shipped distributed execution model. Dask and Ray are deferred and have no executor classes, extras, or registry selectors.
The shipped rfgen.executors entry points are:
Selector |
Class |
Execution environment |
|---|---|---|
|
Synchronous local process |
|
|
User-supplied or ambient Spark cluster |
|
|
Google Cloud Spark environment |
|
|
Amazon EMR Spark environment |
|
|
Azure Synapse Spark environment |
|
|
Databricks Spark environment |
The four managed classes inherit the shipped
SparkExecutor behavior. They do
not import or construct vendor SDK clients. The application host supplies a
configured SparkSession, a zero-argument session factory, or an ambient
SparkSession.builder configuration.
PySpark is not installed by a cloud extra. Install and configure a PySpark version compatible with the target Spark service in the driver and worker environment.
Managed-Spark control protocol¶
Layer 19 ships the provider-neutral control-plane contract in
rfgen.managed_spark; it does not ship a provider SDK adapter or submit a
job to a cloud service. A deployment supplies a ManagedSparkAdapter for one
of gcp_dataproc_serverless, aws_emr_serverless, azure_synapse, or
databricks. The adapter owns provider authentication and transport; the core
package never imports a cloud SDK at this boundary.
ManagedSparkJobSpecV1 is a strict, immutable canonical request. It binds a
run UUID, wheel URI and digest, entry point, arguments, labels, identity and
network maps, an absolute UTC deadline, and spec_sha256. Secrets must remain
in IAM or the adapter configuration, never in the spec maps or logs. Submit
requires a non-empty idempotency key. Reusing it with the same request returns
the original handle; reusing it with another request is an
IdempotencyConflictError.
Every handle, status, result, log page, and reconciliation report carries a
canonical SHA-256 self-hash. Status uses the normalized lifecycle
SUBMITTED, QUEUED, RUNNING, CANCELLING, SUCCEEDED, FAILED,
CANCELLED, or UNKNOWN. An adapter must reconcile UNKNOWN before waiting
or cancelling can proceed; an unresolved outcome is a protocol error rather
than permission to create a second job. Provider request timeouts are fixed at
30 seconds in the request contract. Cancellation must be given a UTC deadline
no more than five minutes ahead; waits are bounded to seven days.
Log pagination is handle-bound and opaque, with a limit from 1 through 1,000.
Reconciliation returns immutable rows describing the state observed, action,
state after action, and evidence digest. The bundled
InMemoryManagedSparkAdapter is deterministic reference/test support only;
it is not a cloud emulator or production backend.
Executor configuration¶
The executor config surface has two fields:
Field |
Type |
Default |
Behavior |
|---|---|---|---|
|
|
|
Open entry-point selector. Blank names are rejected. |
|
positive |
|
Requested PySpark partition count. Ignored by synchronous local execution. |
Free-form executor parameters are rejected. Service-specific Spark settings,
authentication, networking, and resource sizing belong in the supplied
SparkSession configuration.
executor:
name: gcp_serverless_spark
parallelism: 32
Equivalent selectors for the other shipped paths are local, spark, emr,
synapse, and databricks.
Runtime behavior¶
Every executor accepts a one-shot iterable of shard specifications. Before
dispatch, it calls the injected store’s has_shard(shard_id) completion probe
and skips completed shards. Store construction and record writes remain in the
storage and shard-worker boundaries.
plans pending shards in chunks of
10_000;creates one flat
SparkContext.unionwhen multiple chunks are present;applies the supplied shard function as a Spark transformation;
streams terminal results through
toLocalIterator();assigns a Spark job group so
cancel(handle)can callcancelJobGroup(...); andretains only the configured result-detail bound in public handles and results.
Importing rfgen.executors does not import PySpark or a cloud SDK. PySpark is
imported only if a Spark session was not injected and the executor must use
SparkSession.builder.getOrCreate().
Cloud extras¶
Cloud extras provide credential, storage, inference, and batch-annotation SDKs. They are not alternate distributed executor implementations.
Extra |
SDK dependencies |
Shipped capabilities |
|---|---|---|
|
Google Cloud Storage, Google Auth, Google Gen AI |
GCP credentials, GCS storage, Gemini inference, Vertex batch annotation |
|
boto3 |
AWS credentials, S3 storage, SageMaker batch annotation |
|
Azure Identity, Azure Blob Storage |
Azure credentials, Blob storage, Azure batch annotation |
|
OpenAI SDK |
OpenAI batch annotation |
|
Anthropic SDK |
Anthropic batch annotation |
Combine extras when a workflow needs more than one provider:
pip install 'rfgen[gcp,anthropic-batch]'
Provider clients and transports lazy-import their official SDKs. Missing SDKs
raise BackendUnavailableError when the provider path is constructed, not when
the core package is imported.
Offline admission estimates¶
Admission estimation consumes immutable price and quota snapshots. It does not
call provider pricing or quota APIs during deterministic replay. The caller
supplies a configured Layer 14 SigstoreDSSEAdapter as the required
estimate(..., adapter=...) verifier. The estimator calls only its verify
method, with the PRICING_V1 policy, pricing-bundle.json subject, and the
strict PricingBundleV1 predicate model. Tests may supply a structurally
compatible verifier fake; production must supply the Layer 14 adapter.
Known object-store schemes bind to their provider in billable plans: gs to
GCP, s3 to AWS, and az, abfs, abfss, or azure to Azure. Local storage
is explicitly nonbillable and never enters provider pricing.
Storage retention is billed in GB-months, with one month fixed at 730 hours.
Plans may retain a month interval directly. When they also retain source hours,
each month endpoint must exactly equal its hours endpoint divided by 730; the
admission mapper uses the derived interval and records the hours source.
Quota enforcement¶
Quota enforcement consumes the already-complete offline estimate and does not re-query provider pricing or quota APIs. It rejects incomplete estimates, missing or extra unit ceilings, and hard-limit breaches; it admits a request inside every soft limit without an approval; and it requires a signed approval only in the range between soft and hard limits. A supplied approval is verified through the injected Layer 14 DSSE adapter, bound to the exact budget, estimate, resource spec, signer, and policy, then single-used through a Redis replay claim. Any verification or Redis uncertainty rejects the decision rather than admitting it.
The optional operational guards are separate from that decision: limits
owns Redis fixed-window rate-limit TTLs, PostgreSQL advisory locks bound
concurrency, and a caller-provided run-control adapter must report a PAUSING
state observed no later than 30 seconds after a hard breach. These contracts do
not provision a Redis, PostgreSQL, or run-control deployment. See the
Quota Enforcement API for exact models,
signatures, errors, and failure precedence.
Transactional publication¶
The storage transaction boundary publishes a new dataset revision in two
steps. It first writes every staged object to a unique immutable key, then it
conditionally advances the dataset LATEST pointer. Readers use only the
revision named by that pointer and verify each referenced object checksum.
A failed publication leaves the prior pointer and its visible objects intact.
The provider adapters use official SDK conditional-write APIs. The cloud SDK clients are supplied to the adapters, so importing the core package does not construct provider clients.
Provider |
Immutable-object condition |
|
Minimum dependency |
|---|---|---|---|
Amazon S3 |
|
|
|
Google Cloud Storage |
|
|
|
Azure Blob Storage |
|
|
|
S3 multipart uploads do not carry these write preconditions. A multipart
transfer therefore completes to a unique immutable key before the pointer
compare-and-swap operation. Azure lease operations use BlobLeaseClient in
addition to the ETag condition on the pointer.
fsspec is a read and list transport only. It never performs immutable writes,
pointer compare-and-swap, or lease operations because it does not offer a
portable conditional-write contract.
Cloud recovery inventory and quarantine are not defined by this page. A deployment that needs either capability supplies provider-specific operational procedures; it must not treat a generic listing result as authorization to delete or quarantine cloud objects.
Layer 43: disaster-recovery release evidence¶
Treat a provider recovery exercise as release evidence only when its retained, immutable bundle names the provider, account or project, region, backend and runbook revisions, target dataset revision, and UTC start and end times. Preserve the conditional-write observations, recovery inventory and reconciliation report hashes, restored-manifest verification report, measured RPO and RTO, and the exercise outcome with its approving operator. Provider logs alone are insufficient: redact secrets, retain the relevant exported records, and mark partial, failed, or unverified exercises as non-passing.
Register a third-party executor¶
Third-party packages register an executor class through the same entry-point group:
[project.entry-points."rfgen.executors"]
custom_spark = "rfgen_custom:CustomSparkExecutor"
The class implements the BaseDistributedExecutor surface:
from collections.abc import Iterable
from rfgen.executors import BaseDistributedExecutor, ExecutorRunHandle
from rfgen.orchestration import RunResult, ShardResult, ShardSpec
class CustomSparkExecutor(BaseDistributedExecutor):
def submit(
self,
shard_fn,
shards: Iterable[ShardSpec],
) -> ExecutorRunHandle:
...
def wait(self, handle: ExecutorRunHandle) -> RunResult:
...
def cancel(self, handle: ExecutorRunHandle) -> None:
...
The registry selector remains an open string, so installing the package is
enough to make executor.name: custom_spark discoverable. A core framework
change is not required.
Admission estimation¶
rfgen.admission_estimation constructs an offline, canonical cost-admission
artifact for one already-resolved run. It does not call cloud pricing or quota
APIs, select a cloud vendor, or create resources. The caller supplies the
signed pricing bundle and the short-lived provider quota snapshot that it has
already obtained.
All V1 models in this surface are immutable, strict Pydantic models. The
models that declare a *_sha256 self-hash validate it as a SHA-256 digest over
RFC 8785 canonical JSON; embedded plan, demand, provenance, and line-item
models without such a field do not self-hash. Callers must not mutate any
model through model_construct or substitute equivalent-looking unordered
data. See the Admission Estimation API for
the complete field-level contract.
Models by purpose¶
Purpose |
Public models and enums |
Contract |
|---|---|---|
Price evidence |
|
A bundle has uniquely sorted provider/SKU/region/unit prices, optional EUR FX rows, UTC creation and expiry times, and a canonical bundle hash. A price row has contiguous tiers beginning at zero. |
Admission plan |
|
Every quantity is a finite |
Provenance and resource spec |
|
The spec binds the resolved run, plan, exact plugin wheels, resolved configuration hashes, source Git revision, and UTC resolution time. |
Mapped resources |
|
Mapping produces sorted billable provider-qualified demands and retains local storage as a nonbillable provenance row. |
Quota evidence |
|
A quota snapshot contains uniquely sorted exact provider-native readings and expires no more than one hour after creation. Unit conversion is forbidden. |
Estimate result |
|
Results retain source digests, interval endpoints, typed missing inputs, exact per-unit totals, currency totals, and a canonical estimate hash. |
Construct the resource spec¶
The single configuration boundary is:
build_admission_resource_spec(
resolved_cfg: DictConfig,
planning: AdmissionPlanningConfigV1,
*,
plugin_lock: PluginLockV1,
source_revision: str,
resolved_at: datetime,
) -> AdmissionResourceSpecV1
resolved_cfg must be an OmegaConf DictConfig, not a pre-resolved dict.
The constructor resolves every interpolation with missing-value errors enabled,
then strictly validates the generation, executor, storage, and optional
annotation contracts. It rejects mismatched record counts, executor selectors,
storage selectors, storage schemes, and annotation selectors with
AdmissionInputError(code="CONFIG_MISMATCH").
The supplied plugin lock must name uniquely sorted (group, name) selectors,
their distribution/version/wheel SHA-256 identities, and its own canonical
hash. A malformed or unresolved configuration, invalid lock, invalid source
revision, or invalid provenance construction raises
AdmissionInputError(code="PROVENANCE").
Storage provider and retention rules¶
For billable storage, gs binds to GCP, s3 to AWS, and az, abfs,
abfss, or azure to AZURE. An unknown scheme is billable only when the
matching rfgen.stores plugin lock entry declares the plan’s billing provider.
This prevents a path from being costed under a different provider.
When retention_hours is supplied, each retention_months endpoint must be
exactly retention_hours / Decimal(730). Mapping uses that same 730-hour rule
for GB_MONTH demand. LOCAL_NONBILLABLE storage requires a file or
local scheme, no provider or SKU, zero egress, and a
LocalNonbillableReason; it remains in the result provenance but is not
priced.
Map resource demand¶
map_admission_resource_spec(
spec: AdmissionResourceSpecV1,
) -> tuple[tuple[ResourceDemandV1, ...], tuple[NonbillableResourceV1, ...]]
Mapping is deterministic. It emits VCPU and optional GPU hours from executor duration and count intervals, storage and egress rows for billable storage, and request and token rows for an annotation plan. Input and output token intervals are summed endpoint by endpoint. The function returns rows sorted by their canonical identities and never replaces a missing SKU, region, quota, or unit with a nearby alternative.
Verify evidence and estimate¶
estimate(
spec: AdmissionResourceSpecV1,
snapshot: PricingBundleV1,
*,
quota: QuotaSnapshotV1,
pricing_bundle: Bundle,
pricing_policy: AttestationPolicyV1,
adapter: PricingVerificationAdapter,
as_of: datetime,
deadline_monotonic_s: float,
) -> CostEstimateV1
PricingVerificationAdapter is a narrow injected verifier. Its verify(...)
method receives the Sigstore Bundle, the AttestationPolicyV1, and
PricingBundleV1 as the predicate model. The application or deployment owns
construction of this adapter, including trust roots, identity policy, network
transport, and Sigstore configuration. rfgen does not create a default
adapter or silently accept an unverified bundle.
Before mapping or pricing, estimate(...) requires a policy with predicate
PRICING_V1, subject name pricing-bundle.json, and a subject digest equal to
snapshot.bundle_sha256. It then requires the adapter’s verified statement to
bind that exact predicate type, subject, digest, and canonical pricing payload.
Any verification failure raises PricingSignatureError.
Price identity is exact on provider, SKU, region, and unit. A price is stale
after 24 hours, FX is stale after seven days, and quota is stale after one
hour. Pricing tiers are integrated endpoint by endpoint; foreign-currency
endpoints are converted independently. Missing or stale price, FX, and quota
inputs produce a typed MissingCostInputV1 and a non-COMPLETE status rather
than an invented estimate. The monotonic deadline is checked throughout the
operation and expiry raises AdmissionDeadlineError.
Error boundary¶
Error |
Meaning |
|---|---|
|
The resolved configuration or provenance cannot be proved. Its code is |
|
The DSSE policy, bundle, verified statement, or bound pricing payload is not exact. |
|
A price, FX, or pricing-bundle canonical integrity invariant failed. |
|
Price or FX evidence has duplicate or unsorted identity rows. |
|
Quota evidence is malformed, conflicts, is timestamp-invalid, or would require unsafe unit conversion. |
|
The supplied finite monotonic deadline elapsed before an estimate could be emitted. |
See Also¶
Reference / Executors: exact executor methods, handles, and results.
Reference / Compute Environment: driver, worker, and cloud setup.
Reference / Plugin Metadata: metadata for third-party packages.
Reference / Build and CI: optional-dependency validation.
Reference / Attestation: signed-statement policy and verification surface.