feat(config): config as code — manifests, a mounted directory the API applies, export/apply - #634
Merged
Merged
Conversation
…apply in the CLI and UI
Config as code (docs/config-as-code.md): every Job, scheduled Task,
persistent agent, prompt, repo, MCP server, skill and connection can be a
YAML manifest (apiVersion optio/v1, kind, metadata.name = identity, spec),
with references by name and secrets by name or ${{SECRET_NAME}}, never values.
- A cluster mounts a directory of them (OPTIO_CONFIG_DIR; Helm configAsCode.*,
plus api.extraVolumes / extraVolumeMounts) that a worker reads every
interval and applies to one workspace: create / adopt / update (a managed
row edited in the UI is put back and reported as reverted) / replace /
prune, each manifest its own error. Tables config_sources, config_objects.
- One handler per kind (services/config/kinds), the engine in apply.ts, name
resolution in context.ts, reading a directory in files.ts; dry runs plan
without writing and register would-be rows so later manifests resolve.
- Managed rows carry managedBy in every list and detail response; the web
shows a Managed chip next to the Private one, a banner on detail and edit
pages with the YAML and Detach, Download YAML, and Settings → Config as
code (directory, last sync with per-file errors, Sync now, Preview, Export).
- GET /api/config/schema.json (public JSON Schema), status, source/sync,
apply, export[.yaml], objects/:id/detach. CLI: optio export [-o DIR],
optio apply -f, optio diff -f, optio schema.
- updateWork saves persistent agents too; createWork can skip a Job's first run.
Tests: unit (schema, inlining, compare, files, when mapping, CLI reader),
integration (apply: every kind, drift, replace, prune, adopt, errors, detach,
export round trip), pipeline e2e (OPTIO_CONFIG_DIR at boot over HTTP),
Playwright (Settings card, Managed chip). Swift/Kotlin types regenerated.
…values, one spec locator - The directory walker follows symlinks: kubelet lays a ConfigMap out as links into a ..data snapshot, so Dirent.isFile() alone saw an empty directory (the live check synced nothing). Covered in files.test.ts. - configAsCode.mountPath and intervalMs fall back to the chart defaults in the templates: an upgrade with --reuse-values doesn't pick up new defaults and rendered an empty mountPath. - The Playwright Settings assertion picks the first of the two count labels.
# Conflicts: # apps/web/src/app/connections/page.tsx # apps/web/src/app/jobs/[id]/page.tsx # apps/web/src/app/repos/[id]/page.tsx # apps/web/src/app/templates/page.tsx # apps/web/src/components/ui/README.md # apps/web/src/components/work-form/work-form.tsx
A key dropped from a Secret's stringData lingers in its data, so disabling configAsCode left the pod with the old directory and the sync running (seen on the local cluster). An explicit empty value turns it off.
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.
Config as code
Teams that keep infrastructure in a repository can keep Optio there too. Every Job, scheduled Task, persistent agent, prompt, repo, MCP server, skill and connection can be a YAML manifest; a cluster mounts a directory of them and keeps the workspace matching the files. Design and the decisions behind it:
docs/plans/config-as-code.md; behavior:docs/config-as-code.md.The format
Six kinds (
Work,Prompt,Repo,McpServer,Skill,Connection);metadata.nameis the identity; references are names (repos by URL), secrets by name or${{SECRET_NAME}}and never values (a credential written in the clear is refused; exports write the reference instead).*Filefields pull long prompts and skill directories from next to the manifest.GET /api/config/schema.jsonis the JSON Schema for editors and CI.The directory
OPTIO_CONFIG_DIR(HelmconfigAsCode.*: inlinefiles, an existingconfigMap, or anything mounted through the newapi.extraVolumes/extraVolumeMounts) turns it on for one workspace (OPTIO_CONFIG_WORKSPACEslug; default the oldest, or the no-workspace tenant when auth is disabled). A worker reads it everyOPTIO_CONFIG_INTERVALand applies: create / adopt / update / replace / prune (OPTIO_CONFIG_PRUNE, default on), each manifest its own error item. A managed row someone edits in the UI is put back on the next sync and reported as reverted (the decision: rows stay editable, the file wins). The apply is idempotent: a quiet tick writes nothing.What people see
managedByon every list and detail response of the six kinds; a Managed chip next to the Private chip in lists; a banner on detail and edit pages (the file, "edits here are put back at the next sync", YAML, and Detach for admins); Download YAML per resource.optio export [-o DIR],optio apply -f FILE|DIR [--dry-run],optio diff -f,optio schema. A CLI apply is a plain upsert: it manages nothing and never prunes.Code
apps/api/src/services/config/—apply.ts(the engine),kinds/*.ts(one handler per kind: desire / find / diff / create / update / remove / export),context.ts(name resolution),files.ts,source.ts,managed.ts,export.ts;schemas/config.ts,routes/config.ts,workers/config-sync-worker.ts; shared types and*Fileinlining inpackages/shared/src/config/.updateWorknow saves persistent agents too;createWorkcan skip a Job's first run. Migration1791970000_config_as_codeaddsconfig_sourcesandconfig_objects.Tests
*Fileinlining, compare, directory reading,whenmapping, CLI reader.config-apply.int.test.ts, 11): every kind created with names resolved, no-op re-apply, drift reverted, a changed file incl. trigger, replace on kind change, prune and prune-off, per-manifest errors, adopt, detach, export round trip.config-as-code.e2e.test.ts, 6): boot withOPTIO_CONFIG_DIR,managedBythrough the strict response schemas, revert via sync, public schema + CLI-style apply, export, detach.config-as-code.spec.ts): the Settings card and Sync now, the Managed chip on Prompts.Not in this PR
Git repositories polled by the API or CI-push sources (the tables keep a
kind); a manifest field for triggerparamMapping; webhook secrets by reference.