Add the consumed-file carve-out plan for archetypes#21 #27

Merged
vnprc merged 12 commits from agent/consumed-file-carve-out-plan into master 2026-07-31 06:34:31 +01:00
Member

Adds the dev plan for carving each consumed secret file out of the secrets input, so a dev machine's runtime closure holds the files it uses instead of every ciphertext in the deployment, plus the review prompt that drove it. Plan text only — no framework code changes here. Three review passes have run and the plan text has converged; the implementation PR against allod/archetypes follows and carries the closing keyword.

The plan resolves the two questions allod/archetypes#21 left open — carve in the consuming modules rather than adding an identity-template export, and leave the allod-tools input out — and corrects one claim in that issue that would have broken the build: the issue's validation says credential-profiles already accepts a carved path, which is true of the Forgejo matcher and false of the GitHub matcher that guards the very module being changed.

Review

Three passes, rotated models, per dev-plans.md:

Pass Model Effort Findings Origin
0 claude-opus-5 plan authored
1 gpt-5.6-sol xhigh 1 BLOCKER, 4 GAP all original-plan
2 claude-fable-5 max 2 BLOCKER, 3 GAP all review-introduced
3 gpt-5.6-sol xhigh 3 BLOCKER, 2 GAP all review-introduced

Stopped on the first condition in the review prompt: review-introduced findings outnumbered original-plan findings for two consecutive passes. The second condition is not met, because pass 3 still found blockers — all of them in the acceptance-test shell script the reviews themselves wrote. Scope, interface contracts and risk have been stable since pass 1.

Two findings reversed the author's claims and were each re-verified empirically on Nix 2.31.5 rather than accepted on assertion. ./secrets + "/<name>.age" is a path value that interpolates to an individual store file, so the two token-file sites the original plan added to scope were never uncarved. And dev-home-shared.nix:25 pads only the build closure, not the runtime closure the plan measures — pass 1 cut the allod-tools scope for a reason that was itself a build/runtime conflation, which pass 2 corrected while leaving the cut standing on its other ground.

Risk

R0. Plan and review-prompt text; no runtime, generated, or operational behavior changes. The residual risk of the work it describes is assessed in the plan as R3, with the argument against R4 recorded and attacked across two passes.

Validation

Acceptance-test block parses (bash -n) and its fail-open paths were probed. Full Nix execution belongs to implementation, which the plan gates on a disposable machine before any machine the owner works from.

Refs allod/archetypes#21

Adds the dev plan for carving each consumed secret file out of the secrets input, so a dev machine's runtime closure holds the files it uses instead of every ciphertext in the deployment, plus the review prompt that drove it. Plan text only — no framework code changes here. Three review passes have run and the plan text has converged; the implementation PR against `allod/archetypes` follows and carries the closing keyword. The plan resolves the two questions `allod/archetypes#21` left open — carve in the consuming modules rather than adding an identity-template export, and leave the `allod-tools` input out — and corrects one claim in that issue that would have broken the build: the issue's validation says `credential-profiles` already accepts a carved path, which is true of the Forgejo matcher and false of the GitHub matcher that guards the very module being changed. ## Review Three passes, rotated models, per `dev-plans.md`: | Pass | Model | Effort | Findings | Origin | |---|---|---|---|---| | 0 | `claude-opus-5` | — | plan authored | — | | 1 | `gpt-5.6-sol` | xhigh | 1 BLOCKER, 4 GAP | all original-plan | | 2 | `claude-fable-5` | max | 2 BLOCKER, 3 GAP | all review-introduced | | 3 | `gpt-5.6-sol` | xhigh | 3 BLOCKER, 2 GAP | all review-introduced | Stopped on the first condition in the review prompt: review-introduced findings outnumbered original-plan findings for two consecutive passes. The second condition is not met, because pass 3 still found blockers — all of them in the acceptance-test shell script the reviews themselves wrote. Scope, interface contracts and risk have been stable since pass 1. Two findings reversed the author's claims and were each re-verified empirically on Nix 2.31.5 rather than accepted on assertion. `./secrets + "/<name>.age"` is a path value that interpolates to an individual store file, so the two token-file sites the original plan added to scope were never uncarved. And `dev-home-shared.nix:25` pads only the build closure, not the runtime closure the plan measures — pass 1 cut the `allod-tools` scope for a reason that was itself a build/runtime conflation, which pass 2 corrected while leaving the cut standing on its other ground. ## Risk R0. Plan and review-prompt text; no runtime, generated, or operational behavior changes. The residual risk of the work it describes is assessed in the plan as R3, with the argument against R4 recorded and attacked across two passes. ## Validation Acceptance-test block parses (`bash -n`) and its fail-open paths were probed. Full Nix execution belongs to implementation, which the plan gates on a disposable machine before any machine the owner works from. Refs allod/archetypes#21
Plans the carve-out for allod/archetypes#21 and adds the review prompt
that drives its passes.

Model: claude-opus-5
Cut the allod-tools ride-along: dev-home-shared.nix also embeds the tools
root, so the closure claim was unreachable as scoped.

Drop the token-file sites: path concatenation already materialises an
individual store file, so those consumers were never uncarved.

Require exact expected-path equality for the GitHub matcher instead of a
basename suffix, with a synthetic non-empty fixture, since the public
target list never exercises the guard.

Query the realised toplevel output closure rather than .drv requisites.

Model: gpt-5.6-sol
Pass 1 findings (all original-plan defects, none review-introduced):

1. [BLOCKER] allod-tools closure claim unreachable. dev-home-shared.nix:25
   embeds the tools root, so carving agent-hooks.nix:11 alone could never
   remove it. Cut from scope. Commit d52c40a.
2. [GAP] Token-file premise false. ./secrets + "/name.age" is a Nix path
   already materialised as an individual store file, so those two sites were
   never uncarved. Scope reduced. Commit d52c40a.
3. [GAP] Basename suffix matcher accepted a wrong same-named secret, and the
   public target list is empty so the guard was never exercised. Now exact
   expected-path equality plus a synthetic non-empty fixture. Commit d52c40a.
4. [GAP] .drv requisites measures the build-time closure, not the runtime
   system closure. Now builds the toplevel and queries its output. Commit d52c40a.
5. [GAP] Risk argument conflated exposure with decryptability and omitted
   deploy-pin rollback. Commit d52c40a.

SIMPLIFY sweep: considered the allod-tools carve, the token-file edits, and
the redundant per-machine evaluation test. Cut the first two as scope that
did not belong; kept the evaluation test as the only bounded-memory guard.

Next pass: full stability review of the repaired plan at d52c40a, not a
scoped diff, since the repair rewrote scope, contracts, and tests. Use a
model other than the fix author; claude-fable-5 recommended.

Model: gpt-5.6-sol
toString on the token files' path values names the child inside the
source root: run verbatim against the composed allod-dev, test 6 reports
/nix/store/<hash>-source/secrets/agent-pr-token.age and
.../forgejo-https-token-allod-dev.age, hits the /nix/store/*/* case, and
exits 1 on the happy path — while the plan simultaneously forbids
wrapping those sites in a second builtins.path. Interpolation is the
coercion agenix performs when it embeds the file in the activation
script: verified on Nix 2.31.5, it materialises the token path values as
store-root files while an uncarved "${secrets}/..." string keeps its
in-root spelling, so the projection keeps its discriminating power and
stops contradicting the out-of-scope contract. Record the
toString-performs-no-copy caveat in the token-file contract.

Model: claude-fable-5
The set -euo pipefail block never performed the mutation its comment
described: run top to bottom, the check passes unmutated and the if
branch unconditionally exits 1, so the script could not complete even
against a correct implementation. Script the sabotage — scratch-copy the
module, sed the protected-branches source back to the uncarved
interpolation, require the check build to fail, and restore from the
scratch copy rather than git checkout, which would discard uncommitted
implementation work. Also assert the failure names the sabotaged target,
which the comment demanded but nothing verified, so an eval error from a
malformed mutation cannot masquerade as the intended rejection.

Model: claude-fable-5
The cut stands, but its stated reason conflated the two closures this
plan is otherwise careful to separate. dev-home-shared.nix:25 embeds
${allod-tools}/pm inside a runCommandLocal builder and the readFile
consumers carry root context into derivation inputs — build-closure
retention only, which never lands on a machine. In the realised-output
closure the plan measures, the tools root is held solely by
agent-hooks.nix:11: home-manager's insertFileEntry symlinks a home.file
source whose executable bit matches (verified in files.nix at the locked
rev 079a3b5, with the tracked hook executable), and the symlink target
names the root. So carving that one site would remove the root from the
runtime closure; the true reason it stays out of scope is that the tools
input holds no ciphertext. Stating the wrong mechanics would send the
future tools change chasing a directory consumer that only pads the
build closure.

Model: claude-fable-5
Test 7 promised every configuration, one process each, but evaluated
only $M; loop over builtins.attrNames of the composed nixosConfigurations
(allod-dev, installer, nexus, privacy-1 on the public template) with the
candidate override so the bounded-memory sweep actually happens. Give
test 1's premise tripwire an error message: a silent grep exit 1 cannot
tell an already-merged baseline from a wrong premise, and this is the
stop-here signal the closing note leans on. Reword the test 2 repeat
note to say the public template composes exactly one dev machine, so the
loop instruction reads as the private composition's obligation rather
than a step this script appears to skip.

Model: claude-fable-5
The regression-check contract asked for a synthetic non-empty
credential/target input without saying where the ciphertext bytes come
from, and the obvious wrong answer — a runCommand-built fixture tree —
is import-from-derivation, because builtins.path reads its source during
evaluation. Record the working shape: github-credentials.nix takes no
config and uses no mkIf, so the fixture is direct function application;
the synthetic input is the real secrets input with a substituted lib,
pointed at an existing template ciphertext; and the same-basename
wrong-content mutation is a builtins.path rename of a different existing
ciphertext, so no fixture files are added at all.

Model: claude-fable-5
Pass 2 findings (full stability review of d52c40a; all five were
introduced by the pass-1 repair, none by the original plan):

1. [BLOCKER] Test 6 instrument wrong. toString on the un-carved token
   path values names a child of the secrets root, so the projection
   failed the happy path on every forge-access machine while the plan
   forbade changing those sites; coerce by interpolation, the coercion
   agenix performs — verified verbatim against the composed allod-dev.
   Commit e600c7d. Origin: pass 1 (d52c40a); the original block was
   consistent with its wider scope.
2. [BLOCKER] Test 5 sabotage not executable. The set -euo pipefail
   block described the mutation only in a comment, so run top to bottom
   it unconditionally exited 1; the sabotage is now scripted with a
   scratch-copy restore and a wrong-reason filter on the failure
   output. Commit 499af7f. Origin: pass 1 (d52c40a), which scripted the
   original prose instruction without the mutation step.
3. [GAP] allod-tools justification conflated closures.
   dev-home-shared.nix:25 embeds the tools root only in the build
   closure; in the runtime closure the plan measures, the root is held
   solely by agent-hooks.nix:11 (home-manager symlink, verified in
   files.nix at the locked rev), so the cut now rests on its true
   reason: the tools input holds no ciphertext. Commit 79030e5.
   Origin: pass 1 (d52c40a).
4. [GAP] Script coverage claims were comments. Test 7 promised every
   configuration but evaluated one — now a loop over the composed
   nixosConfigurations; test 1's premise tripwire gained an error
   message; test 2's repeat note now says the public template composes
   exactly one dev machine. Commit 80462ea. Origin: pass 1 (d52c40a).
5. [GAP] Synthetic GitHub fixture bytes unspecified. builtins.path
   reads its source at eval time, so a derivation-built fixture is
   import-from-derivation; the plan now pins direct function
   application, a substituted-lib synthetic input over existing
   template ciphertexts, and a builtins.path rename for the
   same-basename mutation. Commit b7b3407. Origin: pass 1 (d52c40a),
   which introduced the fixture requirement.

SIMPLIFY sweep: considered deleting test 6 (kept — the only
agent-runnable projection of the private composition's real generated
values), the test 1 baseline build (kept — the premise tripwire test 2
leans on), the fixture predicate (kept — it is what makes the fixture
exercise the real comparison), and the allowlist/denylist pair (must
not collapse). Cut instead: any checked-in fixture tree — the pinned
fixture shape adds no files.

Empirical grounding this pass, on Nix 2.31.5 at the locked revisions:
token-file path semantics, builtins.path on a string with context
(single-file result context, identical to the literal-path form),
both test 6 instruments against the composed allod-dev, the
SYSTEM/SECRETS_STORE/attrNames preamble, and home-manager's
symlink-vs-copy insertFileEntry.

Next pass: scoped diff review of e600c7d..b7b3407 plus this commit, on
top of the standing lenses. Recommended model: gpt-5.6-sol at xhigh —
cross-vendor from the pass-2 fix author; re-verify the empirical
claims independently rather than trusting the commit messages.

Model: claude-fable-5
The note still said "if it comes back zero", the mechanism of the old
grep -c count; test 1 now matches or errors, so describe the tripwire
firing and name both causes it distinguishes.

Model: claude-fable-5
Test 2 treated a failed requisites query as proof of absence; assign first,
then search. Test 5 matched any output mentioning the sabotaged source, so
an eval trace could pass as the intended rejection; require an exact
check-specific marker, and restore with cp -p so the mode survives. Test 7
could evaluate zero configurations and still succeed; use a checked
assignment and a while loop. The invalid-consumer fixture now inspects the
returned assertions directly instead of forcing age.secrets, which
dereferences the missing consumer first.

Model: gpt-5.6-sol
Pass 3 findings (3 BLOCKER, 2 GAP, all introduced by earlier review
repairs; zero original-plan defects):

1. [BLOCKER] Test 2 fail-open: a failed requisites query read as absence.
2. [BLOCKER] Test 5 wrong-reason match: an eval trace echoing the sabotaged
   source line satisfied the generic pattern.
3. [BLOCKER] Test 7 could run zero iterations after failed discovery and
   still exit clean.
4. [GAP] Test 5 restored contents but downgraded mode 0644 to 0600.
5. [GAP] The invalid-consumer fixture forced age.secrets, which throws on
   the missing consumer before the assertion can report it.
All repaired in 180e876.

SIMPLIFY sweep: no cut justified. The allowlist and denylist catch distinct
security failures, and tests 1 and 2 are premise tripwire and regression
proof respectively.

Stopping rule: the first condition is met. Review-introduced findings
outnumbered original-plan findings for two consecutive passes (pass 2: 5
versus 0; pass 3: 5 versus 0). The second condition is not met, since this
pass found blockers. Plan-text review stops here. Every finding in the last
two passes was in the acceptance-test script the reviews themselves wrote;
the plan's scope, contracts and risk have been stable since pass 1.

Remaining focus areas pass to implementation review: the check's shape
against the real checks structure, and whether the test surface can shrink
once run for real.

Model: gpt-5.6-sol
vnprc approved these changes 2026-07-31 06:34:17 +01:00
vnprc force-pushed agent/consumed-file-carve-out-plan from bdb1835ae2 to 912ae70e1d 2026-07-31 06:34:24 +01:00 Compare
vnprc merged commit 912ae70e1d into master 2026-07-31 06:34:31 +01:00
vnprc deleted branch agent/consumed-file-carve-out-plan 2026-07-31 06:34:32 +01:00
Sign in to join this conversation.
No description provided.