Repository navigation
Couragehong/feat/boot loop stack - #109
Closed
couragehong wants to merge 9 commits into
Closed
couragehong wants to merge 9 commits into
couragehong wants to merge 9 commits into
Conversation
The Vault GetAgentManifest manifest_json carries 'EncKey.json' as a string
that is already a pyenvector-compatible KeyEnvelope (provider_meta + entries
per third_party/evi/include/km/KeyEnvelope.hpp), produced by libevi's
evi_km_wrap_enc_key when keys were generated. The envector SDK loads it via
evi_km_unwrap_enc_key which expects that exact envelope shape.
The previous SaveKeys re-wrapped the bytes ('{"enc_key": "<base64>"}')
which would always fail the cgo unwrap downstream — boot would dial fine,
fetch the manifest, persist a corrupted file, then envector OpenKeysFromFile
would reject it and the daemon would never reach Active.
Renames SaveKeys → SaveEncKey to reflect the single-key scope (Vault no
longer ships EvalKey/SecKey to the plugin). Adds KeyDir(keyID) helper
returning the per-key directory path that envector's WithKeyPath expects
(parent of EncKey.json), so callers don't reconstruct the path inline.
Per spec/components/embedder.md §소켓 경로, the runed daemon socket lookup order is (1) env RUNE_EMBEDDER_SOCKET, (2) config.embedder.socket_path, (3) ~/.runed/embedding.sock default. Centralises that priority chain so both the boot loop and any test/diagnostic harness use the same resolution. ResolveSocketPath(configPath string) string — pass empty string when the config layer doesn't carry an explicit override; returns absolute path under $HOME for the default branch.
The Inject{Vault,Embedder,Envector} methods only set adapter clients on
services. Per-token Vault metadata (AgentID / AgentDEK / IndexName / KeyID)
also needs to reach CaptureService / RecallService / LifecycleService —
without it, capture's AES envelope sealing runs with a zero-value DEK and
recall / lifecycle diagnostics surface zero-value IndexName.
ApplyVaultBundle(*vault.Bundle) does the metadata propagation in one call:
- Capture gets AgentID, AgentDEK, IndexName
- Recall gets IndexName
- Lifecycle gets IndexName, KeyID, AgentDEK, EncKeyLoaded=len(EncKey)>0
Nil-bundle is a no-op for symmetry with other Inject* methods.
…ctor config Rewrites the boot loop to address five integration bugs in #104's first draft, plus a handful of hygiene fixes. Critical: 1. envector.ClientConfig.KeyDim is now set (DefaultKeyDim=1024 const matching Qwen3-Embedding-0.6B per spec). Without it, OpenKeysFromFile().validate() rejects the call with 'WithKeyDim required'. 2. envector.ClientConfig.KeyPath now points to the per-key directory (~/.rune/keys/<keyID>/) via keymanager.KeyDir, not its parent. envector resolves EncKey.json directly under WithKeyPath. 3. embedder.New now uses embedder.ResolveSocketPath("") instead of a TODO empty-string placeholder. 4. After Vault.GetAgentManifest, deps.ApplyVaultBundle(bundle) propagates AgentID / AgentDEK / IndexName / KeyID to the service structs (formerly missing → capture's AES envelope would seal with empty DEK). 5. keymanager.SaveEncKey replaces SaveKeys (matching the rename). Hygiene: - bootOnce helper isolates one boot attempt so partial-success cleanup becomes uniform: any failure after a NewClient succeeds closes the partial conns (vault/embedder/envector) before retrying. No more gRPC conn leak on retry. - RunBootLoop runs ctx.Err() at the top of each iteration so SIGTERM during sleep stops cleanly. - empty-config / config-load failures now record into m.lastError so diagnostics surfaces the cause; attempt counter increments uniformly. - 'persistent failure' log fires every 20 attempts based on attempts (was checked inline only on Vault path). - Added Manager.LastError() public reader for diagnostics tools. - BootAdapterInjector gains ApplyVaultBundle method for the metadata propagation step. Spec note (open follow-up): RunBootLoop returns on first Active. Re-init after dormant↔active transitions is the responsibility of service.LifecycleService.ReloadPipelines (which spawns a fresh RunBootLoop goroutine) — the boot loop itself does not loop forever after success, contrary to a literal reading of rune-mcp.md §부팅 시퀀스. Will revisit if reload behavior turns out wrong.
Adds 18 test cases / 3 packages:
internal/adapters/keymanager/keys_test.go (6 fn):
- WritesBytesVerbatim — file content byte-identical to input
(the regression that motivated this PR)
- EmptyIsNoop — nil / empty []byte both no-op, no dir created
- FilePerm0600 — written file is 0600
- DirPerm0700 — created keyDir is 0700
- KeyDir_ReturnsExpectedPath — $HOME/.rune/keys/<keyID>
- OverwritesExisting — repeat call replaces content
internal/adapters/embedder/socket_test.go (5 fn):
- EnvVarWins — RUNE_EMBEDDER_SOCKET trumps configPath
- ConfigUsedWhenEnvUnset — explicit config path used when env empty
- DefaultWhenNothingProvided — $HOME/.runed/embedding.sock fallback
- DefaultEndsInRunedDir — invariant on default suffix
- EmptyConfigStringFalsThrough — explicit empty string ≠ valid path
internal/mcp/apply_bundle_test.go (6 fn):
- PropagatesToCapture — AgentID / AgentDEK / IndexName set
- PropagatesToRecall — IndexName set
- PropagatesToLifecycle — IndexName / KeyID / AgentDEK / EncKeyLoaded
- EncKeyLoadedFalseWhenEmpty — len(EncKey)==0 → EncKeyLoaded=false
- NilBundleNoOp — nil bundle leaves services untouched
- NilServicesNoOp — nil-safe: empty Deps doesn't panic
All pass under $HOME redirection via t.Setenv (no real $HOME side
effects).
…hon parity)
Mirrors Python server.py _set_dormant_with_reason: when boot loop
encounters a terminal failure (config missing, vault creds missing, or
config.state already dormant), update ~/.rune/config.json with
state="dormant" + dormant_reason + dormant_since (RFC3339 UTC). The
next process spawn picks up the same state without re-attempting boot
until the user explicitly runs /rune:configure or /rune:reload_pipelines.
Adds:
- config.Save / SaveToPath — atomic-ish 0600 write (mirrors Python's
O_WRONLY|O_CREAT|O_TRUNC pattern).
- config.MarkDormant(reason) — load → check idempotency → update fields
→ save. Creates a fresh Config when config.json is missing (fresh
install path).
Wires three boot.go bootDormant branches:
- config.json missing → MarkDormant("not_configured")
- config.state already "dormant" → MarkDormant(refresh) — reaffirms
timestamp + reason on each spawn so
diagnostics shows the most recent
cause
- vault endpoint/token missing → MarkDormant("vault_unconfigured")
Vault transient failures (dial / GetAgentManifest) still retry in-memory
without touching config.json — those are recoverable network blips, not
user-actionable terminal states. Diverges from Python on this point
(Python = always disk-persist on Vault fail) per spec/components/rune-mcp.md
§부팅 시퀀스 retry policy.
Tests (9 fn / config_test): fresh-install create, existing-config field
preservation, idempotent same-reason, new-reason overwrite, 0600 perm,
DormantSince timestamp window, Save roundtrip, dir auto-create, error
prefix sanity.
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.
Summary
Validation
Cross-Agent Invariants
scripts/bootstrap-mcp.shremains the single source of truth for runtime prep (venv/deps/self-heal)codex mcp ...) are clearly separated from cross-agent/common instructionsSKILL.md,commands/rune/*.toml, andAGENT_INTEGRATION.mdstay consistent on boundariesNotes for Reviewers