Sync engine

How tenant objects become deterministic host objects — and how they're traced back.

The sync engine is the runtime that executes a SyncPolicy. Where the planner decides where a tenant object should land, the engine actually applies and garbage-collects the host objects — recording an explainable decision for every action.

Deterministic naming

Every host object is named from the tenant object’s identity:

<resource name>-x-<virtual namespace>-x-<tenant cluster>

For example, a Pod nginx in the tenant’s default namespace on tenant dev becomes nginx-x-default-x-dev. If that would exceed the 63-character DNS label limit, the name is truncated and suffixed with a stable hash so it stays unique and within bounds.

This determinism means the same tenant object always maps to the same host object — no bookkeeping table required.

Reverse mapping

Determinism gives you the forward direction; labels and annotations give you the reverse. Every host object carries:

MetadataKeyPurpose
Labelapp.kubernetes.io/managed-by: tenantplaneMarks the object as tenantplane-owned.
Labeltenantplane.io/tenantSelectable tenant name.
Labeltenantplane.io/virtual-namespaceSelectable virtual namespace.
Labeltenantplane.io/kindSelectable kind.
Annotationtenantplane.io/virtual-namespaceVerbatim original namespace.
Annotationtenantplane.io/virtual-nameVerbatim original name (survives name hashing).

Because the name can be hashed, the original identity is preserved verbatim in annotations — so a reverse lookup is always exact, even for long names.

A convergence pass

For each toHost resource kind, one pass does:

  1. List tenant objects of that kind, skipping tenant system namespaces (kube-system, kube-public, kube-node-lease).
  2. Transform each into its host object: deep-copy, rename/re-namespace, merge reverse-mapping metadata, and strip server-populated and host-owned fields (resourceVersion, uid, ownerReferences, status, …).
  3. Apply it — create if absent, otherwise update in place. Before overwriting, the engine checks the existing object’s provenance: if it belongs to a different tenant source (a name collision) or to no tenantplane source at all (a foreign object occupying the name), it refuses to clobber and records a Skip instead.
  4. Garbage-collect — delete host objects tenantplane manages for this tenant and kind whose tenant source no longer exists. Objects that aren’t tenantplane-managed are never touched.

Individual object errors are aggregated, so one bad object doesn’t abandon the rest of the pass.

Name collisions

The host name is deterministic but not perfectly injective: names over the 63-character DNS limit are truncated and suffixed with a hash, so two very long names could in principle map to the same host name. tenantplane does not pretend this can’t happen — it makes it safe. The apply step above never overwrites an object that reverse-maps to a different source, turning a would-be silent data-loss into a visible Skip decision you can act on.

Decisions

Every action produces a Decision with an action (Create/Update/Delete/ Skip), the tenant reference, the host target, and a human-readable reason. Today these are emitted as Kubernetes Events on the TenantCluster (deletes as Warning, everything else Normal), so kubectl describe tenantcluster answers “why does this host object exist?”.

kubectl describe tenantcluster dev | grep -A2 Events

Found a gap? Open an issue or PR.