๐ Environment ployz-pr-1417 in Ployz Dashboard has no services deployed.
feat(store): store Configs with CLI and SDK access#1417
Summary
Store named Configs as Environment-owned folders of text files. Services attach Configs by stable identity. CLI and SDK commands support file content, permissions, references, branches, sync, and discard.
| 1 | flowchart LR |
| 2 | Environment --> Config |
| 3 | Config --> Files[Text, mode, UID, GID] |
| 4 | Service --> Mount[Config mount] |
| 5 | Mount --> Config |
This layer stores Configs. C3 supplies runtime files, and C5 and C6 supply the dashboard workflow and user guide.
Evidence
C2 passes 71 SQLite semantic tests independently of later layers. The renamed-mount regression checks that discard restores both Working State and Saved State. The integrated core gate passes 2,251 tests, and the full PostgreSQL Store gate passes 252 tests after rebuilding a stale cached artifact without source changes.
Merge Danger
Door: One-way once Config state is stored. Old Store binaries cannot parse the new authored fields.
Blast Radius: Configuration.
New binaries still read old documents. Mount validation also checks Volume path aliases. A pre-existing Service named configs must be renamed before path-based settings work again.
Checks
Codex Review Summary
This comment shows the latest Codex review activity on this pull request.
| Review | Status | Commit | Review trigger |
|---|---|---|---|
| ๐ Code Review | โ Completed 2026-10-06T04:28:48.836219Z | 9bf5255 | New commits |
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review" or "@codex security review".
Codex reacts with ๐ while any review is running, comments if it has suggestions, and reacts with ๐ once all reviews finish with no findings.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01a5826a8a
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -569,6 +579,22 @@ validated_string_newtype!( | |
| 571 | 581 | ); | |
| 582 | + | validated_string_newtype!( | |
| 583 | + | /// A Config's name, unique in its Environment: a lowercase DNS label. | |
| 584 | + | ConfigName, | |
P1 Badge Define Config in the domain glossary
These public types introduce Config as a new capitalized domain concept and assert that it is owned by an Environment, but core/CONTEXT.md currently defines Environment content only in terms of Services and Volumes and gives Config no meaning or lifecycle. Define Config and its relationship to Environment in the glossary before establishing these shared contract types, so later storage, CLI, and deployment work does not build on conflicting semantics.
AGENTS.md reference: AGENTS.md:L7-L7
Useful? React with ๐ย / ๐.
Fixed in 8e4efa167. core/CONTEXT.md now defines Config and Config Mount, and says how each relates to an Environment.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0280a17b69
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -248,6 +415,46 @@ pub fn parse_environment_intent(value: Value) -> Result<SavedEnvironmentIntent, | |
| 434 | + | })?; | |
| 435 | + | let dir = attachment.mount_dir.as_str().trim_end_matches('/'); | |
| 436 | + | for name in config.files.keys() { | |
| 437 | + | if !paths.insert(format!("{dir}/{name}")) { | |
P2 Badge Compare canonical effective mount targets
This collision check only canonicalizes trailing slashes and only collects Config file paths. Consequently, two Config mounts such as /etc and /etc/. containing the same filename are accepted even though they resolve to the same Linux target; likewise, a Volume mounted at /etc/app.yml is accepted alongside file app.yml from a Config mounted at /etc. This permits invalid Working State that will produce duplicate effective mount destinations, so build one canonical target set containing both expanded Config files and Volume mounts before admitting the document.
Useful? React with ๐ย / ๐.
Fixed. /etc/.-style aliases are refused by 9e87cb8ab, which accepts only canonical mount directories. A Volume mounted at or under a Config file's path is refused by 9194c1316, which has tests in environment_intent.rs.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5461d195f6
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -0,0 +1,194 @@ | |
| 156 | + | .applied | |
| 157 | + | .configs | |
| 158 | + | .iter() | |
| 159 | + | .any(|applied| applied.resource_id == node.resource_id), | |
P1 Badge Advance Config nodes after successful deployments
After any successful deployment, this predicate still evaluates against an empty Config collection: deployments freeze only TargetNode::Service and TargetNode::Volume, and deployment::lifecycle::advance consequently inserts only Service and Volume rows into config_applied. Therefore Config listings remain deployed: false, reviews continue treating deployed Configs as new, and a staged deletion disappears from the listing instead of being compared with its deployed version. Configs need to be included in Applied State when the deployment that carries them advances.
Useful? React with ๐ย / ๐.
Intended for this PR. Configs don't reach Applied State until they deploy, which is the next PR in the stack. Until then config ls shows every Config as a create. The PR body lists this under Scope.
| โฆ | โฆ | @@ -156,7 +156,7 @@ pub(super) fn lower( | |
| 157 | 157 | }, | |
| 158 | 158 | )), | |
| 159 | - | CompiledNodeConfig::Volume(_) => None, | |
| 159 | + | CompiledNodeConfig::Volume(_) | CompiledNodeConfig::Config(_) => None, | |
P1 Badge Preserve Config snapshots during deployment lowering
When a Service mounts a Config, this arm discards the only snapshot containing the Config's files. The resulting ServiceConfig mount metadata is also ignored by lower_deployment, which destructures only settings and mounts and builds only a volume graph, so the Deploy Intent contains neither ConfigSpecs nor Config mounts. Such a deployment can report success while the container receives none of the configured files; convert the Config snapshots and Service attachment metadata into the runtime config graph instead of dropping them.
Useful? React with ๐ย / ๐.
Intended for this PR. Configs are stored here but don't deploy yet. history::as_deployed strips Config mounts from what a Deployment runs, so Applied State never points at a Config it doesn't carry. Lowering Configs into the runtime config graph is the next PR in the stack.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 634507ee1f
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -90,3 +90,98 @@ fn authored_documents_reject_derived_service_fields() { | |
| 149 | + | assert_eq!( | |
| 150 | + | config["config"]["files"]["config.yml"]["content"], | |
| 151 | + | "host: ${{ web.HOST }}" | |
| 152 | + | ); | |
P1 Badge Align the Config snapshot assertion with its wire shape
The new configs_compile_with_references_written_by_service_name test fails unconditionally: the serialized value is a ValuePart array, while this assertion expects rendered text. ConfigNodeConfig and the generated SDK contract both currently declare each file as SavedConfigFile, whose content is Array<ValuePart>. Update the assertion to validate that structured representation, or change the implementation and generated contract consistently if compiled snapshots are intended to contain text, so this test does not leave the suite red.
Useful? React with ๐ย / ๐.
Stale. The test was rewritten as configs_compile_with_references_kept_by_service_lineage, which asserts the ValuePart array, and it passes.
| โฆ | โฆ | @@ -131,21 +163,33 @@ pub(crate) enum Target { | |
| 187 | + | return Err(error::invalid( | |
| 188 | + | "A Config has no Settings: address it as configs.CONFIG", | |
| 189 | + | json!({ "example": "configs.sentry" }), | |
| 190 | + | )); | |
P2 Badge Allow restoring individual Config changes
When a Config name or file is changed alongside another file, the review builds paths such as configs.NAME.name and configs.NAME.files.PATH, then uses this parser to determine can_restore. Rejecting every Config path containing another dot marks all those rows non-restorable, so ployz discard can only reset the whole Config and unintentionally discard unrelated edits. Parse the Config's name/file subpaths and route them to field-level restoration.
Useful? React with ๐ย / ๐.
Not in this PR. Every Config is a create until Configs deploy, so a file or name row has no baseline to restore, and configs.NAME is the only meaningful discard. Per-file restore needs Applied Configs, so it ships with deployment in the next PR.
| โฆ | โฆ | @@ -0,0 +1,355 @@ | |
| 26 | + | .arg_required_else_help(true) | |
| 27 | + | .subcommand( | |
| 28 | + | store::scoped(Command::new("create").about("Create a Config; staged until you deploy")) | |
| 29 | + | .arg(positional("name", true).help("Config name, unique in the Environment")) | |
P1 Badge Add user documentation for the Config commands
This introduces the user-facing ployz config command family, including creation, file management, mounting, inspection, and deletion, but the commit changes no file under docs/user/; the current user documentation therefore does not explain the new workflow, file constraints, reference syntax, or mount semantics. Add the shipped behavior to the relevant guide and documentation navigation.
AGENTS.md reference: AGENTS.md:L6-L6
Useful? React with ๐ย / ๐.
Deferred by plan. User docs for Configs (docs/user/services/configs.md) ship in the stack PR that makes Configs deploy and appear in the dashboard. Documenting a command whose files never reach a container would describe behavior users can't use yet.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45ced1465e
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -131,21 +163,33 @@ pub(crate) enum Target { | |
| 182 | + | if path == "configs" { | |
| 183 | + | return Err(name_a_config()); | |
| 184 | + | } | |
| 185 | + | if let Some(config) = path.strip_prefix("configs.") { | |
P1 Badge Keep legacy configs Services addressable
On an upgraded store containing a Service named configsโvalid before this commit because only volumes was reservedโevery path such as configs.replicas is now captured as the Config namespace, while the bare configs path is rejected above. Settings queries and edits, discard, and other path-based operations can therefore no longer address that persisted Service; preserve a legacy/escaped resolution or migrate these names before reserving the namespace.
AGENTS.md reference: AGENTS.md:L5-L5
Useful? React with ๐ย / ๐.
Accepted tradeoff, the same one taken when volumes was reserved. A Service named configs can still be renamed with ployz service rename, because RenameService takes a Service name, not a setting path. After that, path addressing works again.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0804616736
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -126,6 +126,20 @@ fn service() -> Value { | |
| 127 | 127 | }), | |
| 128 | 128 | ); | |
| 129 | + | properties.insert( | |
| 130 | + | "configs".to_owned(), | |
P2 Badge Bump the catalog version for the new Config setting
Adding the published SERVICE.configs.CONFIG path changes the catalog schema, but CATALOG_VERSION remains 1 despite this file's requirement to bump it whenever a published path, type, or meaning changes. Consumers using x-ployz-version therefore cannot distinguish the old catalog from one containing Config mounts; increment the version alongside this schema addition.
Useful? React with ๐ย / ๐.
Not bumping. The version marks a change to an existing published path, type or meaning, and adding a path changes none of them. Commit 98a0e6a42 added the healthcheck paths and kept version 1 for the same reason. Existing consumers read the catalog exactly as before.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9194c1316b
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -126,6 +126,20 @@ fn service() -> Value { | |
| 130 | + | "configs".to_owned(), | |
| 131 | + | json!({ | |
| 132 | + | "title": "Config mounts", | |
| 133 | + | "description": "Where the Service mounts each Config, by Config name, each at SERVICE.configs.CONFIG. Create a Config with `ployz config create`. Unset unmounts it: the Config and its files stay.", | |
P2 Badge Point catalog users to config add
This catalog text tells users to run ployz config create, but the command tree introduced by this commit exposes ployz config add and has no create subcommand. Anyone following schema or explain output reaches an unknown command, so the description should use the actual CLI spelling.
Useful? React with ๐ย / ๐.
Fixed in 6c332a4a1. The catalog now says ployz config add.
| โฆ | โฆ | @@ -149,9 +163,24 @@ fn mount() -> Value { | |
| 168 | + | "title": "Mount directory", | |
| 169 | + | "description": "The absolute directory the Config's files appear in, read-only, in the Service's containers. Unset unmounts it: the Config and its files stay.", | |
| 170 | + | "type": "string", | |
| 171 | + | "pattern": "^/", | |
P2 Badge Match the Config mount schema to server validation
The published schema accepts any string beginning with /, including /, /etc/app/, /etc//app, and /etc/./app, while ConfigAttachment::is_canonical_dir rejects all of these. Schema-driven forms and SDK clients can therefore approve values that the same Config Store later rejects with invalid_argument; encode the canonical-directory constraints in this schema.
Useful? React with ๐ย / ๐.
Fixed in 6c332a4a1. The schema pattern now refuses /, empty segments, . and .. segments, and a trailing /. A catalog test checks the pattern against ConfigAttachment::is_canonical_dir over the same sample set.
๐ก Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c332a4a11
โน๏ธ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with ๐.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| โฆ | โฆ | @@ -230,6 +417,43 @@ pub fn parse_environment_intent(value: Value) -> Result<SavedEnvironmentIntent, | |
| 446 | + | if service.config_attachments.iter().any(|config| { | |
| 447 | + | service.volume_attachments.iter().any(|volume| { | |
| 448 | + | volume.mount_path.as_str().trim_end_matches('/') == config.mount_dir.as_str() | |
| 449 | + | }) | |
P2 Badge Canonicalize Volume targets before collision checks
Fresh evidence after the canonical-Config fix: volume::attach still accepts any absolute ContainerPath, but this comparison normalizes only trailing slashes. A Service can therefore mount a Volume at /etc//sentry or /etc/./sentry and a Config at /etc/sentry; both this check and mounted_files accept the document even though the paths resolve to the same container location, violating the Config Mount invariant that each directory holds one mount. Canonicalize both target types before comparing them, including the Volume-versus-Config-file checks.
AGENTS.md reference: AGENTS.md:L7-L7
Useful? React with ๐ย / ๐.
Fixed in 9bf5255aa. Both Volume checks now compare the Volume path after resolving //, . and .., the way the runtime resolves a mount target. The test covers /etc//a, /etc/./a, /etc/x/../a and //etc/..
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
Added 27 commits
- feat(core): add ConfigName and ConfigFileName1728d03
- test: Config store, branch copy, sync rows and old-state parse1c5d784
- sdk: regenerate payloads284d4aa
- wip(store): Config file refs in rules problems, catalog configs property2f8ed54
- style: rustfmt the Volume target check305100b
- perf(core): find a template's last }} once3e99b44
- fix(store): an exact Config file name picks only that file4637900
- wip(store): config_item module, commands, queries and match-arm sweep4687391
- docs+sdk: Config and Config Mount in CONTEXT, regenerated SDK, node_configs smoke485dba2
- fix(store): a Deployment runs Services without Config Mounts until Configs deploy538a14a
- feat(cli): ployz config; fix review of compiled configs family5d16359
- fix(core): compare Config files without indexing6193f95
- fix(cli): config output goes through ui tables, fields and stream6c05c69
- fix(cli): config errors use the usage exit Failure now carries7547b37
- refactor: trim Config store code and comments758bc55
- fix: canonical Config mount directories; a Config can't hold a file and a folder of one name7d855d7
- fix: ployz config verbs match the house CLI; dashboard refreshes Config views8a8c344
- fix(cli): binary stdin gets the binary-file error90fbc34
- fix(store): catalog names config add and matches canonical mount dirsa269f4c
- wip(store): config mounts in review, discard and settingsa5bb5be
- wip(store): Config refs count live, Own Copy brings Configs, land introduces thema669528
- fix(core): a Volume can't mount where a Config file sitsac332dd
- wip(core): thread Config through environment intent and syncbc7a29b
- fix(store): discarding a renamed Config's mount restores it by IDddeef6c
- fix: clippy clean under -D warningse109976
- fix(core): compare Volume targets with Config mounts after resolving . and ..fa04f93
- fix(store): discard renamed mounts by one resolved resource identity04322ea
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.
Added 2 commits
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.