pub async fn merge_as<K, B>(
api: &Api<K>,
name: &str,
field_manager: &str,
body: &B,
) -> Result<K, Error>Expand description
Merge-patch the PRIMARY resource endpoint of any kube Resource under
field_manager with force = true — the merge-strategy sibling to
apply on the (Patch-strategy × PatchParams-posture) matrix.
Owns the two-link chain
apply_patch_params(<mgr>) + api.patch(name, &pp, &Patch::Merge(&body))
at ONE substrate owner. Closes the four-corner posture matrix the
wire-side patch family stamps:
| Strategy | PatchParams::default() | apply_patch_params(<mgr>) |
|---|---|---|
| Merge | merge | merge_as (this one) |
| Apply | (invalid — SSA requires a field manager) | apply |
merge owns the anonymous-writer merge-patch corner
(PatchParams::default(), no field-manager ownership); apply owns
the SSA corner (Patch::Apply under a named field manager); this
primitive owns the remaining corner — a merge-patch that STILL stamps
a named field manager on the write, chosen when the caller wants
merge-patch semantics (server merges the caller’s partial body into
the existing object per RFC 7396, rather than the SSA ownership
reconciliation model) BUT wants the write attributed to a named
controller in the field-manager ownership audit (so downstream kubectl get -o yaml’s managedFields distinguishes a
tatara-pool-reconciler-stamped bind edit from a
tatara-reconciler-stamped phase-transition status write).
Pre-lift the two-link chain was hand-authored at TWO workspace-wide
consumer sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
both inside tatara-pool-reconciler::controller_allocation:
- Bind arm — stamps the compound
spec.lifetimeoverlay + the threemetadata.annotationsrequestor / allocation / requestor-kind labels onto the pool member Process on transition from Queued to Bound. Body:{"spec": {"lifetime": …}, "metadata": {"annotations": {REQUESTOR: …, ALLOCATION: …, REQUESTOR_KIND: …}}}. Field manager:ctx.config.field_manager(per-instance String). - Release arm — stamps the single
tatara.pleme.io/return-triggerannotation onto the member Process to nudge the Pool reconciler into taking the return path. Body:annotation_body-composed single-key metadata edit. Field manager:ctx.config.field_manager(same String).
Both sites walked the SAME two-link chain — build a PatchParams via
apply_patch_params with the pool-reconciler’s per-instance
field_manager, then dispatch through api.patch(name, &pp, &Patch::Merge(&body)). Post-lift each callsite reads
tatara_process::patch::merge_as(&api, name, <mgr>, &body).await
and the params-build + Patch::Merge wire dispatch lives at ONE
substrate owner. A future normalization of the named-merge-writer
posture (an injectable dry_run mode for a shadow-mode rollout, a
field_validation default when the pool-reconciler flips on strict
validation, an injectable retry policy for the transient-conflict
class the bind arm surfaces on race with a sibling pool controller,
a resourceVersion precondition slot when the pool controller
stamps generation-fenced binds) lands at THIS ONE substrate primitive
(or at apply_patch_params on the params sub-axis) and every
downstream named-merge writer inherits the upgrade mechanically.
Directly benefits the P3 kenshi-runner library lift (any test-Job
controller that stamps a named-merge overlay on its owning Process
— a suite-progress annotation, a per-run bind edit — rides through
the same primitive as the pool-reconciler’s bind + release arms) and
the P5 shigoto Dag refactor (any RecordingJob that stamps a
per-instance-named merge edit on a phase transition, rather than
through the crate::patch::apply SSA path or the anonymous
merge path, rides through this substrate corner rather than
hand-authoring the two-link chain a third time).
The bound relaxation K::DynamicType: Default is NOT required here
(matching apply’s posture, differing from merge /
merge_status) so a future Api<DynamicObject> consumer of the
named-merge corner rides the same primitive as the current
concrete-Api<Process> consumers. Api::patch itself needs only
K: Clone + DeserializeOwned + Debug on its own impl block; the
Default bound on the sibling merge primitives is a legacy of their
pre-lift call sites, none of which exercised DynamicObject.
Return-form axis: Result<K, kube::Error> matches Api::patch
verbatim. Both pre-lift consumers ignore the returned K (the bind
arm captures the Err for a retry decision; the release arm discards
through let _ = …); keeping the return in the signature lets a
future writer that needs the reconciled resourceVersion /
generation from the same wire round-trip read it without a
re-fetch.
Theory anchor: THEORY.md §VI.1 (generation over composition — the
two-link apply_patch_params(<mgr>) + api.patch(name, &pp, &Patch::Merge(&body)) chain recurred at 2 hand-authored sites past
the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger inside one workspace
crate, and is lifted onto ONE substrate owner here, closing the
(Patch-strategy × PatchParams-posture) matrix’s remaining hand-
authored corner). THEORY.md §II.1 invariant 5 (composition preserves
proofs — the pin block below binds the Patch::Merge posture + the
apply_patch_params pass-through + the byte-identical parity with
the pre-lift two-link chain, so a regression that drifts any surface
surfaces here rather than as silent named-merge writer skew across
the two pool-reconciler callsites).