Repository navigation
feat(environment): product intent declares service-secrets generate and may-be-empty - #1149
Merged
Merged
Conversation
…nd may-be-empty An imported hosted environment had no supported producer for its secret policy: --product-intent accepted only exclude-services and the coordinate contract carries no service-secrets, so a generated key (and with it `deploy secrets --rotate`) could only be added by hand-editing imported data. The product intent may now declare, per environment, service-secrets.generate and service-secrets.may-be-empty: which of the product's own keys are minted and which hold no value are product decisions; secret-store, defaults and per-service mappings name the cell's store and stay the contract's. With --product-intent the intent is the only source of those two fields: a contract declaring one is refused, and an entry the intent drops is withdrawn on re-import (as #1140 does for registry mirrors). Each entry is checked against the secret configurations the environment's profile declares; an unknown service, configuration or key is refused by name. Decoding stays strict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gap
An environment's
service-secretsblock had no supported producer for an imported hosted environment.codefly environment import <env> --coordinate-contract … --product-intent …accepted onlyenvironments.<env>.exclude-servicesfrom the intent, and the coordinate contract (infra-baseobinctl coordinate-contract) carries nogenerate/may-be-empty. platform-obin's blue environment got its generators from a pre-release build that read the legacydelivery.intent.yaml; nobody could add a generated key such as{scope: service, configuration: clickhouse, keys: [CLICKHOUSE_PASSWORD], services: [tracing/evidence]}without hand-editing imported data. Without that,codefly deploy secrets --rotate tracing/evidence/clickhouse:CLICKHOUSE_PASSWORDis refused ("service-secrets.generate does not declare it"), so a leaked password cannot be rotated.Design
The product intent may declare, per environment:
Field ownership. I went through every
service-secretsfield:generate: product-owned. Which of the product's own configuration keys are minted at random is a decision about the product's configuration, and no cell producer can know it.may-be-empty: product-owned. Which of the product's keys hold no value in an environment is the same kind of decision, matched on the product's stored key.secret-store: contract-owned. It names the store the cell runs.defaultsandservices.<svc>.{remote-keys, secret-store, template, refresh-interval}: contract-owned. They say where and how the cell's store files each value.environments.ProductSecretPolicy(new,pkg/environments/product_secrets.go) holds the two product fields andProductSecretPolicyKeysnames them.Precedence
--product-intentis passed, a contract that also declaresservice-secrets.generateormay-be-emptyis refused ("declared by both … select one owner"), the same rule asexclude-services. Neither owner silently wins.service-secretsat all. This generalises fix(environment): re-import withdraws a registry mirror the contract no longer carries #1140'swithdrawContractOwnedAbsencesintowithdrawOwnedAbsences(…, owned, parentOptional). It is the same mechanism with a second owned set, not a second mechanism. Contract-owned keys still need a declared parent.--product-intent, nothing about service-secrets changes, so a producer-only refresh keeps the product's policy.Validation
KnownFields(true), andEnvironmentSecretGenerator's own unknown-key refusal. An unknown policy field (for examplesecret-storein the intent) is refused.EnvironmentSecretGenerator.validate, for every environment in the intent.environments.ValidateSecretPolicyloads the environment's configurations with core'sConfigurationInformationLocalReader.Load(env.Runtime()), the same readpkg/gitops' configured-endpoint check uses. The checks are:scope: workspace) must declare a secret configuration of that name, holding those keys.servicesneeds at least one composed service declaring it.may-be-emptykey must be a stored secret key of a declared configuration.Reused:
EnvironmentSecretGenerator.validate, core's configuration reader and key derivation (NameToKey,ServiceSecretConfigurationKeyFromUnique,WorkspaceSecretConfigurationPrefix), the existing withdrawal helper, and the exclusion import path. Not built: no new intent format, and no parallel configuration scan.Classification: fix, at the importer that owns turning authored product decisions into the environment block.
Tests
cmd/environment/product_intent_test.go:TestProductIntentImportsAndWithdrawsSecretPolicychecks four things:generateandmay-be-empty, and the contract'ssecret-storeandservices.remote-keysstay untouched.may-be-emptywithdraws it and keepsgenerate.service-secretswithdraws both, and the contract fields remain.TestProductIntentSecretPolicyRefusalsDoNotWritecovers each of these cases, refused by name with the workspace byte-identical: unknown service, undeclared configuration, undeclared key, a workspace group that does not exist, undeclared may-be-empty key, unknown generator field, unknown policy field, invalid scope, dual ownership, no secret store.TestProductIntentGeneratorWithoutServicesNeedsADeclaringServicecovers a generator with noservices.TestProductIntentImportPreservesPlacementAndSupportsClearing,…RefusalsDoNotWrite,…RefusesDualOwnership,…FlagDryRun) and the fix(environment): re-import withdraws a registry mirror the contract no longer carries #1140 withdrawal tests pass unchanged.Ran:
go test ./cmd/environment/... ./pkg/environments/... ./pkg/deploysecrets/... ./cmd,make lint(0 issues).Docs
--helplong text and flag text, regenerated intodocs/cli-reference.md.docs/commands.mdenvironment import section.docs/configuration-contract.md, with the example and the ownership argument.Not verified
delivery.environments.yamlor the blue contract, and I did not rundeploy secrets --rotateend to end after an import.materializeConfigurationProfilewrites it. On the first import of an environment with a newconfiguration-source, workspace groups that only the not-yet-materialised profile would carry are not visible to the check. Service configurations are unaffected..secret.yamldocument group has no enumerable keys, so a generator naming keys in one is refused. I did not confirm the render's key naming for document groups.🤖 Generated with Claude Code