feat: managed variables in the state file#45
Open
vtkovapi wants to merge 1 commit into
Open
Conversation
Add a `variables` section to `.vapi-state.<env>.json` for centralizing
values that repeat across resources — callback URLs, a default model name,
shared prompt fragments. Resource files reference them with whole-value
`{{name}}` placeholders.
- Forward (push): `resolveVariables` replaces `{{name}}` with the managed
value, native type preserved, wired into `resolveReferences` BEFORE
resourceId→UUID resolution so a variable can yield a reference.
- Drift stays clean: `hashLocalResource` renders variables before hashing;
the platform side is already rendered, so both hash in one basis (no
phantom drift). `canonicalizeForHash` is intentionally unchanged.
- Pull preserves templates: `restoreVariablePlaceholders` re-inserts a
placeholder only where the local file already had it and the value still
matches (guided reverse → zero false positives), on both the main and
`--resolve=theirs` write paths.
- `extractReferencedIds` is variable-aware so dependency auto-creation,
orphan/delete protection, and ignored-reference validation see the real
resourceId behind a `{{placeholder}}`.
- Validation: undefined `{{name}}` is a blocking finding in push + validate.
- State plumbing: `variables` is exempted from the migration guard and the
`npm run migrate` slim-rewrite (or it would trip the legacy-format check
and be dropped); loaded via `normalizeVariables`. `serializeState`
preserves hand-authored object-value key order on save.
- Whole-value only (no in-string interpolation), per design.
Docs: docs/learnings/variables.md + index tables + improvements.md (#27).
Tests: variables / state-variables / resolver-variables (39 specs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ce9ce18 to
8f6df7d
Compare
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.
What
Adds support for managed variables in the gitops state file. Centralize values that repeat across resources — callback URLs, a default model name, shared prompt fragments, dynamically defined names — in one place, and reference them from any resource file with a
{{name}}placeholder.npm run pushsubstitutes the values;npm run pullrestores the placeholders; drift treats a templated file and its rendered platform resource as in sync.Design
Whole-value only (per the agreed scope): a placeholder substitutes only when the entire value is a single
{{name}}. This makes substitution a clean, type-preserving, losslessly-reversible operation, so variables slot into the same symmetric model the engine already uses for tool/assistant/credential references — rather than fighting drift detection.resolveVariablesruns insideresolveReferencesbefore resourceId→UUID resolution, so a variable can even yield a reference (toolIds: ["{{tool_ref}}"]).hashLocalResourcerenders variables before hashing; the platform side is already rendered, so both hash in one basis. No phantom drift, all value types, zero reverse-substitution false positives.canonicalizeForHashis intentionally unchanged.restoreVariablePlaceholdersre-inserts a placeholder only where the local file already had it and the value still matches (guided reverse). A field changed on the dashboard becomes a literal + the dashboard wins.extractReferencedIdsresolves variables too, so dependency auto-creation, orphan/delete protection, and ignored-reference validation all see the real resourceId behind a placeholder.{{name}}is a blocking finding inpushandvalidate.variablesis exempted from the migration guard and thenpm run migrateslim-rewrite (otherwise it trips the legacy-format check / gets dropped).serializeStatekeeps hand-authored object-value key order byte-stable across saves.Out of scope for v1: in-string interpolation,
${ENV_VAR}expansion, secrets in the committed state file (documented).Process
Built via brainstorming → design → TDD. After implementation, ran a multi-agent adversarial review (16 agents, 4 dimensions, per-finding verification): 5 findings confirmed, 7 rejected. All 5 addressed in this PR — the one HIGH (
extractReferencedIdswas not variable-aware, breaking delete-protection and dependency auto-creation) fixed with a regression test; two LOW fixed in code; two niche LOW edges documented.Testing
tests/variables.test.ts,tests/state-variables.test.ts,tests/resolver-variables.test.ts(forward/reverse substitution, type preservation, round-trip identity, migration-guard exemption,migratepreservation, serializer key-order, reference composition, undefined-var validation, nullish-map tolerance).tsc --noEmitclean,biome checkclean.tsx-subprocess tests unrelated to this change).variables→ every code path is a no-op).Docs
docs/learnings/variables.md(new) + the three index tables (README.md,AGENTS.md,CLAUDE.md) + animprovements.mdentry (#27) recording the migration-seam footgun this surfaced.🤖 Generated with Claude Code