Conversation
Accept the decision to have agentic-kit turn on, verify and explain opt-in ruflo components, with the task plan that implements it.
…n (ADR-0016 drift) Adds src/lib/claude-env-projection.mjs: reconcileClaudeComponentEnv projects machine-wide ruflo component keys into the user Claude settings and the governance key into a project's settings.local.json (only when a valid MCP policy exists), and reconcileMemoryPin gives the CLAUDE_FLOW_DB_PATH pin a receipt, adopting a legacy unreceipted pin once when it matches ak's computed value. pinProjectMemoryDbPath in setup.mjs now delegates to reconcileMemoryPin. Widens replaceableRufloRegistration in mcp.mjs to recognize the ak-owned ruflo-components env keys (Task 0's 2026-09-23 spike confirmed Claude Code passes the settings env block through to stdio MCP servers and hooks, so register()'s own desired.env is otherwise unchanged). Records the spike's findings in ADR-0058 §3 and the implementation status table.
Removes pruneIfEmptyEnvelope from claude-env-projection.mjs. It deleted
.claude/settings.local.json (or a machine ~/.claude/settings.json) whenever
the last owned key was retracted and the file happened to render as {} —
including a file that pre-existed before ak ever wrote to it, since per-key
receipts carry no record of "was this file absent before ak's first write".
Reproduced: pre-create settings.local.json as {}, let ak add
RUFLO_MCP_ENFORCE_POLICY, remove the policy file, reconcile again — the
pre-existing file was deleted. Violates ADR-0016 §4.
An emptied file now stays behind as {}, matching the Task 3 engine's and
AQE's existing behavior (owned-env-projection.mjs never deletes a file it
emptied). Updated the "deleting the policy file..." test to assert the key
is absent from env rather than that the file is gone, and added a
regression test that a pre-existing {} file survives an add-then-retract
cycle. Added a negative test in mcp-scopes.test.mjs: a canonical
user-scoped registration carrying a foreign env key alongside an ak key is
preserved (register() returns false, no remove call).
…hooks env baking
Task 5 fix round 1 (review + controller-widened scope):
- ak now OWNS RUFLO_MCP_ENFORCE_POLICY whenever governance is managed: it
sets the key on a valid policy and actively clears any inherited/stale
value otherwise, in the Codex launcher (ruflo-memory.mjs) and both
OpenCode templates (gateway configure(), hooks projectHookEnv()). Leaves
an inherited value untouched when governance is not managed.
- The templates' policy check now mirrors ruflo-components/policy.mjs's
readPolicy exactly (regular file, not a symlink, <=1MiB, plain JSON
object), plus a provenance check: only a policy file ak itself wrote
(renderPolicy's `_about: 'Managed by agentic-kit...'` stamp) counts, so a
foreign .harness/mcp-policy.json (e.g. from a MetaHarness-generated
project) can never silently cap ruflo's MCP server.
- Renamed the composed helper from applyManagedEnforcement to
resolveEnforcedEnvironment in both templates: verified empirically against
stock OpenCode 1.18.32 that a plugin file exporting a name starting with
"apply" is invoked by OpenCode's plugin loader as an additional plugin
factory (single context arg), which crashed and silently broke the whole
gateway's tool projection (opencode-stock-ruflo-gateway.test.mjs's compact
lazy-call acceptance test went from pass to fail without this rename).
- Closed the reported OpenCode-hooks gap: the deployed hooks artifact now
bakes the machine component env + AK_RUFLO_GOVERNANCE marker via a new
RUFLO_COMPONENT_ENV constant substitution (hooksDesiredText in
opencode-artifacts.mjs, mirroring gatewayDesiredText), wired through
deployPlugin/pluginStatus's new componentEnv option and
opencode-lifecycle.mjs's two call sites via opencode-core.mjs's newly
exported rufloComponentEnvFor(cfg). The governance decision inside
projectHookEnv is made against the merged (baked + caller) environment,
not raw process.env alone. The committed template's unsubstituted
RUFLO_COMPONENT_ENV stays {}, so opencode-hooks.test.mjs's existing
assertion is unchanged.
Surfaces every ruflo component in `ak status` (with --refresh to re-probe), adds an `ak sync` step that repairs them right after ruflo upgrades, adds the trust-manifest disclosure and setup results table, and releases every ak-owned setting on `ak uninstall`. Deviates from the task brief in two hermetic-safety fixes: encryptionAtRest (permanently "not-applied", ADR-0059 not yet implemented) is excluded from the fixable set so it never enters ak sync's real plan, and offlineKitConfig() now defaults rufloComponents off (matching its existing agentBrowser/ruvnetBrain precedent) so pre-existing real (non-dry) setup/ sync/uninstall tests stay hermetic instead of spawning real npm installs or mutating the developer's actual ruflo funnel state.
…ag, sync convergence Task 9 fix round 1 per review of 6d2e88e: 1. Add and export rufloProjectRoot(cwd) in apply.mjs: a ruflo project is a git root that ALSO has .claude-flow/ (ADR-0058), not merely any ancestor .git. reconcileRufloComponents now accepts an explicit projectRoot option (undefined -> rufloProjectRoot(cwd); null -> machine-scope only). setup.mjs's run_machine passes projectRoot: null explicitly so a dotfiles repo at/above $HOME can never receive a .harness/mcp-policy.json. The status section and the uninstall step now compute projectRoot via rufloProjectRoot too. 2. uninstall's stepRufloComponents now sets ctx.state.ownershipTeardownOk = false when reconcileClaudeComponentEnv or reconcileMemoryPin report ok === false, or when a policy removal throws -- matching every other uninstall step, so a purge can no longer delete kit.json while a ruflo-components receipt could not actually be released. 3. sync.mjs drops a ruflo-components row whose state is needs-ruflo from the plan under --no-upgrade (same treatment as 'versions'), keyed off the row's own message text rather than a new row field. The status section's LEVEL mapping now reports 'blocked' components as 'fail' (not 'warn'), so ak sync's post-heal convergence check counts them instead of silently reporting "converged" while a component stays broken. Retrofitted 3 existing ruflo-components-apply.test.mjs fixtures to also create .claude-flow/ (they were exercising project-scope behavior via a bare .git, which the stricter project definition no longer treats as a project). status-golden.json is unchanged (verified) -- the offline fixture has no managed/blocked component, so neither change alters its rows.
Document ADR-0058's managed ruflo components: the component/managed-value table and state-meanings table in MANAGED-TOOLS.md, the setup trust disclosure group and restart reminder in SETUP.md, the Overview > Runtime panel and About summary link in DASHBOARD.md, two Troubleshooting rows (governance lockout, stuck applied-not-verified), and one README feature line. Current-behaviour only, grounded in the merged catalogue, env, policy, snapshot, apply, status-section, and dashboard-client source on this branch.
Refresh ADR-0058's Implementation status table against the merged branch commits (catalogue through the dashboard panel); Status stays Accepted pending Task 11A's real-machine verification against ruflo 3.44.0 and filing the four upstream requests. Correct §7 and the Consequences line per Ruling 14: picker evidence is the `hooks route` probe's `embedder=` marker and `doctor -c typesafe`, not a per-picker routed-count stat — `routedByCounts` belongs to the model router (ADR-148), not the agent pickers this ADR manages. Note that the status section's FIXABLE set excludes encryptionAtRest (ADR-0059, not yet implemented) and that the hermetic test suite's default kit.json leaves every ruflo component unmanaged. Add ADR-0016's Updated note: the Claude memory pin is now receipt-owned and removed by `ak uninstall` (ADR-0058).
handleRufloComponents resolved project scope with paths.repoRoot (any git root) instead of rufloProjectRoot (git root AND .claude-flow/), unlike status/sections/ruflo-components.mjs's equivalent call. A git repo above cwd with no .claude-flow/ (e.g. a dotfiles repo at $HOME) could therefore receive project-scope policy reads it should never see. Adds a regression guard asserting the handler's source uses rufloProjectRoot, not repoRoot.
…ence
ruflo's funnel precedence is env > enterprise-policy > user-config >
project-config > package-default (v3/@claude-flow/cli/__tests__/funnel.test.ts).
ak's own `ruflo funnel disable` only ever lands as the user-tier
('user-config') source — apply.mjs's releaseFunnel already gates its undo
on the same /user/i test. classifyComponent's funnel state previously
called any enabled:false "active" regardless of decidedBy, claiming credit
for a disable something else (an enterprise policy, project config, env
var, or even the package default) produced. Now a funnel disabled by any
source other than ak's own reads as user-managed, with the deciding source
named in the meaning — off, but not an error and not ak's doing.
Corrects the shared test fixture's funnel decidedBy from the placeholder
'user' to the real 'user-config' value (confirmed against a live ruflo
3.44.0 capture), and adds coverage for all five precedence sources plus
parseFunnel's round-trip of each.
The citation for lookbackDays pointed at dashboard-server.mjs:1779; the line has since moved to 1824 (handleUsage's readIndex call). Caught by tests/kit/doc-citations.test.mjs.
Real captures against a disposable ruflo@3.44.0 install (Task 11A Steps 2 and 5): doctor-typesafe-installed-3.44.0.txt (the confirmed/enabled row, CLAUDE_FLOW_ROUTER_TYPESAFE=1, after `ak sync` installed the package — "v0.1.0 installed; enabled (hash embedder, uncalibrated)"), and funnel-status-disabled-3.44.0.txt/.json (the disabled state after `ak sync` ran `ruflo funnel disable`, decidedBy: user-config). Machine-local paths redacted to /Users/example/... per the existing fixture convention.
ak never writes CLAUDE_FLOW_ROUTER_* or RUFLO_INTELLIGENCE_MODE into the claude-flow registration (Claude inherits the settings env), so a registration carrying one is user-authored and must not be replaced (ADR-0016 §4). Revert the replaceable key set to AGENT_BROWSER_CONFIG only.
…andbox sandboxHome left XDG_STATE_HOME/XDG_DATA_HOME/XDG_CACHE_HOME/LOCALAPPDATA and CLAUDE_CONFIG_DIR/CODEX_HOME/HERMES_HOME inherited, so a shell that exports them to real paths let setup.run_project write the real evidence cache. Pin or delete each one, make assertSandboxed check the state base, turn ruflo components off in the routing-retirement fixture, and add a child-process tripwire that runs under a hostile environment.
The multi-key engine threw on the first user-set key, so one CLAUDE_FLOW_ROUTER_TYPESAFE=0 left MiniLM and the learning profile unwritten. Per-key conflicts are now recorded (plan.keys, plan.conflicts) and skipped; file-level errors still refuse the file, and the single-key AQE format keeps its refuse-on-conflict contract. A managed value the user deleted is restored rather than wedging the receipt, and the pending marker of a mixed add+remove plan keeps the released key's before-state.
Status and sync classified components from probe evidence alone, so an unwritten Claude settings file read as active, opt-outs never took effect and sync never planned the reconcile. The Claude projection is now authoritative first: per-key write -> not applied, a deleted ak value -> drifted (restored), a foreign or user-edited value -> user-managed, a file ak cannot touch -> blocked for each of its keys. Evidence decides only once the projection has converged. Also: a dropped typesafe package is not applied (sync reinstalls); opt-outs that still hold ak's env or funnel receipt are fixable and sync releases them (funnel: true re-enables ak's disable); governance turned off releases every receipted project; encryption at rest is an info 'not yet managed' row outside the active count; rows carry their state id and sync filters needs-ruflo on it; ensureFunnel treats an unreadable status as nothing to do and leaves an env/enterprise-policy decision alone; the dashboard route mirrors status when ruflo is absent; turn-credit evidence without a probe result has no line; the governance unknown reason says ruflo <= 3.44.0 does not enforce the policy on stdio launches.
Uninstall removed policy files in every receipted project but released the project env key, memory pin and their receipt sidecars only for the cwd project. Projects that receive a project env or pin receipt are now recorded, and teardown (moved to ruflo-components/teardown.mjs) walks policies and projects together. A retained user-edited receipt or a failed typesafe removal under --purge now keeps kit.json and fails the run.
…catalogue
opencode-core and the Codex launcher hard-coded '3.42.0'; use
componentById('mcpGovernance').minRuflo like apply.mjs does.
…olicy on stdio The policy enforcer lives only in MCPServerManager, which neither stdio entry point (bin/mcp-server.js, 'ruflo mcp start') reaches. The catalogue, trust disclosure and code comments claimed audit and caps were in force; they now say ak writes the policy ready for when ruflo wires enforcement (upstream request 6). The funnel and encryption change text no longer promise what sync does not do.
run_machine printed raw result lines with no table and no restart reminder (ADR-0058 §3/§7). Both setup paths and sync now share one report: results table, failed steps, and the reminder after a change. Also pin the unreferenced 3.44.0 installed-typesafe and disabled-funnel fixtures in parser and confirmer tests.
…aviour Governance: ruflo <= 3.44.0 does not enforce the policy on stdio MCP launches, so the component stays unknown (upstream request 6, with the evidence table, added to ADR-0058 §8). TROUBLESHOOTING's lockout row becomes 'governance stays unknown' and no longer says status removes the variable. ADR-0058 records the real-machine outcomes, marks the status/uninstall round trip and dashboard browser check as not verified, and describes projection-first classification, per-key conflict preservation, opt-out release and multi-project uninstall. Encryption at rest is 'not yet managed' and outside the active count.
…-applied, so sync re-disables it Found in the Docker first-use round trip against ruflo 3.45.0: after funnel:true (release) then funnel:false, status said applied-unverified (restart), which carries no fix, so ak sync never ran ruflo funnel disable again.
This branch has not been deployed
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
Implements ADR-0058. ak now manages a set of ruflo components for the user, based on the installed ruflo version. It shows each one, with what its state means, in
ak setup,ak statusand the dashboard.@ruvector/typesafe)CLAUDE_FLOW_ROUTER_TYPESAFE=1)CLAUDE_FLOW_ROUTER_EMBEDDER=minilm).harness/mcp-policy.json(audit log, 120 calls/60 s); env only in ruflo projects with a valid ak policybalanced; all five profiles reportedruflo funnel disable, released on uninstall/opt-out)falseinkit.json. For the funnel, setfunnel: trueto hand it back.doctor,funnel status --json, thehooks routeembedder marker, and the MCP audit log.drifted: a value ak set was removed. The nextak syncrestores it.user-managed: the user changed or set the value. ak leaves it alone.Important finding: governance is written but not enforced by ruflo ≤ 3.44.0
In ruflo 3.44.0,
RUFLO_MCP_ENFORCE_POLICY/.harness/mcp-policy.jsonenforcement lives only indist/src/mcp-server.js(MCPServerManager). Neither real stdio entry point reaches it:bin/mcp-server.js, which ak registers, imports onlydist/src/mcp-client.js.ruflo mcp startnon-TTY fast path inbin/cli.jshas its owntools/callhandler.Tool calls succeed even with the policy file removed, and nothing reaches the audit log. ak still writes the policy, so it is ready when ruflo wires it in. Status, docs and the ADR say plainly that it is not enforced yet. Filed upstream as ruflo#3415.
Verification
pnpm run check: 4489 tests, 4483 pass, 0 fail, 6 skipped (win32). This ran withHOMEand everyXDG_*pointed at temp dirs.lint:links:internal: 0 errors.tests/fixtures/ruflo-components/. They cover typesafe doctor, the MiniLM route marker, funnel status, and governance with and without a policy file.ak status→ak uninstallround trip. An earlier sandbox attempt was invalid; see below.HOMEleaked to realXDG_*paths while this branch was being verified.Follow-ups (non-blocking)
ruflo funnel enablewhile ak manages the funnel as off, status shows "applied, not verified" with no fix. This predates the branch.driftedaction text still says "keep your value".npm root -g.doctor --jsonADR-0058 stays Accepted, with dated notes on what remains. ADR-0016 has an update note: the Claude memory pin is now receipt-owned.
🤖 Generated with Claude Code