Skip to content

feat(approval-gate): enhance configuration management and permissions - #276

Merged
sergiofilhowz merged 1 commit into
mainfrom
feat/approval-gate-configs
Jun 17, 2026
Merged

sergiofilhowz merged 1 commit into
mainfrom
feat/approval-gate-configs

Conversation

@sergiofilhowz

@sergiofilhowz sergiofilhowz commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor
  • Added new permission rules !context::on-config-change and !approval::on-config-change to the iii-permissions.yaml file to improve security and control over configuration changes.
  • Removed the obsolete approval-gate/config.yaml file, consolidating configuration management under the configuration worker for better clarity and authority.
  • Updated the README.md and other documentation to reflect the new configuration structure and the removal of config.yaml, emphasizing the role of the configuration worker.
  • Introduced a new configuration.rs file to handle the integration with the configuration worker, ensuring hot-reload capabilities for configuration changes without requiring a restart.
  • Refactored the main.rs to streamline the boot process, ensuring the authoritative configuration is fetched from the configuration worker.

Summary by CodeRabbit

  • New Features
    • Hot-reload for runtime configuration in approval-gate and context-manager, applying changes immediately (with live re-binding for structural updates).
    • Workers now use the configuration registry as the source of truth, caching the latest values for request handling.
  • Breaking Changes
    • Function/trigger identifiers moved from snake_case to kebab-case.
    • Committed config.yaml files are no longer used; --config is now only a first-registration seed.
    • The internal on-config-change handler is no longer exposed in the public function catalog.
  • Documentation
    • Updated quickstarts and integration/architecture docs to match the new configuration and naming behavior.

@vercel

vercel Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
harness Ready Ready Preview, Comment Jun 17, 2026 2:26pm
workers Ready Ready Preview, Comment Jun 17, 2026 2:26pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bd6e50a-35ed-445c-958c-a3d8da9226fc

📥 Commits

Reviewing files that changed from the base of the PR and between 4d26288 and 4a8d2cb.

📒 Files selected for processing (46)
  • approval-gate/README.md
  • approval-gate/architecture/integration.md
  • approval-gate/architecture/internals.md
  • approval-gate/config.yaml
  • approval-gate/src/config.rs
  • approval-gate/src/configuration.rs
  • approval-gate/src/functions/add_always_allow.rs
  • approval-gate/src/functions/approve_always.rs
  • approval-gate/src/functions/clear_settings.rs
  • approval-gate/src/functions/gate.rs
  • approval-gate/src/functions/get_pending.rs
  • approval-gate/src/functions/get_settings.rs
  • approval-gate/src/functions/list_pending.rs
  • approval-gate/src/functions/mod.rs
  • approval-gate/src/functions/on_config_change.rs
  • approval-gate/src/functions/on_session_deleted.rs
  • approval-gate/src/functions/purge.rs
  • approval-gate/src/functions/remove_always_allow.rs
  • approval-gate/src/functions/resolve.rs
  • approval-gate/src/functions/set_mode.rs
  • approval-gate/src/functions/sweep.rs
  • approval-gate/src/gate_config.rs
  • approval-gate/src/lib.rs
  • approval-gate/src/main.rs
  • approval-gate/src/manifest.rs
  • approval-gate/src/settings.rs
  • approval-gate/src/testkit/engine.rs
  • approval-gate/tests/golden/schemas/approval.on-config-change.json
  • approval-gate/tests/integration.rs
  • approval-gate/tests/schemas.rs
  • context-manager/README.md
  • context-manager/architecture/README.md
  • context-manager/architecture/internals.md
  • context-manager/config.yaml
  • context-manager/src/adapters/router.rs
  • context-manager/src/config.rs
  • context-manager/src/configuration.rs
  • context-manager/src/functions/assemble.rs
  • context-manager/src/functions/compact.rs
  • context-manager/src/main.rs
  • context-manager/src/ports.rs
  • context-manager/tests/common/workers.rs
  • context-manager/tests/common/world.rs
  • context-manager/tests/integration.rs
  • docs/sops/binary-worker.md
  • docs/sops/configuration.md
💤 Files with no reviewable changes (5)
  • context-manager/config.yaml
  • approval-gate/src/functions/on_config_change.rs
  • approval-gate/src/gate_config.rs
  • approval-gate/config.yaml
  • approval-gate/tests/golden/schemas/approval.on-config-change.json
✅ Files skipped from review due to trivial changes (8)
  • approval-gate/src/manifest.rs
  • context-manager/src/functions/assemble.rs
  • context-manager/README.md
  • context-manager/architecture/internals.md
  • docs/sops/binary-worker.md
  • approval-gate/README.md
  • approval-gate/architecture/internals.md
  • docs/sops/configuration.md
🚧 Files skipped from review as they are similar to previous changes (29)
  • approval-gate/src/functions/purge.rs
  • approval-gate/src/functions/list_pending.rs
  • approval-gate/src/functions/clear_settings.rs
  • approval-gate/tests/integration.rs
  • context-manager/tests/common/world.rs
  • approval-gate/src/functions/get_pending.rs
  • approval-gate/src/lib.rs
  • approval-gate/src/functions/set_mode.rs
  • approval-gate/architecture/integration.md
  • approval-gate/src/functions/resolve.rs
  • approval-gate/tests/schemas.rs
  • context-manager/src/functions/compact.rs
  • approval-gate/src/functions/get_settings.rs
  • approval-gate/src/functions/sweep.rs
  • approval-gate/src/functions/add_always_allow.rs
  • context-manager/src/adapters/router.rs
  • approval-gate/src/functions/approve_always.rs
  • approval-gate/src/functions/remove_always_allow.rs
  • context-manager/src/ports.rs
  • context-manager/src/main.rs
  • approval-gate/src/functions/on_session_deleted.rs
  • context-manager/tests/common/workers.rs
  • approval-gate/src/testkit/engine.rs
  • approval-gate/src/functions/mod.rs
  • approval-gate/src/settings.rs
  • approval-gate/src/configuration.rs
  • context-manager/src/config.rs
  • approval-gate/src/config.rs
  • context-manager/src/configuration.rs

📝 Walkthrough

Walkthrough

Replaces the approval-gate gate_config.rs/GateDefaults configuration subsystem with a new configuration.rs module using ConfigCell, TriggerHandles, and live structural trigger re-binding without restart. Extends context-manager to hot-swap FsLeaseStore via LeaseCell on lease_dir changes and read summarizer_timeout_ms per-call from the live config snapshot. Removes both workers' committed config.yaml files. Renames all approval-gate function/trigger identifiers from snake_case to kebab-case throughout API, tech spec, and documentation.

Changes

approval-gate: configuration subsystem refactor

Layer / File(s) Summary
WorkerConfig schema, BootSignature, validation and parsing
approval-gate/src/config.rs
WorkerConfig gains hook, sweep_expression, deployment defaults (default_mode, always_allow_seed, pending_timeout_ms), all timeout budgets, and deny_unknown_fields; adds BootSignature struct extracting only structural fields; exposes public API from_yaml/from_file/from_json/to_json/json_schema/boot_signature; implements expand_env helper for YAML-only ${NAME} substitution; removes load_config; comprehensive tests cover defaults, schema properties, JSON round-trips, env expansion, boot-sig equality distinctions for tuning vs structural changes.
New configuration.rs: ConfigCell, register/fetch, TriggerHandles, hot-reload handler
approval-gate/src/configuration.rs, approval-gate/src/lib.rs
Introduces ConfigCell = Arc<RwLock<Arc<WorkerConfig>>>, CONFIG_ID constant, register_config (schema registration + optional seed), fetch_config (authoritative fetch with defaults fallback), apply_config (snapshot swap), TriggerHandles (hook + sweep triggers), bind_hook/bind_sweep (best-effort bindings), rebind_slot (register-new-then-unregister-old), typed approval::on-config-change handler that re-fetches config and selectively re-binds only changed structural triggers, trigger_with_retry (resilient RPC). Unit tests verify snapshot swap and rebind ordering. Module exported from lib.rs.
Remove gate_config.rs, settings.rs migration, on_config_change.rs deletion
approval-gate/src/gate_config.rs, approval-gate/config.yaml, approval-gate/src/settings.rs, approval-gate/src/functions/on_config_change.rs, approval-gate/tests/golden/schemas/approval.on-config-change.json
Deletes gate_config.rs entirely (removes GateDefaults, SharedDefaults, snapshot, replace, parse_config_value, entry_schema, register_entry, read_defaults), deletes committed config.yaml, deletes on_config_change.rs and golden schema. Refactors settings.rs to replace all GateDefaults references with WorkerConfig in seeded_from(cfg), effective(stored, cfg), materialize_and(cfg), and test helpers.
Deps struct refactor, on_config_change removal from catalog
approval-gate/src/functions/mod.rs
Removes on_config_change module export and ON_CONFIG_CHANGE_* constants; replaces Deps.defaults/Deps.cfg with config: ConfigCell; adds pub async fn config(&self) -> Arc<WorkerConfig> snapshot accessor; removes ON_CONFIG_CHANGE from register_all and catalog() wire-surface.
All function handlers: deps.config().await migration
approval-gate/src/functions/*.rs (13 handlers)
Every handler (gate, get_settings, set_mode, add_always_allow, approve_always, remove_always_allow, clear_settings, get_pending, list_pending, resolve, sweep, purge, on_session_deleted) replaces deps.cfg.*/deps.defaults/snapshot() with per-call deps.config().await and reads all timeouts and defaults from the returned WorkerConfig snapshot.
Boot sequence refactor, optional seed, manifest defaults
approval-gate/src/main.rs, approval-gate/src/manifest.rs
Makes --config optional seed-only argument; replaces eager YAML load with register_config/fetch_config/ConfigCell construction; wires Deps with config cell; registers TriggerHandles for hook/sweep; registers config-change trigger last; updates function count (14→13) and manifest default_config JSON to include new defaults.
Testkit, integration tests, schema snapshot tests
approval-gate/src/testkit/engine.rs, approval-gate/tests/integration.rs, approval-gate/tests/schemas.rs
Testkit: creates configuration dir, extends 30s readiness with configuration::get probe, replaces defaults: SharedDefaults with config: ConfigCell, holds boot_lock for full test execution. Integration test reads stack.config.try_read() instead of snapshot. Schema tests expect 13 functions, drop approval::on-config-change assertions. Deletes golden schema file.
Docs, tech spec kebab-case renames, permissions
approval-gate/README.md, approval-gate/architecture/..., tech-specs/2026-06-agentic/approval-gate.md, docs/sops/binary-worker.md, docs/sops/configuration.md, iii-permissions.yaml
READMEs and architecture docs updated to remove config.yaml premise, document configuration worker as hard boot dependency, describe ConfigCell hot-reload tiers. Tech spec renames all function/trigger identifiers snake_case → kebab-case (e.g., pending_created → pending-created, set_mode → set-mode). Sops docs clarify hot-reload-as-default, typed handler requirements, permission denials. Adds context::on-config-change and approval::on-config-change deny rules to iii-permissions.yaml.

context-manager: LeaseCell hot-swap and per-call summarizer timeout

Layer / File(s) Summary
BootSignature narrowed, LeaseCell and Deps.leases() introduced
context-manager/src/config.rs, context-manager/src/ports.rs, context-manager/config.yaml
BootSignature reduced to lease_dir only (removes summarizer_timeout_ms); removes load_config helper and test. Introduces LeaseCell = Arc<RwLock<Arc<dyn LeaseStore>>> type and lease_cell() constructor in ports.rs; changes Deps.leases from fixed Arc to LeaseCell; adds Deps::leases() snapshot accessor. Removes committed config.yaml.
configuration.rs hot-reload rewrite: FsLeaseStore rebuild/swap on lease_dir change
context-manager/src/configuration.rs
register_config_trigger accepts leases: LeaseCell instead of boot-time BootSignature; removes restart-required refusal logic; adds typed OnConfigChangeEvent/OnConfigChangeResponse handler contract; on_config_change re-fetches config, rebuilds and swaps FsLeaseStore on lease_dir change with last-good behavior on rebuild failure, swaps config snapshot. Removes old tests; adds lease-swap test.
RouterSummarizer per-call timeout reading
context-manager/src/adapters/router.rs
RouterSummarizer stores config: ConfigCell instead of fixed timeout_ms; summarize reads summarizer_timeout_ms from live snapshot per call.
main.rs wiring, function handlers, test updates
context-manager/src/main.rs, context-manager/src/functions/assemble.rs, context-manager/src/functions/compact.rs, context-manager/tests/common/workers.rs, context-manager/tests/common/world.rs, context-manager/tests/integration.rs
main.rs constructs ConfigCell, builds RouterSummarizer with it, wraps FsLeaseStore in lease_cell, passes cells to register_config_trigger. Handlers use deps.leases().await instead of direct field access. Test helpers wire cell-based config and lease store. Integration test removes --config from worker command.
context-manager documentation
context-manager/README.md, context-manager/architecture/README.md, context-manager/architecture/internals.md
Removes restart-required exceptions; documents per-call summarizer_timeout_ms reading and FsLeaseStore rebuild-and-swap on lease_dir change with last-good behavior.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant main as approval-gate main
  participant configuration as configuration.rs
  participant ConfigWorker as configuration worker
  participant handlers as approval::* handlers
  participant ConfigCell

  rect rgba(30, 100, 200, 0.5)
    note over CLI,ConfigWorker: Boot sequence
    CLI->>main: start (--url, optional --config seed)
    main->>configuration: register_config(seed?)
    configuration->>ConfigWorker: register schema + seed initial_value
    main->>configuration: fetch_config()
    ConfigWorker-->>main: WorkerConfig (authoritative)
    main->>ConfigCell: Arc<RwLock<Arc<WorkerConfig>>>
    main->>configuration: bind_hook + bind_sweep → TriggerHandles
    main->>configuration: register_config_trigger(cell, handles)
  end

  rect rgba(20, 150, 80, 0.5)
    note over ConfigWorker,ConfigCell: Live config reload
    ConfigWorker->>configuration: configuration:updated trigger
    configuration->>ConfigWorker: re-fetch authoritative WorkerConfig
    configuration->>configuration: compare boot_signature()
    alt hook or sweep_expression changed
      configuration->>configuration: rebind_slot (register new, unregister old)
    end
    configuration->>ConfigCell: apply_config (snapshot swap)
  end

  rect rgba(180, 60, 20, 0.5)
    note over handlers,ConfigCell: Per-call config read
    handlers->>ConfigCell: deps.config().await
    ConfigCell-->>handlers: Arc<WorkerConfig> snapshot
    handlers->>handlers: use cfg.* timeouts and defaults
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • iii-hq/workers#260: Introduced the approval-gate configuration subsystem (gate_config.rs, on_config_change.rs, GateDefaults) that this PR replaces with the new configuration.rs/ConfigCell architecture.

Suggested reviewers

  • ytallo

🐇 No more config.yaml to tend,
The cell swaps live around each bend!
Kebab-case names now march in line,
BootSignature draws the structural spine.
Hot-reload blooms — no restart's end! 🌱

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/approval-gate-configs

@github-actions

github-actions Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 22 skipped (no docs/).

Layer Result
structure ✓
vale ✓
ai ✓
render ✓

Four for four. Nicely done.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tech-specs/2026-06-agentic/approval-gate.md (1)

359-364: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align configuration dependency semantics with the implemented boot contract.

Line 359 still describes configuration as a soft dependency with fallback defaults. The current runtime contract is hard dependency (register_config/fetch_config failure aborts boot), so this section is now inconsistent with the worker behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-agentic/approval-gate.md` around lines 359 - 364, The
section describing the `configuration` dependency (starting at line 359)
currently documents it as a soft dependency with built-in fallback defaults, but
the actual implementation treats it as a hard dependency where failures in
`register_config` or `fetch_config` abort the boot process. Update this section
to remove the description of soft dependency behavior and fallback defaults, and
instead clearly document that `configuration` is a required hard dependency
whose initialization failures will halt system boot.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@approval-gate/architecture/integration.md`:
- Around line 20-24: The integration.md architecture document has inconsistent
fully-qualified function IDs in the function table. Add the missing `approval::`
prefix to the function names remove-always-allow, on-session-deleted, and
on-turn-completed so they match the consistent naming pattern used for other
entries like add-always-allow, approve-always, get-settings, and clear-settings.
Ensure all function IDs in the table include the namespace prefix to prevent
breaking consumer bindings and provide accurate documentation of callable
function identifiers.

In `@approval-gate/src/config.rs`:
- Around line 86-89: The documentation comments describing the BOOT SIGNATURE
behavior for `hook` and `sweep_expression` at lines 86-89 and 98-103 are
outdated and incorrectly state that config changes to these fields are refused
on hot-reload with a restart required message. Update these documentation
comments to accurately reflect the current runtime behavior, which now re-binds
both `hook` and `sweep_expression` live during hot-reload rather than refusing
the changes. This will align the schema documentation with the actual
operational behavior and prevent operator confusion.

In `@approval-gate/src/configuration.rs`:
- Around line 274-286: The issue is that apply_config is called unconditionally
even when rebind_slot operations for hook or sweep may have failed (returned
None), which advances the boot_signature in ConfigCell while stale bindings
remain active, preventing retries on future config changes. Add guards to ensure
apply_config is only called after successful rebind operations: verify that both
rebind_slot calls for the hook (when hook config changes) and sweep (when
sweep_expression changes) complete successfully, or restructure the logic to
only call apply_config if all necessary structural rebinds have produced valid
new handles, keeping the last-good signature in ConfigCell when rebinds fail.

In `@tech-specs/2026-06-agentic/approval-gate.md`:
- Around line 439-444: The harness hook trigger type name documented in the spec
does not match the actual implementation. On line 439, replace the hook name
`harness::hook::pre_dispatch` with the correct hook name
`harness::hook::pre-trigger` to align with the current approval-gate binding
implementation. This ensures integrators reference the correct hook identifier
when implementing the approval-gate functionality.
- Around line 819-820: In the migration table on line 819, the trigger name
`pending_resolved` is missing the `approval::` prefix and does not match the
naming convention used for other triggers in the same cell. Replace
`pending_resolved` with `approval::pending-resolved` to maintain consistency
with the wire surface naming convention established by the
`approval::pending-created` trigger shown in the same line.

---

Outside diff comments:
In `@tech-specs/2026-06-agentic/approval-gate.md`:
- Around line 359-364: The section describing the `configuration` dependency
(starting at line 359) currently documents it as a soft dependency with built-in
fallback defaults, but the actual implementation treats it as a hard dependency
where failures in `register_config` or `fetch_config` abort the boot process.
Update this section to remove the description of soft dependency behavior and
fallback defaults, and instead clearly document that `configuration` is a
required hard dependency whose initialization failures will halt system boot.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 360e48c4-d630-47d4-81dc-9da403887c62

📥 Commits

Reviewing files that changed from the base of the PR and between 5f220da and 4d26288.

📒 Files selected for processing (48)
  • approval-gate/README.md
  • approval-gate/architecture/integration.md
  • approval-gate/architecture/internals.md
  • approval-gate/config.yaml
  • approval-gate/src/config.rs
  • approval-gate/src/configuration.rs
  • approval-gate/src/functions/add_always_allow.rs
  • approval-gate/src/functions/approve_always.rs
  • approval-gate/src/functions/clear_settings.rs
  • approval-gate/src/functions/gate.rs
  • approval-gate/src/functions/get_pending.rs
  • approval-gate/src/functions/get_settings.rs
  • approval-gate/src/functions/list_pending.rs
  • approval-gate/src/functions/mod.rs
  • approval-gate/src/functions/on_config_change.rs
  • approval-gate/src/functions/on_session_deleted.rs
  • approval-gate/src/functions/purge.rs
  • approval-gate/src/functions/remove_always_allow.rs
  • approval-gate/src/functions/resolve.rs
  • approval-gate/src/functions/set_mode.rs
  • approval-gate/src/functions/sweep.rs
  • approval-gate/src/gate_config.rs
  • approval-gate/src/lib.rs
  • approval-gate/src/main.rs
  • approval-gate/src/manifest.rs
  • approval-gate/src/settings.rs
  • approval-gate/src/testkit/engine.rs
  • approval-gate/tests/golden/schemas/approval.on-config-change.json
  • approval-gate/tests/integration.rs
  • approval-gate/tests/schemas.rs
  • context-manager/README.md
  • context-manager/architecture/README.md
  • context-manager/architecture/internals.md
  • context-manager/config.yaml
  • context-manager/src/adapters/router.rs
  • context-manager/src/config.rs
  • context-manager/src/configuration.rs
  • context-manager/src/functions/assemble.rs
  • context-manager/src/functions/compact.rs
  • context-manager/src/main.rs
  • context-manager/src/ports.rs
  • context-manager/tests/common/workers.rs
  • context-manager/tests/common/world.rs
  • context-manager/tests/integration.rs
  • docs/sops/binary-worker.md
  • docs/sops/configuration.md
  • iii-permissions.yaml
  • tech-specs/2026-06-agentic/approval-gate.md
💤 Files with no reviewable changes (5)
  • approval-gate/tests/golden/schemas/approval.on-config-change.json
  • approval-gate/src/functions/on_config_change.rs
  • context-manager/config.yaml
  • approval-gate/config.yaml
  • approval-gate/src/gate_config.rs

Comment on lines +20 to +24
| `approval::add-always-allow` / `remove-always-allow` | console (human-only) | Curate the auto-mode trust list (idempotent add / no-op remove). |
| `approval::approve-always` | console (human-only) | Per-session grant honoured in **every** mode; call it right before `resolve { decision: "allow" }` for an "Approve always" button. |
| `approval::get-settings` | console | Effective settings + `source: "stored" \| "defaults"`. Never writes. |
| `approval::clear-settings` | console | Drop the stored record; revert to deployment defaults. |
| `approval::on-config-change` / `on_session_deleted` / `on_turn_completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
| `approval::on-config-change` / `on-session-deleted` / `on-turn-completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use fully-qualified function IDs consistently in the function table.

Line 20 and Line 24 drop the approval:: prefix on remove-always-allow, on-session-deleted, and on-turn-completed. This documents non-existent IDs and can break consumer bindings/calls.

Proposed doc fix
-| `approval::add-always-allow` / `remove-always-allow` | console (human-only) | Curate the auto-mode trust list (idempotent add / no-op remove). |
+| `approval::add-always-allow` / `approval::remove-always-allow` | console (human-only) | Curate the auto-mode trust list (idempotent add / no-op remove). |
-| `approval::on-config-change` / `on-session-deleted` / `on-turn-completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
+| `approval::on-config-change` / `approval::on-session-deleted` / `approval::on-turn-completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `approval::add-always-allow` / `remove-always-allow` | console (human-only) | Curate the auto-mode trust list (idempotent add / no-op remove). |
| `approval::approve-always` | console (human-only) | Per-session grant honoured in **every** mode; call it right before `resolve { decision: "allow" }` for an "Approve always" button. |
| `approval::get-settings` | console | Effective settings + `source: "stored" \| "defaults"`. Never writes. |
| `approval::clear-settings` | console | Drop the stored record; revert to deployment defaults. |
| `approval::on-config-change` / `on_session_deleted` / `on_turn_completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
| `approval::on-config-change` / `on-session-deleted` / `on-turn-completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
| `approval::add-always-allow` / `approval::remove-always-allow` | console (human-only) | Curate the auto-mode trust list (idempotent add / no-op remove). |
| `approval::approve-always` | console (human-only) | Per-session grant honoured in **every** mode; call it right before `resolve { decision: "allow" }` for an "Approve always" button. |
| `approval::get-settings` | console | Effective settings + `source: "stored" \| "defaults"`. Never writes. |
| `approval::clear-settings` | console | Drop the stored record; revert to deployment defaults. |
| `approval::on-config-change` / `approval::on-session-deleted` / `approval::on-turn-completed` / `approval::sweep` | trigger handlers | Internal — never call directly. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/architecture/integration.md` around lines 20 - 24, The
integration.md architecture document has inconsistent fully-qualified function
IDs in the function table. Add the missing `approval::` prefix to the function
names remove-always-allow, on-session-deleted, and on-turn-completed so they
match the consistent naming pattern used for other entries like
add-always-allow, approve-always, get-settings, and clear-settings. Ensure all
function IDs in the table include the namespace prefix to prevent breaking
consumer bindings and provide accurate documentation of callable function
identifiers.

Comment on lines +86 to +89
/// - The BOOT SIGNATURE (`hook` + `sweep_expression`): consumed ONCE at
/// startup to bind the `harness::hook::pre-trigger` hook and the cron
/// sweep. A config change that alters either is REFUSED on hot-reload
/// (logged "restart required", the previous snapshot kept).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align structural reload docs with actual live rebind behavior.

Line 86-89 and Line 98/101 still describe hook/sweep_expression as restart-required, but runtime now re-binds them live. This mismatch can mislead operators via schema/docs.

Suggested doc-only patch
-/// - The BOOT SIGNATURE (`hook` + `sweep_expression`): consumed ONCE at
-///   startup to bind the `harness::hook::pre-trigger` hook and the cron
-///   sweep. A config change that alters either is REFUSED on hot-reload
-///   (logged "restart required", the previous snapshot kept).
+/// - The BOOT SIGNATURE (`hook` + `sweep_expression`): structural trigger
+///   bindings. On change, runtime re-binds the affected trigger live.
@@
-    /// The `harness::hook::pre-trigger` binding (restart-required).
+    /// The `harness::hook::pre-trigger` binding (live re-bound on change).
@@
-    /// 6-field cron expression for the expiry sweep (restart-required).
+    /// 6-field cron expression for the expiry sweep (live re-bound on change).

Also applies to: 98-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/src/config.rs` around lines 86 - 89, The documentation comments
describing the BOOT SIGNATURE behavior for `hook` and `sweep_expression` at
lines 86-89 and 98-103 are outdated and incorrectly state that config changes to
these fields are refused on hot-reload with a restart required message. Update
these documentation comments to accurately reflect the current runtime behavior,
which now re-binds both `hook` and `sweep_expression` live during hot-reload
rather than refusing the changes. This will align the schema documentation with
the actual operational behavior and prevent operator confusion.

Comment on lines +274 to +286
if old.boot_signature() != cfg.boot_signature() {
if old.hook != cfg.hook {
rebind_slot(&handles.hook, bind_hook(iii, &cfg));
tracing::info!("approval-gate hook re-bound (hook config changed)");
}
if old.sweep_expression != cfg.sweep_expression {
rebind_slot(&handles.sweep, bind_sweep(iii, &cfg));
tracing::info!("approval-gate sweep re-bound (sweep_expression changed)");
}
}

apply_config(cell, cfg).await;
tracing::info!("approval-gate configuration reloaded");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t swap ConfigCell when structural rebind fails.

At Line 285, apply_config runs even when Line 276/280 produced no new handle (None). That can advance boot_signature in ConfigCell while old trigger bindings remain active, preventing retries on later config-change events and leaving stale enforcement wiring.

Suggested guard to keep last-good structural signature
 async fn on_config_change(iii: &III, cell: &ConfigCell, handles: &TriggerHandles) {
@@
-    if old.boot_signature() != cfg.boot_signature() {
+    let mut structural_rebind_failed = false;
+    if old.boot_signature() != cfg.boot_signature() {
         if old.hook != cfg.hook {
-            rebind_slot(&handles.hook, bind_hook(iii, &cfg));
-            tracing::info!("approval-gate hook re-bound (hook config changed)");
+            let new = bind_hook(iii, &cfg);
+            if new.is_none() {
+                structural_rebind_failed = true;
+                tracing::error!("config-change: hook rebind failed; keeping previous config snapshot");
+            } else {
+                rebind_slot(&handles.hook, new);
+                tracing::info!("approval-gate hook re-bound (hook config changed)");
+            }
         }
         if old.sweep_expression != cfg.sweep_expression {
-            rebind_slot(&handles.sweep, bind_sweep(iii, &cfg));
-            tracing::info!("approval-gate sweep re-bound (sweep_expression changed)");
+            let new = bind_sweep(iii, &cfg);
+            if new.is_none() {
+                structural_rebind_failed = true;
+                tracing::error!("config-change: sweep rebind failed; keeping previous config snapshot");
+            } else {
+                rebind_slot(&handles.sweep, new);
+                tracing::info!("approval-gate sweep re-bound (sweep_expression changed)");
+            }
         }
     }
+
+    if structural_rebind_failed {
+        return;
+    }
 
     apply_config(cell, cfg).await;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if old.boot_signature() != cfg.boot_signature() {
if old.hook != cfg.hook {
rebind_slot(&handles.hook, bind_hook(iii, &cfg));
tracing::info!("approval-gate hook re-bound (hook config changed)");
}
if old.sweep_expression != cfg.sweep_expression {
rebind_slot(&handles.sweep, bind_sweep(iii, &cfg));
tracing::info!("approval-gate sweep re-bound (sweep_expression changed)");
}
}
apply_config(cell, cfg).await;
tracing::info!("approval-gate configuration reloaded");
let mut structural_rebind_failed = false;
if old.boot_signature() != cfg.boot_signature() {
if old.hook != cfg.hook {
let new = bind_hook(iii, &cfg);
if new.is_none() {
structural_rebind_failed = true;
tracing::error!("config-change: hook rebind failed; keeping previous config snapshot");
} else {
rebind_slot(&handles.hook, new);
tracing::info!("approval-gate hook re-bound (hook config changed)");
}
}
if old.sweep_expression != cfg.sweep_expression {
let new = bind_sweep(iii, &cfg);
if new.is_none() {
structural_rebind_failed = true;
tracing::error!("config-change: sweep rebind failed; keeping previous config snapshot");
} else {
rebind_slot(&handles.sweep, new);
tracing::info!("approval-gate sweep re-bound (sweep_expression changed)");
}
}
}
if structural_rebind_failed {
return;
}
apply_config(cell, cfg).await;
tracing::info!("approval-gate configuration reloaded");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@approval-gate/src/configuration.rs` around lines 274 - 286, The issue is that
apply_config is called unconditionally even when rebind_slot operations for hook
or sweep may have failed (returned None), which advances the boot_signature in
ConfigCell while stale bindings remain active, preventing retries on future
config changes. Add guards to ensure apply_config is only called after
successful rebind operations: verify that both rebind_slot calls for the hook
(when hook config changes) and sweep (when sweep_expression changes) complete
successfully, or restructure the logic to only call apply_config if all
necessary structural rebinds have produced valid new handles, keeping the
last-good signature in ConfigCell when rebinds fail.

Comment on lines 439 to 444
- **`harness::hook::pre_dispatch`** ([harness](harness.md#hooks)) → `approval::gate` — the
synchronous hook binding itself (`functions` filter, `timeout_ms`, `on_error: "fail_closed"`;
see [The `approval::gate` hook](#the-approvalgate-hook)).
- **`configuration`** on `configuration_id: "approval-gate"` → `approval::on_config_change` —
- **`configuration`** on `configuration_id: "approval-gate"` → `approval::on-config-change` —
reload deployment defaults reactively (replaces the prior deployment's
`approval::on_harness_config` binding on the harness entry).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update the harness hook trigger type name in the spec.

Line 439 documents harness::hook::pre_dispatch, but current approval-gate binding uses harness::hook::pre-trigger. This mismatch can cause incorrect integrator implementations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-agentic/approval-gate.md` around lines 439 - 444, The
harness hook trigger type name documented in the spec does not match the actual
implementation. On line 439, replace the hook name `harness::hook::pre_dispatch`
with the correct hook name `harness::hook::pre-trigger` to align with the
current approval-gate binding implementation. This ensures integrators reference
the correct hook identifier when implementing the approval-gate functionality.

Comment on lines +819 to +820
| No pending signal; console derives from `turn_state_changed` + `awaiting_approval[]` | `approval::pending-created` / `pending_resolved` triggers + `approval_pending` inbox |
| No global pending list | `approval::list-pending` / `approval::get-pending` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the remaining snake_case trigger name in the migration table.

Line 819 uses pending_resolved without worker prefix. It should be approval::pending-resolved to match the renamed wire surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-agentic/approval-gate.md` around lines 819 - 820, In the
migration table on line 819, the trigger name `pending_resolved` is missing the
`approval::` prefix and does not match the naming convention used for other
triggers in the same cell. Replace `pending_resolved` with
`approval::pending-resolved` to maintain consistency with the wire surface
naming convention established by the `approval::pending-created` trigger shown
in the same line.

- Added new permission rules `!context::on-config-change` and `!approval::on-config-change` to the `iii-permissions.yaml` file to improve security and control over configuration changes.
- Removed the obsolete `approval-gate/config.yaml` file, consolidating configuration management under the `configuration` worker for better clarity and authority.
- Updated the `README.md` and other documentation to reflect the new configuration structure and the removal of `config.yaml`, emphasizing the role of the `configuration` worker.
- Introduced a new `configuration.rs` file to handle the integration with the `configuration` worker, ensuring hot-reload capabilities for configuration changes without requiring a restart.
- Refactored the `main.rs` to streamline the boot process, ensuring the authoritative configuration is fetched from the `configuration` worker.
@sergiofilhowz
sergiofilhowz force-pushed the feat/approval-gate-configs branch from 4d26288 to 4a8d2cb Compare June 17, 2026 14:26
@sergiofilhowz
sergiofilhowz merged commit 6869ed1 into main Jun 17, 2026
15 checks passed

This branch was successfully deployed

1 active deployment
Preview – workers — 4a8d2cb5 Deployed Jun 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants