Skip to content

docs(unified-tls-pki): fix the canonical trust-anchor name and its owner - #36

Open
Aleksei Sviridkin (lexfrei) wants to merge 4 commits into
mainfrom
fix/tls-pki-canonical-name-and-ownerref
Open

docs(unified-tls-pki): fix the canonical trust-anchor name and its owner#36
Aleksei Sviridkin (lexfrei) wants to merge 4 commits into
mainfrom
fix/tls-pki-canonical-name-and-ownerref

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Implementing the accepted design surfaced four errors in it. All are corrected here, across both proposals. Every one was found by building the thing, not by reading it.

The canonical trust anchor is now <release>.tenant-ca, and the reason it took three names is the point. <release>-ca-cert was rejected because Percona PSMDB creates a Secret of exactly that name and puts a private key in it — a collision across engines. Its replacement <release>-tenant-ca was justified as "claimed by no operator this platform ships". That was also false, one level up: it collides across releases. For an application foo the projection is postgres-foo-tenant-ca; for a sibling application foo-tenant, CloudNativePG's own CA is postgres-foo-tenant + -ca — the same string. Both names are legal and several Postgres instances per namespace is ordinary. One direction of that collision cannot be guarded at all: if the projection is written first, CloudNativePG rejects the key-free Secret (missing ca.key secret data), the sibling's PKI never completes, and that application never starts — silently, blaming a Secret its owner never created. It is a regression, and there is nothing to refuse, because the controller that wrote first was legitimate. <release>.tenant-ca is disjoint by character class instead: application names are DNS-1035 labels and structurally cannot contain a dot, release prefixes are dot-free, and Secret names are DNS-1123 subdomains where a dot is legal — so no prefix + application + operator suffix can ever produce a dotted name, for any operator, now or later. That is a proof rather than a survey, which is what the two previous names lacked.

The projection cannot be owner-referenced to the application CR. The apps.cozystack.io kinds are virtual: they are served by the aggregated apiserver, and an application is stored as a HelmRelease whose spec.values is the application spec verbatim. There is no application object in etcd and therefore no UID to reference. The HelmRelease is the only real object in the chain — and it is sufficient, because deleting the application through the aggregated API deletes the HelmRelease, which garbage-collects the projection with it.

The source-selection label is not enough on its own. The platform runs cert-manager with enableCertificateOwnerRef: false, so a cert-manager-issued Secret carries no ownerReference back to its application and the lineage graph cannot resolve which release it belongs to. The consume contract therefore requires a second label, internal.cozystack.io/publish-ca-cert-release, carrying the release name, stamped alongside the opt-in label in the same secretTemplate block.

The trust-anchor guard must parse, not pattern-match. The proposal's fail-closed boundary was specified as a header check — reject anything carrying a private-key header, accept anything carrying a certificate header. That is not sufficient: PEM armour around arbitrary bytes satisfies it, and pem.Decode does not close the gap either, since it validates the armour and the base64 rather than the contents. Only x509.ParseCertificate on every decoded block, with no trailing remainder, actually asserts "this is a certificate".

All four were found by the implementation in cozystack/cozystack#3299.

Summary by CodeRabbit

  • Documentation
    • Updated unified TLS/PKI trust-anchor guidance to use the canonical <release>.tenant-ca Secret name (replacing the prior <release>-ca-cert).
    • Clarified that the trust-anchor Secret contains only ca.crt and uses the updated tenant-visible labeling.
    • Refreshed the delivery/cleanup documentation, including how certificate-source matching works and how the projected trust-anchor Secret is owned for reliable garbage collection.
    • Updated diagrams, open questions, and consumer references to match the revised contract and naming.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a90256cf-4a0e-480f-9be7-b76ede7a3f19

📝 Walkthrough

Walkthrough

The proposals standardize the tenant trust-anchor Secret as <release>.tenant-ca, define its labels and contents, add release-based source selection, revise projected Secret ownership, and update external database exposure references.

Changes

Tenant trust-anchor contract

Layer / File(s) Summary
Canonical trust-anchor naming
design-proposals/{external-database-exposure,unified-tls-pki}/README.md
Consumer references and the unified TLS/PKI contract now use <release>.tenant-ca, including its ca.crt contents and tenant-ca marker label.
Extraction controller delivery contract
design-proposals/unified-tls-pki/README.md
The controller uses publish-ca-cert-release for source association, projects <release>.tenant-ca, owner-references it to the owning HelmRelease, and updates related edge cases, tests, and alternatives.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • cozystack/cozystack#3286 — Covers the unified TLS/PKI extraction-controller contract and release-based CA source labeling.

Possibly related PRs

Suggested reviewers: lllamnyp

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main documentation changes: the canonical trust-anchor name and its owning resource.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tls-pki-canonical-name-and-ownerref

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the design proposals for external database exposure and unified TLS PKI. It renames the canonical trust-anchor Secret from <release>-ca-cert to <release>-tenant-ca to prevent naming collisions with existing operator-created Secrets (such as Percona PSMDB). Additionally, it updates the extraction controller's design to owner-reference the HelmRelease instead of the virtual application instance CR to ensure proper garbage collection. The reviewer feedback points out that the external database exposure proposal still contains outdated text claiming redis self-publishes its CA directly, whereas under the updated design, all engines (including redis and kafka) utilize the extraction controller to project their CAs to the canonical <release>-tenant-ca Secret.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread design-proposals/external-database-exposure/README.md Outdated
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review July 14, 2026 16:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@design-proposals/external-database-exposure/README.md`:
- Line 139: Update the diagram’s trust-anchor Secret reference near the external
database exposure flow to use <release>.tenant-ca instead of <release>-ca-cert,
matching the trust-anchor name defined in the surrounding proposal and keeping
the ca.crt-only contract unchanged.

In `@design-proposals/unified-tls-pki/README.md`:
- Around line 156-167: Define one canonical delivery path: require every
engine-specific CA Secret, including self-publishing Redis and similar sources,
to be projected into <release>.tenant-ca by the extraction controller rather
than writing that canonical object directly. Update
design-proposals/unified-tls-pki/README.md at lines 156-167 to make this
mandatory and consistent, and update
design-proposals/external-database-exposure/README.md at line 19 to describe
Redis’s <release>-ca-cert strictly as an internal source.
- Around line 156-167: Clarify the design so every engine has one mandatory path
for producing <release>.tenant-ca. Either require the extraction controller to
project the canonical Secret for all engines, or explicitly document each
self-publishing engine's equivalent canonical-object creation and remove any
conflicting opt-out language. Update the descriptions in the
extraction-controller and reuse sections consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff0f6c24-ea7c-4bf2-a5a6-98138745a2e1

📥 Commits

Reviewing files that changed from the base of the PR and between 789654a and bbbbfce.

📒 Files selected for processing (2)
  • design-proposals/external-database-exposure/README.md
  • design-proposals/unified-tls-pki/README.md

Comment thread design-proposals/external-database-exposure/README.md
Comment thread design-proposals/unified-tls-pki/README.md Outdated

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed as a docs pass. The rename and the ownership correction are applied only in the definitional sections, so the document currently contradicts itself on both points it sets out to fix. Suggest holding until the references are reconciled.

Stale/contradictory references (design-proposals/unified-tls-pki/README.md unless noted):

  • L175 still says the one canonical trust-anchor object a tenant sees is <release>-ca-cert, contradicting the new definition at L137 (<release>.tenant-ca).
  • L197 re-asserts both errors this PR fixes: the stale name, and garbage-collection via an owner reference to the application instance, contradicting rewritten section (b) which shows the owner-ref must be the HelmRelease.
  • L99 (interior-tier diagram node, ca.crt-only) and external-database-exposure/README.md L110 (diagram node projected via tenantsecrets) still use the stale canonical name.
  • L171, L179, L203, L228 still reference the projected/canonical object by the old name.

Note: occurrences at L41, L43, L121-124, L156, L160, L167, L214, L226 correctly keep -ca-cert because they name engine-specific source secrets or describe the old-name collision; those should not change.

Minor: external-database-exposure/README.md L19 says redis self-publishes while unified-tls-pki/README.md L167 says redis still needs projection; the two docs disagree.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/tls-pki-canonical-name-and-ownerref branch from bbbbfce to 1366c24 Compare July 20, 2026 02:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@design-proposals/unified-tls-pki/README.md`:
- Line 160: Update the source-value validation contract to strictly parse every
PEM value with x509.ParseCertificate, rejecting non-certificate PEM blocks,
malformed input, and any trailing data instead of relying on PRIVATE KEY header
detection. Extend the controller tests covering the source selection and
certificate validation flow with malformed and trailing-data cases.
- Line 215: The proposal inconsistently treats publish-ca-cert and
publish-ca-cert-release as both required contract fields and unresolved names.
Update the documentation around the label/annotation convention and the
definitions at the corresponding contract section to either finalize these exact
names consistently or mark them as placeholders throughout, ensuring source
producers and the controller use the same contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 680ecd39-8923-4eb8-8ab7-9bba409a51ad

📥 Commits

Reviewing files that changed from the base of the PR and between bbbbfce and 1366c24.

📒 Files selected for processing (2)
  • design-proposals/external-database-exposure/README.md
  • design-proposals/unified-tls-pki/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • design-proposals/external-database-exposure/README.md

Comment thread design-proposals/unified-tls-pki/README.md Outdated
Comment thread design-proposals/unified-tls-pki/README.md Outdated
@lexfrei

Copy link
Copy Markdown
Contributor Author

Good catch, and it was wrong in a stronger sense than the comment says. The forked redis-operator does not self-publish a key-free CA at all on main: caCertSecretName has zero occurrences there and the image builds stock upstream spotahome/redis-operator with a labels-only patch. The fork and its key-free output live in cozystack/cozystack#2729, still open. So kafka is the only engine at the target today. Fixed in 17ef55d on the stacked branch (#41), in all four places that claimed it.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT LGTM (request changes). The titled change (renaming the trust anchor and moving its owner to the HelmRelease) is done cleanly and consistently across both proposals, with no orphaned old names and the mermaid diagrams updated.

One blocker: the description says it corrects four errors, but the document implements three. The fourth (the guard must parse the certificate with x509.ParseCertificate rather than pattern-match the PEM header) is absent: grep finds no x509/ParseCertificate/pem.Decode, and section 5(b) still specifies the header check the PR body itself calls insufficient. Please either add the x509-parse correction to the contract, or drop the fourth item from the description, so an implementer does not build the weaker header guard on a security-load-bearing boundary.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: the CHANGES_REQUESTED from 2026-08-18 is not addressed.

  • [MAJOR] §5(b) still specifies the guard as "carries no -----BEGIN … PRIVATE KEY----- header", while the PR body claims the guard was moved to x509.ParseCertificate. The diff does not touch that text (grep for x509/ParseCertificate/pem.Decode is empty on the branch head), and there have been no commits since 2026-07-19. Worse, the already-merged controller (#3407) actually implements x509.ParseCertificate in a pem.Decode loop, so the ADR now lags both its own PR body and the deployed code. Please align §5(b) with the merged implementation.
  • [MINOR] The §3 redis row and external-database-exposure/README.md still assert in the present tense that redis self-publishes a key-free <release>-ca-cert through its forked operator. That was confirmed inaccurate in-thread: on main the operator is stock and the fork lives in the open #2729. The fix currently exists only in the stacked #41, so #36 on its own carries a false statement.
  • [NIT] Open questions still list the publish-ca-cert label names as unresolved although §5(a) treats them as an accepted contract (#41 removes the label mechanism entirely, so this is minor).

Implementation surfaced two errors in the accepted design.

The canonical projection cannot be named <release>-ca-cert. Percona PSMDB
creates a Secret of exactly that name and puts a private key in it, so
source and target collide on the very engine that most needs the
projection; Strimzi publishes its key-free CA under a different name
again. The document itself warned three times that this name means
opposite things on different engines, used that to reject name-based
source selection, and then picked it for the target anyway. The canonical
object is now <release>-tenant-ca, which no shipped operator claims.

The projection also cannot be owner-referenced to the application CR. The
apps.cozystack.io kinds are virtual: an application is stored as a
HelmRelease, so there is no object in etcd to reference. The HelmRelease
is the only real owner, and deleting the application deletes it, which
garbage-collects the projection.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Implementation showed the source-selection label is not enough on its
own. The platform runs cert-manager with enableCertificateOwnerRef set to
false, so a cert-manager-issued Secret carries no ownerReference back to
its application and the lineage graph cannot tell which release it belongs
to. The consume contract therefore requires a second label,
internal.cozystack.io/publish-ca-cert-release, carrying the release name,
stamped alongside the opt-in label in the same secretTemplate block.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/tls-pki-canonical-name-and-ownerref branch from 1366c24 to a30f354 Compare August 31, 2026 10:40
…truction

The trust anchor was named <release>-tenant-ca on the grounds that no
operator the platform ships claims it. That is false. It collides across
releases: for an application foo the projection is postgres-foo-tenant-ca,
and for a sibling application foo-tenant CloudNativePG's own CA secret is
postgres-foo-tenant plus -ca, which is the same string. Both names are
legal and several Postgres instances in one namespace is ordinary.

The collision has a direction no guard can refuse. When the projection is
written first, CloudNativePG rejects the key-free secret with 'missing
ca.key secret data' and the sibling's PKI never completes, so that
application never starts, blaming a secret its owner never created. The
controller that wrote first was within its rights, so there is nothing to
refuse; and before this contract existed both applications worked.

The name is now <release>.tenant-ca, which is disjoint by character class
rather than by survey: application names are DNS-1035 labels and cannot
contain a dot, release prefixes are dot-free, and secret names are
DNS-1123 subdomains where a dot is legal. No prefix, application name and
operator suffix can therefore produce a dotted name, for any operator,
now or later.

This is the second time the survey argument failed — the previous name was
rejected because Percona claims it. Recording the reasoning so the third
name is not chosen the same way.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/tls-pki-canonical-name-and-ownerref branch from a30f354 to f8cc231 Compare August 31, 2026 11:01
The sentinel-based §5 rework predates the extraction controller's
actual merge and drifted from it on three points: the redis row and
its §5(a) framing still claimed a key-free self-published CA in
present tense, although the forked operator that would emit it is
still an open pull request; the content guard described a
private-key-header check where the merged controller instead decodes
each PEM block and rebuilds the chain only from blocks that parse as
an x509 certificate; and three places promised a
ValidatingAdmissionPolicy pinning sentinel writes to helm-controller,
which was never built — RBAC denying tenants any verb on
internal.cozystack.io is the actual control.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. What was wrong, what it now says, and what the code says

The claim checks out on both counts. Old, new, and ground truth:

Before this PR This PR cozystack/cozystack main today
Canonical trust anchor <release>-ca-cert <release>.tenant-ca <release>.tenant-ca
Owner of the projected Secret the application-instance CR, resolved from the app.kubernetes.io/instance label on the source the TenantProjection sentinel the TenantProjection sentinel
How the source is selected a label on the CA Secret, internal.cozystack.io/publish-ca-cert a chart-rendered TenantProjection naming sourceSecretName TenantProjection.spec.projections[].sourceSecretName

Evidence, all present on cozystack/cozystack main (merged in 37a91f98b feat(cozystack-controller): extract application CA into a key-free tenant Secret):

  • internal/controller/cacert/reconciler.go:194projectionSuffix = ".tenant-ca", and :353 builds target := release + projectionSuffix. The comment there is explicit that the dot is load-bearing ("Do not 'tidy' the dot into a dash"), for exactly the DNS-1035-vs-DNS-1123 reason this PR's new §4 paragraph gives.
  • internal/controller/cacert/reconciler.go:1103 sentinelOwnerRef() — the projection's single OwnerReference is internal.cozystack.io/v1alpha1, Kind=TenantProjection, Controller: true, BlockOwnerDeletion: false. §5(b) states all three, including blockOwnerDeletion false and the reason (a trust anchor must not delay app teardown).
  • internal/controller/cacert/reconciler.go:115TenantCALabel = "internal.cozystack.io/tenant-ca", matching §4/§5(c).
  • api/internalapi/v1alpha1/tenantprojection_types.go — group internal.cozystack.io, v1alpha1, kind TenantProjection, spec.projections[].{type,sourceSecretName,sourceKey}, type enum CACert, status.conditions with Ready/SourceNotFound. Every field name in the PR's YAML block is correct.
  • packages/apps/postgres/templates/tenant-projection.yaml — the first real consumer, rendering exactly this shape.

So the old owner claim was not merely different, it was unimplementable as written: the doc pointed the owner-reference at the application-instance CR, and the new text explains why that cannot work (apps.cozystack.io kinds are virtual, served by the aggregated apiserver over a stored HelmRelease, so there is no UID to reference). That reasoning is correct and is the most valuable part of this change.

The name history in §4 is also accurate, with one nuance worth calling out: <release>-ca-cert<release>-tenant-ca<release>.tenant-ca describes iterations within this PR's own commit series (a294a97 introduced -tenant-ca, f8cc231 replaced it with .tenant-ca); only -ca-cert was ever on main. The middle step is reasoning worth keeping — it is what produced the dot — but a reader may assume -tenant-ca was once published, and it was not.

2. Is amending in place the right vehicle?

Better than the pattern's usual shape, but it uses none of this repo's existing conventions.

The why is not lost — this PR deliberately preserves it in three places: §4's paragraph on the name revisions, §5's paragraph recording that the label design was replaced after the API-owner review in cozystack/cozystack#3299, and an "Alternatives considered" entry keeping the superseded label mechanism with its flaws.

But the metadata block is untouched: Date: 2026-06-24, Status: Accepted, no revision note. This repo already has three conventions that would fit — Date: … revised <date> (used by database-horizontal-autoscaling and distributed-tracing), a Supersedes: line (compute-plane), and a decisions/ record (compute-plane/decisions/0001-*.md).

Worth flagging: design-proposals/decision-records/README.md, currently in Review, names this PR by number — "#40, #41 and #36 are the same shape: an implementation finding rewriting an accepted proposal, with the why left in the pull-request body." That is only half true of this one, and adding a revised 2026-07-16 to the Date: line plus a one-line pointer to cozystack/cozystack#3299 would make it not true at all, cheaply.

3. Coherence and completeness

No dangling <release>-ca-cert or <release>-tenant-ca references to the projection remain anywhere in this repository. Every surviving -ca-cert string names a different object on purpose — PSMDB's key-bearing <release>-ca-cert, Kafka's <release>-clients-ca-cert, the redis fork's output. Both mermaid diagrams were updated. Prose, the engine table, §6 ordering, User-facing changes, Upgrade/rollback, Security, Failure cases, Testing, Rollout and Alternatives were all swept consistently. external-database-exposure/README.md changes because it is a downstream consumer that named the same anchor in three spots (dependency bullet, diagram node, WS5 hook list); all three now say <release>.tenant-ca. That is the right reason and it is consistent.

The head commit says "reconcile the proposal with the merged controller," and four things did not get reconciled:

(a) cozystack/cozystack#2990 is merged, and the doc still says it is open — in three places. It merged 2026-07-15, and internal/controller/wildcardsecret/reconciler.go is on main. The doc still carries "Edge, open: cozystack/cozystack#2990", the caveat "paths attributed to an open PR (for example the wildcard-secret reconciler in PR cozystack/cozystack#2990) are not yet on main", and Rollout step 1's "still open". The §5(b) reference to that reconciler as a skeleton is now a reference to merged code, which is strictly better — worth saying so.

(b) The RBAC claim in §5(d) understates the merged grant. The doc says the controller "holds only get/list/watch on tenantprojections". The merged packages/system/cozystack-controller/templates/rbac.yaml also grants tenantprojections/status: get, update, patch — which §5(e) requires, since the controller writes Ready. The point being made (no create/delete; the chart owns creation) is right; the word "only" is what is wrong.

(c) Open questions is stale. It still calls TenantProjection a "working name" and says "the group, kind, and the spec.projections[].type vocabulary are not yet frozen", and asks about the namespace convention. All four are settled and shipped: group internal.cozystack.io, v1alpha1, kind TenantProjection (shortName tproj), type enum pinned to CACert, namespaced, CRD at packages/system/cozystack-controller/definitions/internal.cozystack.io_tenantprojections.yaml. In a change whose purpose is reconciling with merged code, leaving "not yet frozen" over a shipped CRD is the most visible remaining drift.

(d) The sentinel YAML example's metadata.name does not match the merged chart, and implies the name matters. The doc shows metadata.name: <release>; the merged postgres chart renders name: {{ .Release.Name }}-ca. More importantly the sentinel's own name is irrelevant to the output — the controller derives the release from the helm.toolkit.fluxcd.io/name label Flux stamps (reconciler.go:332), and a sentinel without that label gets Ready=False, Reason=NoRelease. Chart authors will copy this block, so either match the shipped example or add a half-sentence that the name is free-form and the Flux label is the load-bearing part.

Two smaller factual points:

  • The redis table row overstates what exists. | redis (open PR) | chart + cert-manager | <release>-ca-tls (chart cert-manager) | yes | …. On main, packages/apps/redis contains no TLS at all — a case-insensitive search for tls across that package returns nothing, and the templates directory has no cert-manager Certificate. So the "CA-bearing Secret today" and "PKI owner: chart + cert-manager" cells describe cozystack/cozystack#2729, not main. This PR fixed the row's conclusion (redis is not at the target) but carried the premise over unchanged. The rest of the row and the corrected not yet verdict are right, and the mongodb row's "the chart renders no TLS objects" is correct for main.
  • external-database-exposure now says "copying verbatim", which contradicts the other half of this PR. Its dependency bullet reads "copying verbatim from redis and kafka's own key-free CA object". But unified-tls-pki §5(b), rewritten here, says the controller "rebuild[s] the projected value only from blocks that validate as a certificate — never copying the input verbatim", and the merged certificateChainPEM() does exactly that re-encode. "Copying with nothing to strip" is the phrase used elsewhere and is the accurate one.

One optional completeness note. The merged controller carries design-level behaviour the proposal does not record: the SelectorsDigestAnnotation mechanism that forces re-admission when an ApplicationDefinition's spec.secrets change, and the revocation path it exists for — when a definition stops selecting the anchor, the tenant's access must be taken away, and a frozen tenantresource=true verdict would leave a tenant reading a withdrawn trust anchor. §5(c) glosses this as "(and to false should the Secret ever stop matching)", which is the conclusion without the hard part. Also unrecorded: the source-sits-at-the-canonical-name path (key-free → silent success; key-bearing → refuse loudly, CanonicalNameOccupied), and the MultipleCACertProjections / ReleaseContested guards. None of this is required of a design doc, but revocation is a design decision rather than an implementation detail.

Line-level

  • The prose is strong. §4's "collision-free by character class rather than by survey… That is a proof. 'No operator claims it today' is a guess with an expiry date, and it expired twice" is the best paragraph in the document, and it is the argument the code comment at reconciler.go:186 arrives at independently.
  • SHOUTING for emphasis (ENGINES, RELEASES, CHARACTER CLASS) appears four times in one paragraph. The surrounding document uses bold; one convention would read better.
  • Markdown and mermaid are clean — table column count unchanged, the dotted name inside the quoted mermaid node label is fine, no broken links, the YAML block is well-formed and fenced.
  • The §5 heading rename to "Delivery: a declared sentinel and one engine-agnostic controller" is good; the sub-part lettering runs (a)–(e) with no gaps.

Bottom line

The correction is accurate — the substance has my approval. <release>.tenant-ca and the sentinel owner-reference both match internal/controller/cacert/reconciler.go on cozystack/cozystack main exactly, as do the label, the CRD field names, the SourceNotFound reason, and blockOwnerDeletion: false. The reasoning added here (the dot as a structural collision proof; why the application-instance CR cannot be an owner) is correct and is the part worth having.

Requesting changes for one more pass on the four items above that the reconciliation commit missed — #2990 described as open when it merged 2026-07-15, the status subresource missing from the RBAC claim, Open questions calling a shipped CRD "not yet frozen", and the sentinel example's name — plus the two one-line fixes ("copying verbatim" in external-database-exposure, and the redis row's -ca-tls premise). All are mechanical; none touches the argument.


Reviewed with assistance from Claude Code; findings verified by hand against cozystack/cozystack main.

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.

3 participants