[FEATURE] Session-scoped ingredient locking with structural enforcement#3381
Merged
Trecek merged 6 commits intoMay 31, 2026
Merged
Conversation
Adds lock_ingredients MCP tool and ingredient_lock_guard PreToolUse hook for structurally enforcing recipe ingredient values within a kitchen session. - lock_ingredients tool: locks ingredient values to overlay, resolves locked_steps from active_recipe_steps using skip_when_false, supports unlock by rebuilding locked_steps from remaining ingredients - _write_ingredient_locks helper: atomic R-M-W with fcntl.flock serialization - _check_ingredient_locks: server-side enforcement in run_skill (primary gate), with resume_session_id exemption - ingredient_lock_guard.py: supplemental PreToolUse hook (fail-open) - Registered in HOOK_REGISTRY (run_skill + dispatch_food_truck) and NEW_SUBDIR_BASENAMES - Updated FREE_RANGE_TOOLS, _DISPLAY_CATEGORIES, _UNFORMATTED_TOOLS, _TOOL_PARAMS - Updated hardcoded test fixtures (test_type_constants, test_gate, test_server_tool_registration, test_doc_counts) - Added INGREDIENT LOCKING section to sous-chef SKILL.md - Updated doc tool counts (55->56) across all documentation files - Added test files: test_lock_ingredients.py, test_run_skill_locks.py, test_ingredient_lock_guard.py - Updated sub-CLAUDE.md file tables for new files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fcntl allowlist: add tools_kitchen.py to fcntl allowed paths - doc counts: update test_docs_state_55_mcp_tools to test_docs_state_56_mcp_tools - never_raises contract: refactor lock check into single-expression guard - json write sites: update allowlist line numbers for atomic_write - _TOOL_PARAMS sync: add tools_kitchen to _SERVER_TOOL_MODULES - unlock bug: fix locked_ingredients pop on dict copy; pass None signal for locked_steps when pipeline becomes empty - headless denial test: assert result["result"] instead of result["error"] - business logic extraction: move step-computation to module-level helpers (_compute_locked_steps, _compute_unlocked_steps, _apply_unlock_keys) - subpackage isolation: bump hooks/guards exemption to 24 files - hook registry hash: sync via task sync-hooks-hash - arch violations: replace print() with sys.stdout.write() in guard - hook test env: add cwd parameter to _run helper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fail-open tests (empty stdin, invalid JSON, missing overlay) previously only asserted exit code 0 without checking that no deny JSON was emitted. A regression that outputs a deny JSON on malformed input would pass silently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The second unlock loop via .get() was redundant — the first loop already pops all unlock_keys from li[pipeline_id] when pipeline_id exists. The second loop using .get() either operates on the same dict (no-op since keys were already popped) or on a transient dict (no effect). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move overlay read + merge + step computation into _write_ingredient_locks so all state operations happen under the flock. Previously lock_ingredients read the overlay outside the flock to compute current_pipeline_li, then passed the pre-computed values to _write_ingredient_locks which read again under flock — a concurrent call between reads could lose updates. Also removes dead _compute_locked_steps function (extracted but never called — _compute_unlocked_steps was used instead). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _write_ingredient_locks refactor moved the atomic_write call from line 762 to line 751. Update the _LEGACY_JSON_WRITES allowlist to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a47353e to
15a21be
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.
Summary
Add a
lock_ingredientsMCP tool andingredient_lock_guard.pyPreToolUse hook to structurally enforce recipe ingredient values within a kitchen session. The orchestrator callslock_ingredientsonce at session start to declare ingredient values (e.g.,investigate=false). The server resolves these into alocked_stepsdict (step name -> allowed bool) written to the hook config overlay. A server-side check inrun_skillis the primary enforcer; the hook provides supplemental UX feedback and audit trail.close_kitchenalready deletes the overlay file, clearing all locks automatically.This addresses the recurring step-skipping failure class (#1083, #3337, #3065, #3238) where orchestrators re-interpret user instructions at every step and drift.
Requirements
Add a unified session-scoped ingredient locking mechanism: a single MCP tool (
lock_ingredients) that lets the orchestrator declare ingredient values at session start, persists them to the hook config overlay, and a new PreToolUse hook (ingredient_lock_guard.py) that structurally enforces those values — blockingrun_skillcalls that violate the declared configuration.This closes the gap where the orchestrator can misinterpret or forget user instructions mid-session. Once the orchestrator locks
investigate=false, the system enforces it mechanically — the orchestrator cannot override its own declaration without explicitly unlocking first.Closes #3357
Implementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/impl-20260530-183542-576015/.autoskillit/temp/make-plan/session_scoped_ingredient_locking_plan_2026-05-30_190000.md🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary
* Step used a non-Anthropic provider; caching behavior may differ.
Token Efficiency
Model Usage Breakdown