Skip to content

let an artifact say who owns it instead of inferring it from its kinds - #83

Merged
thc1006 merged 1 commit into
mainfrom
fix/artifact-provenance
Aug 11, 2026
Merged

let an artifact say who owns it instead of inferring it from its kinds#83
thc1006 merged 1 commit into
mainfrom
fix/artifact-provenance

Conversation

@thc1006

@thc1006 thc1006 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Why

Three findings from the review of #81, all reproduced against its head before anything changed here. They are one problem: the compiler was inferring ownership from things that cannot express it.

Attribution was a kind only one renderer emits. A ResourceClaimTemplate is not one — both write it — so the standalone *-scheduler-fallback.yaml holds nothing that decides. It was a subset of both sets, belonged to neither, and turning --dra-fallback off left the previous generation in the desired-state directory permanently. Measured: after --prune, the file is still there and stale_unattributable is absent, so nothing tells the operator either.

Ownership was a mission fingerprint. The priority-class bundle has none: it is cluster-scoped, one set per installation, which is what --priority-class-prefix exists to keep from colliding. Two bundles both answering "no mission" is how the ownership check compared None with None and let a second installation replace the first's class set. Measured: team-a- class names gone, no warning, and the Jobs referencing them untouched.

A mission-scoped --prune deleted that bundle outright. One invocation does not hold the installation's desired set, so it cannot tell a class nobody needs from one another mission's Jobs still reference. A warning after the fact is not ownership.

What

Every document carries orbital/renderer, orbital/owner-scope, orbital/artifact-role and orbital/ownership-schema, with the owner's own name in orbital/owner-id beside the fingerprint — 64 bits of a digest should not decide a delete by itself.

The bundle's owner is the prefix, because that is what makes one installation's classes distinct from another's in a cluster. A cross-owner replacement is refused; the same installation replacing its own is not.

Ordinary --prune manages mission scope. --prune-global is the opt-in for cluster-scoped artifacts, and only for the installation that wrote them. Either way they are named in the report and on stderr, because leaving them silently is how an operator finds out by having a workload fail admission.

Every document in one file has to agree on that tuple. An operator concatenating two renders into one bundle for kubectl apply -f produces a file that answers for nobody, and it is not deleted on either writer's say-so.

An artifact carrying no schema label predates this and is reported rather than acted on. Adopting it would mean guessing again; re-rendering adopts it properly. That is a deliberate behaviour change for a directory written by an older version, and it fails closed.

Verification

Written as failing tests first, then each guarantee checked against a mutation: attributing by kind again, dropping the installation comparison in preflight, giving the bundle mission scope, letting an ordinary prune take it, letting another installation take it, and letting a file's documents disagree.

The last one was not caught. The agreement rule had no test at all, which is why there are three now — and re-running that mutation fails all three.

The three original reproductions were re-run end to end: the second prefix now exits 2 with team-a- intact, the scheduler fallback is retired by its own renderer and not by the other, and a second mission's --prune leaves the bundle and says why.

1001 pytest passed, 1 skipped. ruff clean, mypy clean, 15 goldens pass.

Depends on #82

Third in the stack: #81, then #82, then this. It rewrites the ownership model #81 introduced, so reviewing them apart would mean resolving that overlap at merge time.

… kinds

Three defects from the same external review, all reproduced before the change
and all rooted in guessing.

Attribution was a kind only one renderer emits. A ResourceClaimTemplate is not
one: both write it, so the standalone `-scheduler-fallback.yaml` holds nothing
that decides, was a subset of both sets, and belonged to neither. Turning
--dra-fallback off left the previous generation in the desired-state directory
for good -- and it was not reported as unattributable either, so nothing said
so. Every document names its writer now, and the renderer that wrote it takes
it back while the other still does not touch it.

Ownership was a mission fingerprint. The priority-class bundle has none: it is
cluster-scoped, one set per installation, which is what --priority-class-prefix
keeps from colliding. Two bundles both answering "no mission" is how the
ownership check compared `None` with `None` and let a second installation
replace the first's class set, silently, while the Jobs naming those classes
stayed where they were. The prefix is the installation, so that is the owner
the bundle carries, and a cross-owner replacement is refused.

And a mission-scoped --prune deleted that bundle outright. A single invocation
does not hold the installation's desired set, so it cannot tell a class nobody
needs any more from one another mission's Jobs still reference. It leaves them
now and says it did, with --prune-global as the opt-in for the installation
that owns them. A bundle belonging to someone else is named and left whatever
the run was asked to do.

So every document carries renderer, owner-scope, artifact-role and a schema
version, with the owner's own name in an annotation beside the fingerprint --
64 bits of a digest should not decide a delete by itself. Every document in one
file has to agree on that tuple: an operator concatenating two renders into one
bundle produces a file that answers for nobody, and it is not deleted on either
writer's say-so.

An artifact carrying no schema at all predates this and is reported rather than
acted on. Adopting it would mean guessing again; re-rendering adopts it
properly.

Mutations: attributing by kind again, dropping the installation comparison in
preflight, giving the bundle mission scope, letting an ordinary prune take it,
letting another installation take it, and letting a file's documents disagree.
The last one was not caught at first -- the agreement rule had no test -- which
is why there are three now.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 changed the base branch from fix/structured-input-errors to main August 11, 2026 04:18
@thc1006
thc1006 merged commit 2750da3 into main Aug 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant