Zastava Architecture (Retired)

Scope. This is the public-facing retirement record for Zastava, the former Kubernetes admission/runtime-observation direction. Zastava is no longer a live Stella Ops surface; runtime posture and workload observation have been absorbed into the deployment agent (StellaOps.Agent.Host). Read this if you are tracing the history of StellaOps.Zastava.*, the zastava.* wire schema identifiers, or the reused Zastava name. For the live replacement, see Replacement Architecture and ../release-orchestrator/architecture.md. For the name-collision disambiguation (the live registry-webhook SBOM source type and the dead CLI stub), see README.md.

FULLY RETIRED 2026-05-12 (SPRINT_20260512_018). The Zastava source tree (StellaOps.Zastava.Agent, StellaOps.Zastava.Observer, StellaOps.Zastava.Webhook, StellaOps.Zastava.Core, all tests, and the module solution) has been moved to docs-archive/code-snapshots/zastava-2026-05-12-retired/. The zastava-webhook compose service (Slot 43) and Dockerfile stages are decommissioned. No live solution references StellaOps.Zastava.* projects anymore. This document is preserved as the public-facing retirement record.

Status

This architecture is retired for active Stella Ops implementation.

The previous Zastava design centered on Kubernetes admission and runtime observation patterns. That direction conflicts with the current product decision: Stella Ops does not support Kubernetes or Helm deployments.

Contract relocation note (Sprint 20260512_020): The runtime/admission DTOs previously housed at src/Zastava/__Libraries/StellaOps.Zastava.Core/Contracts/ were moved to the neutral library src/ReleaseOrchestrator/__Libraries/StellaOps.Runtime.Contracts/ under namespace StellaOps.Runtime.Contracts. The static class formerly named ZastavaContractVersions is now RuntimeContractVersions. The schema STRING identifiers on the wire (zastava.runtime.event@v1, zastava.admission.decision@v1) are unchanged for backward compatibility with deployed Scanner.WebService consumers. The transitional StellaOps.Zastava.Core ProjectReference to StellaOps.Runtime.Contracts was dropped in SPRINT_20260512_018 (ZA-ARCH-004) when the whole Zastava tree was archived.

Replacement Architecture

Runtime posture and workload observation are now absorbed into the deployment agent (StellaOps.Agent.Host) rather than a separate host-agent product per the Sprint 20260512_020 design decision. The agent that already ships, gets provisioned with mTLS certs, and runs as stellaops-agent-core on .10/.20 also carries the runtime.observe.container capability that emits RuntimeEvidenceEnvelope payloads to BOTH Signals (signals.runtime_facts) and the Findings Ledger (findings.runtime_traces). Both downstream tables gained a nullable release_id column in this sprint so runtime evidence can be tied to a deployed release without cross-schema joins.

Legacy references (kept for completeness, no longer load-bearing):

See:

Guardrail

Do not restore Kubernetes admission webhook, DaemonSet, Kubernetes API watch, Helm chart, or kubectl workflows under Zastava. Any surviving runtime posture concept must be redesigned for non-Kubernetes host agents first.

CRI digest resolution (in-process, fail-closed) — ARCHIVED DESIGN

ARCHIVED / NOT LIVE. The components described in this section are not present in src/. They were part of the now-retired StellaOps.Zastava.Observer project and live only in the archived snapshot at docs-archive/code-snapshots/zastava-2026-05-12-retired/src/StellaOps.Zastava.Observer/ContainerRuntime/Cri/. No live solution references ContainerRuntimePoller, ICriImageDigestResolver, CriImageDigestResolverFactory, or the CRI/kubelet resolvers below. The section is preserved as a historical design record; the kubelet pod-status fallback in particular is Kubernetes-specific and conflicts with the non-Kubernetes guardrail above. The fail-closed digest-resolution intent survives in the replacement architecture (the runtime.observe.container capability and the RuntimeEvidenceEnvelope); the implementation below does not.

The archived StellaOps.Zastava.Observer components (the ContainerRuntimePoller, the CRI v1 client, and the runtime-facts emitter) were designed to obtain an authoritative (imageRef, imageId) tuple for every running container before publishing a runtime fact. Synthesising digests from container IDs, image names, or wall-clock timestamps was forbidden — the non-resolved state was first-class and was propagated downstream as fail-closed input for Signals/Scanner consumers.

Precedence

ContainerStatus(image_ref) ──┐
                             ├─► CRI primary resolver ──► resolved (imageRef, imageId)
ImageStatus(image_spec) ─────┘                                │
                                                              ▼  (only on RuntimeReportedNoDigest)
                                              kubelet pod-status fallback
                                              (containerStatuses[*].imageID)
                                                              │
                                                              ▼
                                              not-resolved:<reason> (fail-closed)
  1. CRI primary (ContainerdCriDigestResolver, CriOCriDigestResolver, DockerCriDigestResolver).

    • Call RuntimeService.ContainerStatus. If image_ref is digest-pinned or a bare algo:hex token, resolve immediately.
    • Otherwise call ImageService.ImageStatus(image_spec) and prefer the first parseable repo digest, then a digest-shaped image id.
    • Docker’s primary uses IWindowsContainerRuntimeClient and reads the inspect endpoint’s Image (image id) and Config.Image (ref) fields.
  2. Kubelet pod-status fallback (KubeletPodStatusFallbackResolver).

    • Activates only when the primary returned NotResolvedReason.RuntimeReportedNoDigest. Other reasons (RuntimeUnreachable, ContainerNotFound) are surfaced unchanged so the fallback never papers over runtime errors.
    • Reads containerStatuses[*].imageID from the Kubernetes pod-status view (host-agent kubelet client, registered as IKubernetesPodStatusReader). If the field is empty the original RuntimeReportedNoDigest is preserved — there is no further escalation.
  3. Not-resolved marker. When neither layer produces a digest, the runtime-facts envelope’s Workload.ImageRef is set to a stable lowercase marker:

    • not-resolved:runtime-unreachable
    • not-resolved:container-not-found
    • not-resolved:runtime-reported-no-digest
    • not-resolved:digest-malformed

    The envelope also carries runtime.image.digestSource = not-resolved and runtime.image.notResolvedReason = <marker> evidence entries so downstream Signals/Scanner consumers treat the workload as evaluation-incomplete and deny it under fail-closed posture.

The (archived) interface contract is at docs-archive/code-snapshots/zastava-2026-05-12-retired/src/StellaOps.Zastava.Observer/ContainerRuntime/Cri/ICriImageDigestResolver.cs and the runtime selection was in CriImageDigestResolverFactory.cs (same archived directory).