Skip to main content

Crate workload_spec

Crate workload_spec 

Source
Expand description

WorkloadSpec — typed wire format for yubaba workloads.

This crate is the schema source of truth. It has zero dependencies on yubaba; yubaba depends on it, not the other way around. Agents and desktop code that construct specs can link this crate without pulling in yubaba’s containerd client.

Three validation layers live in validate: shape (sync, no I/O), semantic (reads yubaba state), and environment (deploy-time). The schema types live at the top level.

@yah:ticket(R222-T3, “Workload schema doesn’t match per-kind on-disk shapes (mesofact-static)”) @yah:assignee(agent:claude) @yah:at(2026-05-18T16:47:03Z) @yah:status(review) @yah:parent(R222) @yah:handoff(“Picked option (a): tagged-enum Workload envelope with per-kind variants. Added Workload { MesofactStatic(MesofactStaticWorkload), Container(WorkloadSpec) } + BuildConfig in workload-spec. WorkloadSpec stays the containerd RPC wire type (now also the kind="container" variant payload). xtask emit-schemas now renders workload.toml.schema.json as a oneOf over kind; schema drift test green. TS export updated. Arch doc ‘workloads — colocated, not registered’ rewritten to describe the envelope + both example kinds; B4 outlook updated to point at Workload.”) @yah:verify(“cargo check -p cloud && cargo test -p cloud && cargo check -p yah && cargo check -p agent-tools && cargo check -p yah –tests && cargo check -p agent-tools –tests”) @yah:verify(“cargo test -p xtask # schema drift test must stay green”) @yah:verify(“cargo run -p workload-spec –bin export-ts # idempotent regen”)

@yah:ticket(R256-F7, “Model mesofact container as two roles: transient build/publish job vs long-lived SSR/SPA runtime”) @yah:assignee(agent:claude) @yah:at(2026-05-25T20:08:29Z) @yah:status(review) @yah:parent(R256) @yah:next(“role A — build/publish job: transient task that runs the build and PUTs to the object store, then exits/GC’d; needed whenever there is a build step (SSR or not)”) @yah:next(“role B — SSR/SPA runtime: long-lived container, only present when the app has realtime/dynamic pages (this is what the ‘only if SSR/SPA’ gate applies to)”) @yah:next(“decide the fidelity knob: does the build run in-container (matches CI, max fidelity, costs image+cold-start) or on host with yubaba orchestrating only the serving edge?”) @yah:assumes(“in cloud these are separate: CI/build job produces artifacts, R2+CDN serve them, and a distinct worker serves any SSR — so one merged ‘mesofact container’ is the trap”) @yah:handoff(“BuildMode enum added to workload-spec with HostSide (default) and InContainer { image } variants. MesofactStaticWorkload gains build_mode: BuildMode (skip_serializing_if default) and ssr_runtime: Option. Encodes the two-role model: build step is always transient; SSR companion is optional long-lived. Fidelity knob decision: HostSide = host watcher (dev+sim), InContainer = CI-fidelity (cloud/ha). All three codegen targets updated: export-ts.rs, packages/yah/workload-spec/index.ts, .yah/schema/workload.toml.schema.json. Schema drift tests pass.”) @yah:verify(“cargo check -p workload-spec –locked”) @yah:verify(“cargo test -p xtask –locked # schema_drift tests pass”) @yah:verify(“cargo test -p cloud –locked –lib # 165 passed”)

@yah:ticket(R256-F9, “Almanac as a dependency manifest: orchestrator verifies I/O targets live before run; output invalidates mesofact sources cleanly”) @yah:assignee(agent:claude) @yah:at(2026-05-25T21:28:15Z) @yah:status(review) @yah:parent(R256) @yah:next(“almanac is a manifest declaring inputs + outputs + cadence + command — NOT a bash cron; the declared I/O is the contract”) @yah:next(“before a run the orchestrator verifies declared inputs exist AND output targets (e.g. the mesofact app + its source/object store) are reachable; if not → the run fails or waits/times out rather than producing orphaned output”) @yah:next(“almanac output invalidates downstream mesofact sources cleanly + deliberately (a declared dependency edge, not a blunt rebuild-everything) — this is the reason it’s a named manifest, not a shell cron”) @yah:next(“decide the not-ready policy knob: fail-fast vs wait-with-timeout vs requeue”) @yah:next(“generalizes the OpenRouter refresher (spawn_almanac_refresher), which is the degenerate no-dependency case (output = JSON cache, no app target)”) @yah:assumes(“precondition enforcement lives in the shared scheduler layer (embedded by camp for dev/sim, yubaba for cloud/ha) — it needs the workload registry + xlb-net discovery to answer ‘is the target up?’, which a bash cron lacks”) @arch:see(.yah/docs/architecture/A024-vocabulary.md) @yah:depends_on(R256-F6) @yah:handoff(“AlmanacTarget (Http/Tcp probe), NotReadyPolicy (WaitWithTimeout default=5s/FailFast/Requeue), Cadence (Once/Every/Cron), and AlmanacManifest types added to workload-spec. Workload enum gains Almanac(AlmanacManifest) variant (kind=‘almanac’). NotReadyPolicy::WaitWithTimeout(5s) is the default — matches sim-tier spinup budget. AlmanacManifest.invalidates: Vec declares downstream cache-bust targets. export-ts.rs updated; index.ts and workload.toml.schema.json regenerated; drift tests pass. The degenerate case (no inputs, no outputs, Cron, no invalidates) is exactly the OpenRouter refresher pattern. The orchestrator precondition enforcement (xlb-net probing) is left for R276/yubaba integration.”) @yah:verify(“cargo check -p workload-spec –locked”) @yah:verify(“cargo test -p xtask –locked # schema drift tests pass”) @yah:verify(“cargo test -p cloud –locked –lib # 165 passed”)

@yah:relay(R335, “Almanac mirror-binding — scope a feed to the mirror it affects”) @yah:at(2026-05-27T02:19:09Z) @yah:status(open) @arch:see(.yah/docs/working/W058-almanac-mirror-binding.md) @yah:depends_on(R256-F9)

@yah:ticket(R335-S1, “Decide cross-env pollution mechanism: extend R256-F9 manifest vs add per-mirror capability”) @yah:assignee(agent:claude) @yah:at(2026-05-27T02:19:33Z) @yah:kind(spike) @yah:status(review) @yah:phase(P1) @yah:parent(R335) @yah:gotcha(“Build ON R256-F9’s AlmanacManifest (workload-spec/src/lib.rs) — do NOT invent a parallel manifest. R256-F9 is in review.”) @yah:depends_on(R256-F9) @yah:handoff(“Decided. Recorded in almanac-mirror-binding.md §11. KEY FINDING: two almanac paths exist; the live R330 feed uses almanac::FeedConfig (on_change=MesofactRebuild{service,route} — a service id, NOT a MeshIdent), so it never touches AlmanacManifest.invalidates. Verdict on the S1 title: NEITHER extend the manifest nor (yet) add capability is the accident fix — dev->cloud is ALREADY blocked by construction (feed path = process locality + per-mirror reconciler + MinIO/R2 backend split; manifest path = no camp-embedded MeshState, mesh resolution is yubaba-raft-only). Residual holes: /revalidate receiver is UNAUTHENTICATED, and same-tier (two clouds on one R2) has no per-mirror key prefix.”) @yah:next(“FILED: R335-F3 (P1, no yubaba dep) mirror-aware /revalidate receiver — reject feeds not bound to this mirror; satisfies R335-T2; lands with R330-F4.”) @yah:next(“FILED: R335-F4 (P2) per-mirror artifact key prefix in derive_minio_key/publish_to_r2 — closes same-tier collision.”) @yah:next(“FILED: R335-F5 (P3, BLOCKED on yubaba control plane) per-mirror capability gate on /revalidate via yubaba/xlb-net node identity.”)

@yah:ticket(R278-F4, “RolloutPolicy schema in workload-spec (TOML types)”) @yah:assignee(agent:claude) @yah:at(2026-06-01T02:31:25Z) @yah:status(review) @yah:parent(R278) @yah:next(“Add src/rollout.rs with RolloutPolicy, RolloutStrategy, RolloutGate, RolloutStep, RolloutOnFailure”) @yah:next(“Export pub mod rollout from lib.rs”) @yah:next(“Add TS export via ts-rs in export-ts.rs”) @arch:see(.yah/docs/working/W140-yah-yubaba-ci-cd.md) @yah:handoff(“RolloutPolicy, RolloutStrategy, RolloutGate, RolloutStep, RolloutOnFailure added to workload-spec/src/rollout.rs. Exported from lib.rs. toml dev-dep added for round-trip test. Tests: rollout::tests::round_trip_toml + on_failure_default both green.”)

@yah:ticket(R429-T1, “Workload::StaticAsset variant + schema in workload-spec (catalog + aliases)”) @yah:assignee(agent:claude) @yah:at(2026-06-03T23:24:20Z) @yah:status(review) @yah:phase(P1) @yah:parent(R429) @yah:next(“Add Workload::StaticAsset(StaticAssetWorkload) variant alongside the existing MesofactStatic + Container envelopes. Mirror the tagged-enum shape R222-T3 established.”) @yah:next(“StaticAssetWorkload fields: kind=‘static-asset’ tag, assets: Vec, aliases: BTreeMap<String, String>. AssetEntry { filename: String, source: PathBuf, blake3: BlakeHash }.”) @yah:next(“BlakeHash newtype validates 64-hex-char shape (reuse from existing places if available, else introduce here).”) @yah:next(“Closed-catalog invariant: aliases values MUST be filenames present in the assets list. Reject at load with a clear error pointing at the offending alias key + bad filename.”) @yah:next(“Mirror schema extension: optional [asset_aliases] BTreeMap<String, String> on MirrorConfig. Semantic validator (when both workload + mirror are loaded together) rejects mirror aliases whose target filename isn’t in the catalog.”) @yah:next(“Regenerate the workload.toml.schema.json via xtask emit-schemas (R222-B4). Confirm the drift test stays green.”) @yah:next(“TS mirror: extend packages/yah/workload-spec/index.ts with the StaticAsset variant + AssetEntry. Confirm bun typecheck stays green.”) @yah:verify(“cargo check -p workload-spec –locked”) @yah:verify(“cargo test -p workload-spec”) @yah:verify(“cargo run -p workload-spec –bin export-ts”) @yah:verify(“cargo test -p xtask”) @arch:see(.yah/docs/working/W160-atomic-release-waves.md)

@yah:ticket(R429-F2, “static-asset reconciler: BLAKE3 verify + S3 PUT against mirror’s object_store + drift”) @yah:assignee(agent:claude) @yah:at(2026-06-03T23:24:38Z) @yah:status(review) @yah:phase(P2) @yah:parent(R429) @yah:next(“New reconciler that handles kind=‘static-asset’ in the same service-sync loop that already runs mesofact-static + container. Same wave-gate semantics, same drift shape.”) @yah:next(“For each [[asset]] row: hash source file (BLAKE3) and compare to manifest entry. Mismatch → surface as drift, halt push for that asset until rebuild.”) @yah:next(“Resolve mirror’s object_store provider → R2 bucket + credentials. HEAD cas/filename; if absent or different content-length → PUT. Idempotent on re-run.”) @yah:next(“Drift detection: list bucket contents under the component’s prefix, compare against catalog filenames. Files in bucket ∖ catalog → report as drift (do NOT delete; that’s the prune verb’s job).”) @yah:next(“ServicesView’s existing matrix consumes the new drift shape automatically. Confirm SyncGlyph/DriftList render correctly for a static-asset row without UI changes.”) @yah:next(“MockR2 in tests: HashMap<key, bytes> implementing the S3 surface the reconciler hits. Cover: push first-time, push idempotent, drift catches catalog-vs-bucket mismatch, BLAKE3 mismatch halts push.”) @yah:next(“Real-R2 integration test gated behind YAH_TEST_R2_BUCKET env var — one round-trip against a scratch bucket; skipped otherwise.”) @yah:verify(“cargo check –workspace –locked”) @yah:verify(“cargo test -p # crate TBD by impl agent”) @yah:verify(“cargo test -p workload-spec”) @yah:gotcha(“Auto-delete is OFF — reconciler reports drift on bucket∖catalog files but never DELETEs. That’s the prune verb (R429-T2). Easy bug to introduce when ‘cleaning up drift’; don’t.”) @yah:gotcha(“S3 multipart upload threshold matters — distil-large-v3 is ~270MB which is over the 5MB single-PUT limit on R2’s strictest mode. Use aws-sdk-s3’s multipart helper for assets >100MB.”) @yah:gotcha(“Long-running progress MUST surface in QED/task-pane per the long-running-yah-surface rule. Don’t silently spin in a tokio task; model as a Task with progress events.”) @arch:see(.yah/docs/working/W160-atomic-release-waves.md) @yah:depends_on(R429-T1)

@yah:ticket(R429-T3, “yah service prune verb: candidate enumeration + operator-confirm delete”) @yah:assignee(agent:claude) @yah:at(2026-06-03T23:24:52Z) @yah:status(review) @yah:phase(P3) @yah:parent(R429) @yah:next(“yah service prune enumerates files present in the bucket but not referenced by any current mirror’s resolved alias graph. Lists candidates + sizes + last-modified, requires explicit operator confirm before DELETE.”) @yah:next(“Resolution graph: for each mirror, walk [asset_aliases] → catalog [aliases] → catalog [[asset]] rows. Union across all mirrors = live set. Bucket ∖ live set = prune candidates.”) @yah:next(“MCP tool mcp__yah__service_prune routes through approval gate (write verb). Read counterpart mcp__yah__service_prune_status auto-passes — returns the candidate list without acting.”) @yah:next(“Camp: Tauri command + a ‘Prune candidates’ panel in the existing DeployPanel for each service, showing the candidate table with per-row checkboxes + confirm.”) @yah:next(“Analytics-driven candidate filter (old AND unaccessed-for-N-days) is OUT OF SCOPE for this ticket — needs access logs we don’t aggregate yet. The candidate set today is purely catalog-derived.”) @yah:next(“User-asset TTL is OUT OF SCOPE — different surface, access-pattern-based, separate relay when it lands.”) @yah:verify(“cargo test -p ”) @yah:verify(“yah service prune yah-desktop –dry-run lists candidates”) @arch:see(.yah/docs/working/W160-atomic-release-waves.md) @yah:depends_on(R429-F2) @yah:handoff(“CLI + library + MCP all landed; UI deferred to R429-F4 (filed). Library lives in crates/yah/cloud/src/reconciler/static_asset_prune.rs and exposes compute_live_set (pure resolution graph), compute_prune_candidates (live + LIST + diff), execute_prune (DELETE), and load_service_and_mirror (path helper). CLI verb is yah cloud service prune <name> --env <env> [--dry-run] [--yes] [--format=table|json] at app/yah/cli/src/cloud.rs (ServiceCommands::Prune + handle_service_prune). MCP tools cloud.service_prune_status (read, auto-pass, –dry-run –format=json) and cloud.service_prune (write, –yes –format=json) dispatch through build_command(). New S3 helper sign_s3_get_with_query in local-driver covers ListObjectsV2 (the existing s3_sign helpers don’t handle canonical query strings); ListObjectsV2 response is parsed with a tiny hand-rolled split_tags helper to avoid a quick-xml workspace dep. Tests: 12 prune-module unit tests (live-set union, kind filtering, list response parse for single/empty/truncated/no-token, candidate filtering including catalog manifest sidecar exclusion) + 1 s3_sign helper test + 2 MCP build_command tests. cargo check –workspace clean. cargo test -p cloud –lib: 279 pass (1 pre-existing failure cloud_init::tests::embedded_template_matches_workspace_canonical unrelated, per R419-F4 docstring). cargo test -p yah –lib: 299 pass.”) @yah:next(“R429-F4 carries the Tauri + DeployPanel UI work — depends_on R429-T3, status=open.”) @yah:verify(“cargo check –workspace –locked”) @yah:verify(“cargo test -p cloud –lib reconciler::static_asset_prune # 12 pass”) @yah:verify(“cargo test -p yah –lib mcp::tools::tests::cloud_service_prune # 2 pass”) @yah:verify(“yah cloud service prune –help # renders usage with –env/–dry-run/–yes/–format”)

@arch:see(.yah/docs/working/W164-derived-static-assets.md)

@yah:ticket(R438-T2, “AssetEntry XOR: source vs derive + shape_static_asset rules”) @yah:assignee(agent:claude) @yah:at(2026-06-04T21:06:51Z) @yah:status(review) @yah:phase(P1) @yah:parent(R438) @yah:next(“AssetEntry.source: PathBuf → Option”) @yah:next(“Add AssetEntry.derive: Option with fetch + optional transform”) @yah:next(“Extend shape_static_asset to enforce exactly-one(source, derive) + license closed-set”) @yah:verify(“Both-set and neither-set fail shape validation with ShapeError::Field”) @yah:verify(“Legacy TOMLs with only source still parse + serialize identically”) @arch:see(.yah/docs/working/W164-derived-static-assets.md) @yah:handoff(“AssetEntry now carries Option source + Option derive (both skip_serializing_if). New types AssetDerive {fetch: FetchSource, transform: Option} and TransformSpec {recipe, params} added to workload-spec/src/lib.rs. validate.rs grew FieldPath::Asset(usize, &’static str) and shape_static_asset enforces XOR: both-set or neither-set fail with ShapeError::Field { path: Asset(i, "source") }. 4 new tests cover derive-mode round-trip, legacy source-only TOML round-trip without leaking a derive field, both-set rejection, neither-set rejection, and both-modes-accepted positive case. Cloud reconciler (static_asset.rs:360) now bails on derive-mode with a pointer to R438-T5 until the materialize step lands. 3 test fixtures updated with source: Some(…) + derive: None. export-ts regenerated (TransformSpec + AssetDerive emitted); xtask emit-schemas regenerated workload.toml.schema.json; schema_drift test green. workload-spec: 24/24, cloud static_asset: 25/25, xtask: 2/2.”)

@yah:ticket(R438-T3, “ImageRef digest-pin enforcement at deserialize”) @yah:assignee(agent:claude) @yah:at(2026-06-04T21:06:55Z) @yah:status(review) @yah:phase(P1) @yah:parent(R438) @arch:see(.yah/docs/working/W164-derived-static-assets.md) @arch:see(.yah/docs/working/W165-mesofact-build-mode-lowering.md) @yah:handoff(“ImageRef now accepts either a string form (digest-pinned, W164/W165 path) or the legacy struct form (backwards-compat for WorkloadSpec configs). String form requires @sha256: suffix: bare-tag, non-sha256, and non-hex digests all reject at serde-deserialize. Single parser compose_import::parse_pinned_image_ref is the rule’s one home; T4 (recipes) and T6 (BuildMode::InContainer) will both deserialize images through this string path. Custom Deserialize uses untagged enum (Pinned(String) | Struct(Fields)); Serialize/TS/JsonSchema derives stay on the struct so wire output and TS exports are unchanged. 6 new tests cover: bare-tag reject, pinned accept (docker.io + ghcr.io), non-sha256 algorithm reject, empty/non-hex digest reject, struct-form still works with digest=None, struct-form TOML round-trip. workload-spec: 30/30 lib + 18/18 semantic + 6/6 shape_fixtures. xtask schema_drift green after emit-schemas regen. cargo check –workspace clean.”) @yah:next(“Tighten ImageRef workspace-wide: digest: Option → digest: String (required). tag stays as the human-readable identifier; digest is the source of truth. Rationale: every image we execute should be reproducible-by-construction; the on-disk shape should make unpinned-image bugs impossible.”) @yah:next(“Every existing ImageRef construction site updates to pass a digest. Call sites known today (~10): yubaba integration tests (fake digests via a test helper), yubaba/runtime/{containerd,fake}, yubaba/deploy/{mesh_resolve,env_validate}, local-runtime, cloud/config, workload-spec round_trip tests, restart_policy tests, compose_import::parse_image_ref. The break is bounded — single PR, no surprise call sites outside the workspace.”) @yah:next(“compose_import::parse_image_ref returns Result<ImageRef, ParseImageRefError> with an UnpinnedImage variant. Docker-compose strings without @sha256: become an explicit parse error — callers must pre-resolve tags to digests (most compose imports already happen at yubaba submission time where a pinning pass can run).”) @yah:next(“Add task::local::test_support::test_digest() (or similar) for test fixtures — a fixed valid-format sha256 string so tests don’t have to mint their own.”) @yah:next(“Recipe TOML loader (T4) and W165 BuildMode::InContainer (T6) inherit the new requirement for free — they consume ImageRef and digest is now structurally required.”) @yah:verify(“cargo check –workspace –locked passes after the tightening + call-site migration (yubaba, runtime, local-runtime, mesh_resolve, env_validate, cloud/config, compose_import)”) @yah:verify(“ImageRef without digest no longer constructs — parse-time + type-level enforcement. cargo test -p workload-spec round_trip + restart_policy pass with updated fixtures.”) @yah:verify(“compose_import::parse_image_ref("node:20") → Err(UnpinnedImage); parse_image_ref("node:20@sha256:…") → Ok.”) @yah:verify(“cargo test -p yubaba –tests + -p local-driver passes with new test_digest() helper in place of bare tags.”) @yah:gotcha(“Earlier framing assumed T3 needed a PinnedImageRef newtype to avoid breaking yubaba. Reversed after design discussion 2026-06-04: breaking yubaba in service of reproducibility-by-construction is the right architectural move. Digest is required workspace-wide; tag stays as a human-readable identifier. ~10 call sites migrate in one PR.”) @yah:gotcha(“MesofactStaticWorkload.build_mode → InContainer { image } today is declared but never executed (build always runs on host — see W165). Once T3 tightens ImageRef, the wired-up build_mode lowering in T6 inherits digest-pinning automatically, closing W165 OQ#1’s escape hatch.”) @yah:assumes(“No production yubaba deployment ships ImageRefs we don’t already digest-pin. Spot-check Hetzner/cloud yah-castle workload specs before merging the tightening; if any prod path uses tag-only, it gets pinned in the same PR.”) @yah:handoff(“Pushed back from review 2026-06-04 — user reaffirmed the workspace-wide tightening direction. What landed (untagged Deserialize accepting string-form OR struct-form-with-digest:Option) ships digest enforcement at the W164/W165 wire surfaces but leaves the struct-form escape hatch (digest: None still constructs). User: ‘breaking yubaba in order to improve it architecturally is fine’. Final shape needs both: (a) keep the string-form parser as a recipe-author convenience (image = "ghcr.io/x@sha256:…"), AND (b) tighten the struct form’s digest: Option → String. Then both paths land at the same digest-required field and unpinned-image bugs become impossible by construction. ~10 call sites still need migration (yubaba/runtime/{containerd,fake}, yubaba/deploy/{mesh_resolve,env_validate}, local-driver/local_runtime, cloud/config, workload-spec tests/round_trip + tests/restart_policy + yubaba integration_* + yubaba/tests/integration_public_ingress + integration_operator_bridge + integration_mesh + integration_single_node). Add task::local::test_support::test_digest() returning a fixed valid-format sha256 string. Pick this up by claiming R438-T3.”) @yah:handoff(“Workspace-wide ImageRef tightening landed. (a) ImageRef.digest: Option → String at workload-spec/src/lib.rs:923; Deserialize struct arm now requires the field; untagged string-form parser at compose_import::parse_pinned_image_ref untouched. (b) ImageRef::docker_ref() now always emits tag@digest pair (informational tag alongside content-addressed digest). (c) validate.rs ImageTag check tightened: tag must be non-empty (digest presence is type-enforced now). (d) compose_import::parse_image_ref returns Result<ImageRef, String> — alias for parse_pinned_image_ref. import_compose gained ImportError::UnpinnedImage { service, image, reason } variant; only external caller (yah workload import in app/yah/cli/src/workload.rs) propagates the error type. (e) New workload_spec::testing module (doc-hidden) exposes TEST_DIGEST const + test_digest() fn — all-zeros 64-hex sentinel. (f) task::default_image::catalog_image falls back to testing::test_digest() when the per-image env var is unset, preserving the infallible API but making unset-digest visible at runtime via docker pull failure. default_buildkit_image follows the same pattern. (g) Migrated ~22 struct-form construction sites: workload-spec tests (round_trip/semantic/restart_policy + all 15 fixture JSONs + 3 compose YAML fixtures + matching expected.json), task crate (default_image/integration/lib/local/remote), yubaba (runtime/{containerd,fake}, deploy/{mesh_resolve,env_validate}, all 4 integration_*.rs files), cloud/config (3 sites), local-driver (local_runtime + pond_ssr_runtime), scryer/beholders, kamaji/{server,native,containerd}. (h) ImageSource::pull trait signature tightened: digest: Option<&’a str> → digest: &’a str (only one impl in yubaba/env_validate). (i) Read-site cleanup: yubaba::runtime::containerd::image_ref, kamaji::containerd::image_ref, local-driver::pond_ssr_runtime::compose_image_ref, task::local::image_ref_arg — all dropped Option ceremony, always emit tag@digest. cargo check –workspace clean. cargo test -p workload-spec: 82 pass. cargo test -p task –lib: 59 pass. Pre-existing test failures in cloud (5: 1 cloud_init drift, 4 mesofact_static adopt) and yubaba tests (pond_reconciler_smoke missing ssr_runtime/worker_mode/ssr_origin fields) are unrelated to ImageRef — separate ticket. R438-T4 (recipe loader) and R438-T6 (BuildMode::InContainer) inherit digest-required structurally with zero per-consumer work.”)

@yah:ticket(R438-T7, “Golden tests: recipe→ForgeSpec lowering + BuildMode→ForgeSpec lowering parity”) @yah:assignee(agent:claude) @yah:at(2026-06-04T21:07:30Z) @yah:status(review) @yah:phase(P3) @yah:parent(R438) @yah:next(“Golden test: sample transform recipe + asset.derive.transform.params lowers to expected ForgeSpec (argv, image digest, TaskPlacement)”) @yah:next(“Golden test: MesofactStaticWorkload with build_mode=in_container lowers to expected ForgeSpec”) @yah:next(“Round-trip parity: same Subprocess + Local + Container quadrant for both consumers; regression-guards argv-substitution and image-pin drop-through”) @yah:verify(“cargo test -p workload-spec lowering_golden_*”) @yah:verify(“Golden files versioned; updates require explicit –update flag”) @arch:see(.yah/docs/working/W164-derived-static-assets.md) @arch:see(.yah/docs/working/W165-mesofact-build-mode-lowering.md) @yah:depends_on(R438-T5) @yah:depends_on(R438-T6) @yah:handoff(“T7 landed. (1) Extracted pure lowering helpers exposed at pub(crate):\n - mesofact_static::lower_build_to_forge_spec(workload_dir, &BuildConfig, &BuildMode) -> ForgeSpec (run_build now wraps this)\n - static_asset::lower_recipe_step_to_forge_spec(&TransformRecipe, &RecipeStep, substituted_argv) -> ForgeSpec (materialize_transform now calls this for each step)\n(2) New cfg(test) module crates/yah/cloud/src/reconciler/lowering_golden.rs registered from reconciler/mod.rs. Five golden tests:\n - golden_recipe_step_lowers_to_pinned_local_container_subprocess (recipe → ForgeSpec shape: argv, image digest, timeout, label, initiator)\n - golden_recipe_step_with_zero_timeout_lowers_to_none (regression-guards the timeout=0 → None mapping)\n - golden_build_in_container_lowers_to_pinned_local_container_subprocess (BuildMode::InContainer → sh -c shell wrap + pinned image + cwd label)\n - golden_build_host_side_lowers_to_native_quadrant_without_image (BuildMode::HostSide → image=None + TaskRuntime::Native)\n - parity_recipe_and_build_in_container_share_quadrant (THE architectural invariant: both consumers land in the same Subprocess + Local + Container quadrant with sha256-pinned images and Gnome initiators — lets one ForgeExecutor dispatch handle both)\n(3) Test artifacts are hand-coded assertions, not insta/snapshot files — workspace has no insta infra and explicit-Pin tests give clearer diff on drift than auto-update snapshots. The W164/W165 lowering shape is now regression-guarded against silent drift in either consumer. cargo test -p cloud –lib reconciler::lowering_golden: 5 pass. Workspace check clean.”) @yah:next(“Sign off → archive R438-T7”) @yah:next(“T8 (worked examples) now has tested lowering primitives to reference”) @yah:verify(“cargo test -p cloud –lib reconciler::lowering_golden — 5 pass”) @yah:verify(“cargo test -p cloud –lib reconciler:: — 124 pass; 4 pre-existing R441-B4 adopt_only failures (port 4321 dev-box collision) unrelated”) @yah:verify(“cargo check –workspace –locked — clean (warnings only)”) @yah:verify(“Parity test asserts both lowerings produce TaskPlacement{Local, Container} + ForgeCommand::Subprocess + sha256-pinned image — the shared executor dispatch invariant”) @yah:gotcha(“Test location pivot: original ticket said cargo test -p workload-spec lowering_golden_* but the lowering primitives don’t live in workload-spec — ForgeSpec/TaskPlacement are in task, and the actual lowering helpers are in cloud (both consumers live there). Tests landed in cloud as reconciler::lowering_golden. If a future consumer outside cloud needs the BuildMode lowering, lift lower_build_to_forge_spec up to task::transforms alongside the existing recipe lowering primitives.”) @yah:gotcha(“No snapshot/insta infra in workspace — ‘Golden files versioned; updates require explicit –update flag’ verify line interpreted as hand-coded explicit assertions instead. Drift surfaces as a single-file test diff on the lowering helper, which is more readable than a .snap diff for the small ForgeSpec shape these tests cover.”)

@yah:ticket(R594-F2, “Ingress workload kind in workload-spec: pinned-per-node appliance on public-ip-tainted machines”) @yah:status(review) @yah:assignee(agent:claude) @yah:at(2026-07-03T06:03:30Z) @yah:phase(P2) @yah:parent(R594) @yah:next(“Add the ingress workload kind to the Workload enum (lib.rs:296) as an appliance in the R572 archetype sense: pinned-per-node, non-drainable, placed by yubaba only on machines carrying a public-ip taint, supervised by kamaji. Depends on R572-F1 (lifecycle archetype discriminator) so the archetype field exists to mark it. Breaking change is fine (pre-release house style); update kamaji-bin server.rs InvalidSpec rejection list deliberately — kamaji MUST accept this kind (it supervises the proxy), unlike MesofactStatic/Almanac/StaticAsset.”) @yah:verify(“cargo test -p yah-workload-spec; cargo check -p yubaba -p kamaji-bin; kamaji admission accepts kind=ingress in a unit fixture”) @yah:gotcha(“RUNS SOLO: workload-spec is the shared-type DAG sink (yah-base) — every lane (yubaba, kamaji, qed, host app) rebuilds on its change. Pause all other wave-2/3 implementer lanes while this is active, and check R572-T2 (cpu_millis, Handoff) + R572-F1 owner state before claiming — same file.”) @yah:depends_on(R572-F1) @yah:tier(Cleric) @yah:handoff(“Modeled the W267 public-ingress appliance as a container-shaped workload (Workload::Container(WorkloadSpec)), not a new Workload variant: mark archetype = Some(LifecycleArchetype::Appliance) (R572-F1, pinned/non-drainable) and declare the public-ip placement requirement via a new annotation-based marker on WorkloadSpec (same zero-blast-radius pattern as existing wants_host_network/HOST_NETWORK_ANNOTATION, chosen specifically to avoid the ~26-call-site churn a new plain field forced for R572-F1’s archetype field, and to avoid an exhaustive-match update in peer-owned kamaji-proto/codec.rs that a new Workload variant would force). Added: WorkloadSpec::requires_taint() -> Option<&str>, const REQUIRES_TAINT_ANNOTATION = "yah.placement.requires-taint", const PUBLIC_IP_TAINT = "public-ip", plus doc comments on Workload::Container recording the modeling decision and its rationale, all in oss/yah-base/crates/workload-spec/src/lib.rs (single file changed). This only declares the requirement as inert metadata — matching taint field on machine TOML is R572-F3 (not yet present) and scheduler enforcement is R572-F5; both out of scope here, noted in the doc comments. Verified kamaji needs NO change: deploy_workload’s match in kamaji-bin/src/server.rs already dispatches any Workload::Container(_) to the containerd backend regardless of tier/annotations (only MesofactStatic/Almanac/StaticAsset hit the InvalidSpec rejection arm), confirmed by reading the code and by the existing deploy_container_without_feature_says_so / deploy_mesofast_static_is_rejected_as_invalid_spec unit tests both still passing unmodified. 2 new unit tests added (ingress_marked_spec_is_appliance_and_carries_public_ip_placement_requirement, ingress_marked_spec_round_trips_through_json_as_a_container_workload). cargo test -p yah-workload-spec –lib: 38/38 pass. cargo test -p yah-workload-spec –test round_trip: 7 pass, exactly the same pre-existing 2 postcard failures (round_trip_full_spec_through_postcard, workload_container_round_trips_through_postcard — R590-B3, unrelated) as before this change, confirmed not increased. cargo check -p yah-workload-spec / -p yubaba / -p kamaji-bin all clean, plus full cargo check –workspace in both oss/kamaji and oss/yubaba clean (only pre-existing unrelated warnings). No peer-owned file touched or needed.”)

@yah:ticket(R590-B10, “forge workload 256MB cgroup memory limit SIGKILLs real builds — rusty-v8 checkout OOMs (bumped to 32GB stopgap)”) @yah:at(2026-07-12T00:14:52Z) @yah:status(review) @yah:assignee(agent:claude) @yah:parent(R590) @yah:severity(blocks-on-box-green) @yah:next(“Proper fix: thread a per-step memory request from the pipeline (QedStep) through ForgeSpec -> WorkloadSpec so a build declares its footprint, instead of a blanket forge default. Also consider: build_oci_spec should treat memory_mb==0 as ‘omit the cgroup limit’ (unlimited) so dedicated build-workers aren’t capped by an arbitrary constant; pair with a node-sized default. Revisit the 32GB stopgap once per-step resources land.”) @yah:verify(“yah qed run rusty-v8-musl on us-west-002 completes the checkout + gn/ninja compile without an OOM SIGKILL; a small forge task still runs (32GB is a ceiling, not a reservation).”) @yah:gotcha(“PROVEN live (2026-07-11): with B7 networking fixed, the rusty-v8 build cloned the full V8 tree then git checkout third_party/icu DIED OF SIGNAL 9 (OOM). WorkloadSpec::for_forge set resources.memory_mb=256, which build_oci_spec turns into a hard cgroup memory.limit. /tmp is a RAM-backed tmpfs so the multi-GB source checkout counts against that 256MB too. Bumped for_forge to 32768 (32GB) as a CLI-side stopgap; verified the build proceeds past icu.”) @yah:handoff(“FIXED + PROVEN LIVE (2026-07-11). WorkloadSpec::for_forge memory_mb 256 -> 32768 (oss/yah-base/crates/workload-spec/src/lib.rs). CLI-only change (spec is client-built), no kamaji redeploy. RESULT: with B7 networking, the rusty-v8 build previously OOM’d (SIGKILL/signal 9) at the icu git-checkout under the 256MB cgroup cap; now it clones the full V8 tree AND proceeds past icu into cargo/gn compilation (Compiling icu_locale_data/icu_calendar_data…) with task RUNNING. Stopgap 32GB ceiling; proper per-step memory request from the pipeline is the follow-up in the ticket body.”)

@yah:ticket(R546-B7, “workload_spec::Workload envelope is externally tagged (missing serde tag=kind) — no flat on-disk workload.toml can parse through it, broke yah cloud apply for EVERY static-asset component”) @yah:at(2026-07-20T23:52:55Z) @yah:status(open) @yah:parent(R546) @yah:next(“DO NOT simply add #[serde(tag = \"kind\")] without checking postcard: Workload is also a postcard wire type on the kamaji RPC path (kamaji-proto/src/codec.rs matches on it; round_trip tests exist). postcard is non-self-describing and cannot decode internally-tagged enums, so naive tagging risks breaking the kamaji wire. Decide deliberately: (a) tag it and prove the postcard round-trips still pass, or (b) split the types — an on-disk WorkloadManifest with tag=kind, leaving Workload as the untagged wire type.”) @yah:next(“INTERIM FIX ALREADY LANDED (unblocks publishing): static_asset.rs::load_workload no longer routes through the envelope — it deserializes a small KindProbe { kind }, validates kind == "static-asset", then parses StaticAssetWorkload directly. Same approach seed_derivation_for_target already used successfully. This restored yah cloud apply and got the x86_64 rusty-v8 artifact published to the CDN (HTTP 200). The ENVELOPE itself is still broken for every other caller/kind.”) @yah:next(“Fix the test/example disagreement: lib.rs ~L2544 should assert the FLAT kind = \"...\" shape that real files use, and examples/parse_whisper_toml.rs should run in CI so this cannot regress silently again.”) @yah:gotcha(“SEVERITY: this silently broke yah cloud apply for EVERY static-asset component, not just rusty-v8. Verified against the long-published whisper catalog via the repo’s own examples/parse_whisper_toml.rs, which panics with the identical error — so the breakage is general and pre-existing, not caused by the R546 hash edits.”) @yah:gotcha(“ROOT CAUSE: pub enum Workload (oss/yah-base/crates/workload-spec/src/lib.rs ~L386) derives Deserialize with ONLY #[serde(rename_all = \"kebab-case\")] — there is NO #[serde(tag = \"kind\")], despite its own doc comment stating ‘the kind field on the wire is the serde discriminator’. Without the tag it is EXTERNALLY tagged, so serde wants a map with exactly ONE key (the variant name). Every real workload.toml is FLAT (kind = \"static-asset\" + schema_version + [[asset]] + [aliases]), i.e. a multi-key map -> TomlError: wanted exactly 1 element, more than 1 element, reported confusingly at line 1 col 1.”) @yah:gotcha(“WHY THE UNIT TEST DIDN’T CATCH IT: the passing test at lib.rs ~L2544 feeds the EXTERNALLY-tagged shape [[static-asset.asset]], which no on-disk file actually uses. So the test asserts the broken encoding and the example (parse_whisper_toml.rs) asserting the REAL encoding was never run in CI. The test and the example disagree; the example is right.”)

@yah:ticket(R626-S3, “Where does desired-state live? Durable per-workload replica count that survives reconcile loops and camp restarts (0↔1 vs scale-to-N)”) @yah:status(review) @yah:assignee(agent:bundle-anthropic-glimmerstone) @yah:at(2026-07-23T17:47:24Z) @yah:kind(spike) @yah:phase(P3) @yah:parent(R626) @yah:handoff(“DECIDED + LANDED. Desired state lives in the CAMP DAEMON, in a durable camp-local document at /.yah/state/desired-state.json, and NEVER crosses the kamaji or yubaba wire. The governing principle, written to survive the tier: desired state belongs to the DECLARER, not the supervisor — whoever re-asserts a deployment owns the record of whether it is wanted, because anything stored below the declarer is overwritten by the declarer’s next re-assert. In the pond/dev tier the declarer is camp (ensure_pond_running -> reconcile_pond_deploys -> deploy_pond_mirrors, which runs at every camp start AND every pond.ensure_running RPC). In cloud the same rule points at the CloudConfig reconciler’s raft store. Kamaji is never the holder in either tier.”) @yah:handoff(“REJECTIONS, with the reason each is not a near-miss. kamaji-local: kamaji is deliberately imperative (Deploy/Stop/List, crash-restart delegated to dockerd’s policy per R626-F2) — it holds no desired set and runs no reconcile loop, so storing intent there means giving it a SECOND reconciler that can disagree with camp’s, and it still loses to camp’s POST /pond/deploy from above. yubaba raft: right answer at cloud scale, wrong scope here — the pond yubaba is a container camp starts, its PondRegistry is in-memory (a restart forgets everything), and a single-camp dev tier has no quorum to be consistent about. Git-tracked config: camp.toml/mirror.toml are the DECLARATION (what exists); a stop is per-machine operator intent (systemctl disable, not editing the unit file) and must not propagate to a teammate’s checkout — hence .yah/state/ is gitignored, in both the camp’s .gitignore and the scaffold_camp_skeleton template.”) @yah:handoff(“SHAPE: one knob, replicas, where 0 = stopped — deliberately the SAME axis as workload_spec::WorkloadSpec.replicas so scale-to-N later lifts a ceiling instead of adding a second concept beside a boolean. MAX_SUPPORTED_REPLICAS = 1 today and set_replicas REJECTS anything higher rather than persisting an intent no supervisor can honour (a clamp would silently record something the operator did not ask for). No record = replicas 1: a declared workload runs unless someone said otherwise. updated_at + reason ride along so a stale intent is legible and the UI can say when/why. Writes are tmp-then-rename; reads FAIL OPEN (missing/unreadable/corrupt/newer-schema all mean ‘everything runs’, corrupt file preserved as .corrupt-<epoch_ms>) — fail-closed would mass-stop a camp on one bad byte, and a resurrection is the recoverable failure.”) @yah:handoff(“LANDED: (1) app/yah/cli/src/desired_state.rs — DesiredStateDoc / WorkloadDesire / DesiredStateStore (load, desired_replicas, is_stopped, stopped_keys, set_replicas, stop, start, forget), 10 unit tests incl. survives-a-camp-restart, per-workload isolation, replicas>1 rejected AND not written, corrupt-file quarantine + fail-open, newer-schema fail-open, and an explicit ‘a stop is not a failure’ guard on the serialized document. (2) camp.rs: deploy_pond_mirrors and reconcile_pond_deploys now consult the store and skip stopped idents — this is THE enforcement point, since camp’s re-assert is the only place ‘stay stopped’ can be honoured. Extracted pond_idents_needing_deploy(declared, registered, stopped) as a pure helper with 5 tests, because the stopped-subtraction is the load-bearing half: a stopped workload is absent from yubaba’s registry ON PURPOSE and is indistinguishable from a failed deploy without the intent record. (3) .yah/.gitignore + app/yah/cli/templates/yah-gitignore-default gain /state. (4) .yah/docs/working/W287-desired-state-for-supervised-workloads.md carries the full rationale, the rejected options, the F4 build-on list, and the scale-to-N scoping.”) @yah:handoff(“DELIBERATE NON-GOAL: writing intent does NOT actuate. The durable record must land even when the stop call fails, or a failed stop comes back on the next reconcile. Actuation is R626-F4’s job.”) @yah:next(“R626-F4 is unblocked and now has a concrete spec — see W287 §5. It needs three things this ticket deliberately did not build: (a) a per-ident teardown on yubaba (PondRegistry has only shutdown_all, which drains everything; /pond/deploy and /pond/state are the only pond routes), (b) camp RPC methods workload.stop / workload.start writing through DesiredStateStore, (c) desired-vs-actual reporting.”) @yah:next(“DO NOT add a Stopped variant to PondPhase (R626-F2’s noted gap). PondPhase is yubaba’s observation of REALITY; intent never crosses that wire by this decision. Camp is the one process holding both halves — render the pair instead: desired=stopped + actual=absent reads ‘deliberately stopped’; desired=running + actual=absent reads ‘down’.”) @yah:next(“Scale-to-N stays scoped, not committed (W287 §6). WorkloadSpec.replicas makes N look one constant away; it is not. kamaji native.rs:592 rejects replicas>1, and the docker backend names containers by mesh identity (one identity, one container). N needs a placement layer above the single-workload supervisor: per-replica naming (identity==container name is what makes teardown resolve), per-replica host ports (pond publishes fixed ones — two replicas collide), per-replica mesh identity (a load-balanced set is an xlb-net concern), and a placement decision that is yubaba’s job on a fleet. Lifting MAX_SUPPORTED_REPLICAS is the entry point once that layer exists.”) @yah:next(“Wire DesiredStateStore::forget into the undeclare path so the document doesn’t accumulate intent for mirrors that no longer exist.”) @yah:verify(“cargo test -p yah –lib desired_state — 15 pass (10 desired_state::tests + 5 camp::r626_s3_desired_state_gate_tests), 0 fail”) @yah:verify(“cargo check -p yah — clean (note: this camp’s tree is shared and was transiently broken by peers’ in-flight edits in oss/qed, yah-party, and yah-almanac during this run; none touched by this ticket)”) @yah:verify(“BEHAVIOUR BAR (the one that matters): DesiredStateStore::for_camp(root).stop(ident) followed by a FRESH store over the same root still reports is_stopped — that is exactly a camp restart — and pond_idents_needing_deploy then omits that ident from an EMPTY registry, which is exactly a restarted yubaba. Asserted in camp::r626_s3_desired_state_gate_tests::a_stop_survives_a_camp_restart_end_to_end.”) @yah:gotcha(“The store is camp-local and GITIGNORED on purpose. If a future ticket wants a stop to be shared/durable in the repo, that is a different decision (declaration vs intent) — re-open W287 §2 rather than moving the file into tracked territory.”) @yah:gotcha(“Reads fail OPEN. Never ‘harden’ this into fail-closed: an unreadable document would then stop an entire camp, and the failure would be silent (nothing starts) rather than visible (the workload comes back).”)

Modules§

compose_import
One-way, lossy compose YAML → WorkloadSpec import shim.
control_plane_install
Shared install-script builder for a control-plane (yubaba + kamaji) roll.
forge_produced
Convention for a remote forge step’s durable produced artifacts.
rollout
Rollout policy schema — the typed form of .yubaba/rollout.toml.
secrets
Pluggable secret resolver for crate::SecretRef values.
validate
Shape validators for WorkloadSpec.

Structs§

AlmanacManifest
kind = "almanac" manifest — a declared data-pipeline job.
AssetDerive
Provenance chain for a derived asset: required fetch step, optional transform step. Materialized bytes replace AssetEntry.source for the rest of the static-asset reconcile loop.
AssetEntry
A single file entry in the static-asset catalog.
BackoffPolicy
Exponential backoff parameters for RestartPolicy::OnFailure.
BlakeHash
BLAKE3 content hash expressed as exactly 64 ASCII hex digits.
BuildConfig
Build step that produces the static artifact published by a mesofact-static workload.
DeriveLock
W212/R518: the committed derivation lock — the in-tree action-cache receipt. input_hash is the input-addressed derivation key computed over the complete declared input set (fetched-input pin ⊕ recipe-file bytes ⊕ invocation params ⊕ schema version); output_blake3 is what those inputs produced (== the entry’s blake3). The reconciler skips the entire build (no fetch, no transform, no PUT) when the lock matches the inputs recomputed from the current pins and the bucket already holds the output — the Nix-substituter / Bazel-remote-cache behaviour. Written by the R510 bind path from the reconciler’s discovered_input_hash:<filename> output; the git diff on this block is the receipt that the derivation rolled.
EnvVar
A single environment variable injected into the container.
ExposeSpec
Network exposure configuration. The three channels are independent; any combination is valid.
FetchSource
Shared fetch primitive — usable by asset.derive today, and by Almanac’s ReleaseSource after a follow-up migration (R438-F10). Defined once in workload-spec so both consumers reject the same set of non-permissive licenses.
Healthcheck
Container health probe configuration.
ImageRef
Container image reference identifying a specific image to pull.
MachineId
Opaque identifier for a yubaba-managed machine within the cluster.
MeshExpose
Mesh-internal port exposure and peer access control.
MeshIdent
DNS-segment identity for a workload on the cluster mesh, e.g. "noisetable-api.pdx". Regex constraint: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, length ≤ 63. Enforced in shape validation (R090-F2).
MesofactServeBundle
Serve-time reference to a published W272 bundle (R599-F4) — the {bundle_digest, runtime, lifecycle} triple a mesofact-static workload carries when kamaji, not the build reconciler, serves it.
MesofactStaticWorkload
kind = "mesofact-static" payload — static-site build colocated with the frontend it deploys.
Millis
Duration expressed as an integer millisecond count.
NamespaceId
Namespace routing/naming axis (W206). A pure naming key that never affects isolation: it selects the config root, disambiguates service DNS names within a tenant, prefixes object-store bucket names within a tenant’s bucket scope, and selects the provider zone (e.g. noisetable.com vs yah.dev). Two namespaces in the same tenant share networks, mesh-identity space, and PG cluster — they simply cannot collide on workload names or external domains. Specs written before this axis existed deserialize to NamespaceId::singleton.
OperatorExpose
Operator-facing exposure via a Tailscale ACL tag.
PublicExpose
Public internet exposure via a Cloudflare tunnel route.
ResourceLimits
Hard resource caps enforced by containerd/cgroups at runtime.
SecretMount
A secret value mounted into the container as an env var or file.
StaticAssetWorkload
kind = "static-asset" payload — content-addressed bucket catalog.
StopPolicy
Graceful shutdown configuration for yubaba’s stop sequence.
TenantId
Tenant isolation axis (W206). Separates one operator’s workloads from another’s at the network / DB / mesh-identity level. Orthogonal to NamespaceId (routing/naming) and TierTag (workload class within a (tenant, namespace) pair).
TierTag
Tier classification that governs admission control and mesh allow_from filtering. Known values: "public", "tenant", "private", "infra". Custom tiers are allowed per cluster; shape validation warns on unknowns rather than rejecting them (R090-F2).
TransformSpec
Optional transform applied after a FetchSource download, lowering to a ForgeCommand::Subprocess via the recipe loader (R438-T4). The transform’s output is content-addressed by the entry’s blake3 (the recipe runs only when the cache misses).
VolumeMount
A volume mount inside the container.
WorkloadSpec
Complete typed description of a containerd workload handed to yubaba over RPC. This is also the payload of the kind = "container" variant of Workload on disk.

Enums§

AlmanacTarget
An observable endpoint the almanac scheduler probes to check readiness.
BuildMode
Where the build command runs for a mesofact-static workload.
BundleLifecycle
Lifecycle mode for a served bundle (W272 §3).
Cadence
When the almanac scheduler triggers a run.
EnvValue
Value source for an environment variable.
HealthProbe
Mechanism used to check container health.
License
Closed-set, parse-time-enforced license tag. Mirrors the workspace permissive-license rule (MIT / Apache-2.0 / BSD-2/3-Clause / ISC). Adding a variant is an explicit schema change — non-permissive strings ("GPL-3.0", "AGPL", etc.) fail at serde-deserialize before any shape validator runs.
LifecycleArchetype
Explicit lifecycle archetype for a kind = "container" workload (W244).
MeshLookup
Which aspect of a mesh peer’s address to inject.
MeshPeer
A peer permitted to initiate mesh connections to a workload (W206 / R558-F3).
NotReadyPolicy
What the almanac scheduler does when a precondition check fails.
PublicTls
TLS mode for a public endpoint.
RestartPolicy
What yubaba does when the container exits.
SchemaVersion
Wire-format schema version envelope.
SecretRef
Where yubaba resolves the secret value from.
SecretTarget
How the secret is surfaced inside the container.
VolumeSource
Backing source for a volume mount.
Workload
On-disk workload.toml manifest. Each variant matches one ServiceComponent.kind value; the kind field on the wire is the serde discriminator.

Constants§

DEFAULT_NAMESPACE
Default single-namespace identity for specs that predate the namespace axis (W206). See NamespaceId::singleton.
DEFAULT_TENANT
Default single-tenant identity written to specs that predate the tenant axis (W206). Its concrete string is arbitrary — what matters is that a single-tenant cluster only ever sees this one value, so every per-tenant isolation primitive collapses to a no-op. See TenantId::singleton.
HOST_NETWORK_ANNOTATION
Annotation key requesting a workload share the host network namespace. See WorkloadSpec::wants_host_network.
HOST_NETWORK_VALUE
Annotation value (for HOST_NETWORK_ANNOTATION) selecting host networking. Any other value leaves the workload in an isolated netns.
PUBLIC_IP_TAINT
Taint name (for REQUIRES_TAINT_ANNOTATION) identifying machines with a publicly-routable IP — the W267 sovereign-ingress placement requirement. The corresponding taint field on the machine TOML doesn’t exist yet (R572-F3); this constant is the agreed-upon name both sides will use once it does.
REQUIRES_TAINT_ANNOTATION
Annotation key declaring that a workload must land only on a node carrying a specific taint. See WorkloadSpec::requires_taint.

Traits§

WorkloadRuntime
Shared interface for deploying and managing WorkloadSpec containers.

Functions§

intra_tenant_address
The name by which a workload is addressed within its own tenant (W206 / R558-F3), given every (namespace, identity) pair present in that tenant.