feat(bundle): context-intelligence opt-in dispatch filtering — workspace pattern filter and dispatch gate#19
feat(bundle): context-intelligence opt-in dispatch filtering — workspace pattern filter and dispatch gate#19colombod wants to merge 59 commits into
Conversation
db40667 to
22afbba
Compare
|
Reviewed for parent_id propagation impact. Core logic is sound; deny-all default is the correct security posture. Three concrete concerns before merge: 1. Silent-failure mode on config migration. Old flat keys ( 2. Missing combined test for parent_id × forwarding_enabled. 3. Environment audit before merge. Any DTU profile or settings.yaml using the old flat keys goes silent on the server side after merge. Worth a one-pass JSONL parent_id write is unaffected by all of the above — that path is before the filter evaluates. Server-side delivery is the operational caveat. Otherwise approved. |
|
All three concerns addressed in commits 1. Silent-failure WARNING — 2. parent_id × forwarding_enabled combined test — Added 3. Environment audit — Full grep across all source files, tests, and DTU profiles. The only real stale reference was the example config in the upload CLI's help text ( 331/331 hook tests passing, 162/162 upload tool tests passing. |
…ed to module docstring
All three test files had minor cosmetic line-wrapping issues where ruff prefers single-line signatures for functions that fit within the 88-char limit. Auto-fixed by `ruff format`. Also sync uv.lock files to reflect the 0.1.0 → 0.1.1 version already set in pyproject.toml. No logic changes — all 327 tests continue to pass.
…lligence-awareness.md
…ace-pattern implementation
…, reserved for custom resolvers
…ested URL + API key
Add TestServerConfig class with three tests that fail with AttributeError
because _server_config() method does not yet exist on ConfigResolver.
Update TestContextIntelligenceServerUrl and TestContextIntelligenceApiKey to
use nested config shape {"context_intelligence_server": {"url": ...}} and
{"context_intelligence_server": {"api_key": ...}} respectively. These tests
fail because the implementation still reads flat keys.
RED phase for task-1 of the nested config restructure.
…ution to use nested config
- Add _server_config() private helper method that returns the nested
config['context_intelligence_server'] dict (or {} if absent/non-dict)
- Update context_intelligence_server_url to read from
config['context_intelligence_server']['url'] instead of flat key
- Update context_intelligence_api_key to read from
config['context_intelligence_server']['api_key'] instead of flat key
- Both properties now support coordinator-level nested config as step 2
in the four-step resolution order
Resolves task-2
…igence_server config
…nfig Both properties now delegate to self._server_config().get(...) instead of self._config.get(...), so they read from the nested config['context_intelligence_server'] dict. Updated TestForwardingEnabled tests to use the nested config structure so they correctly exercise the new property resolution path. - allow_workspaces: reads context_intelligence_server.allow_workspaces - deny_workspaces: reads context_intelligence_server.deny_workspaces - Both return list() copies, default [] when key absent
…ding (task-5) - Delete test_host_override_false_short_circuits and test_host_override_none_does_not_short_circuit from TestForwardingEnabled (reduce from 10 to 8 tests) - Update TestForwardingEnabled class docstring to reflect four-step resolution chain (removed forwarding_enabled: False short-circuit step) - Replace _evaluate_forwarding with four-step version removing config['forwarding_enabled'] check — resolution now: empty allow → False, workspace not opted in → False, workspace denied → False, default → True - Remove forwarding_enabled: false bullet from ConfigResolver class docstring; replace with note that allow/deny_workspaces are configured under context_intelligence_server key
…ence_server YAML block
Update TestSettingsYamlFallback and _parse_settings_yaml to use nested YAML structure:
overrides:
hook-context-intelligence:
config:
context_intelligence_server:
url: <value>
api_key: <value>
Previously the parser read flat keys (context_intelligence_server_url,
context_intelligence_api_key) directly under config:. Now it navigates
the context_intelligence_server nested block to read url and api_key.
Changes:
- context_intelligence/config.py: update docstring and both parsers
(PyYAML path and fallback line-based parser) to read nested structure
- tests/test_config_resolver.py: update 3 tests in TestSettingsYamlFallback
to write nested YAML matching the new expected format
All 86 tests pass.
…k-7) Replace flat context_intelligence_server_url and context_intelligence_api_key config keys with a nested context_intelligence_server dict containing url and api_key sub-keys, plus commented-out allow_workspaces and deny_workspaces fields. Update test_behavior_hook_has_config to assert the new nested structure: - assert 'context_intelligence_server' in config - assert isinstance(config['context_intelligence_server'], dict)
…server config (task-8) Replace flat config key documentation (context_intelligence_server_url, etc.) with the nested context_intelligence_server dict structure. Sub-keys url, api_key, allow_workspaces, and deny_workspaces are documented as nested under context_intelligence_server. Remove the forwarding_enabled key which is no longer part of the public config interface. Update TestModuleDocstring.test_docstring_documents_forwarding_enabled → test_docstring_documents_nested_server_config to assert the new nested structure and verify absence of the old flat keys.
…r, remove forwarding_enabled key
- Fix tests in test_skill_fetcher_mount.py to use nested config format
(context_intelligence_server: {url: ...} instead of context_intelligence_server_url: ...)
- Fix test_config_has_thin_forwarder_keys to check for nested key
- Remove idna>=3.15 from pyproject.toml (httpx subdependency, not needed directly)
- Reformat context_intelligence/config.py via ruff format
- Update uv.lock after pyproject.toml change
All 328 tests pass. Ruff check and format both clean.
…igence_server config
…nd add include property - Replace _server_config() to read from config['server'] instead of config['context_intelligence_server'] - Add include property that merges lists from hook config, coordinator config, and AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE env var with deduplication (insertion-order preserved via dict.fromkeys) TestInclude: 3/3 PASS Collateral failures in TestServerConfig, TestContextIntelligenceServerUrl, TestContextIntelligenceApiKey, TestAllowWorkspaces, TestDenyWorkspaces, TestForwardingEnabled (to be fixed in tasks 3, 8, 9)
…ver to server key Task 3: Update test fixtures that were collateral damage from Task 2's _server_config() rename from 'context_intelligence_server' to 'server'. Changes: - TestContextIntelligenceServerUrl: update config dicts to use 'server' key - TestContextIntelligenceApiKey: update config dicts to use 'server' key - TestServerConfig: update class to test 'server' key, update docstrings - TestSettingsYamlFallback: update YAML literals to use 'server:' key Note: TestSettingsYamlFallback::test_server_url_falls_back_to_settings_yaml and ::test_api_key_falls_back_to_settings_yaml remain failing because _parse_settings_yaml() in context_intelligence/config.py still reads from 'context_intelligence_server:' YAML key. This is expected per Task 3 spec (Step 5) and will be fixed in Task 4. TestAllowWorkspaces/TestDenyWorkspaces/TestForwardingEnabled remain red intentionally (to be updated in Tasks 8/9).
…erver' for url/api_key
Two properties (context_intelligence_server_url, context_intelligence_api_key)
called _coordinator_config_get("context_intelligence_server") directly — bypassing
_server_config() for the coordinator-layer lookup. After Task 2 helper rename
(_server_config() now reads 'server' key), the coordinator layer for url/api_key
was silently broken.
Also updates _parse_settings_yaml() in context_intelligence/config.py to read
the 'server' key instead of 'context_intelligence_server' from settings.yaml,
fixing TestSettingsYamlFallback tests.
Changes:
- config_resolver.py: coordinator lookup for url/api_key changed from
'context_intelligence_server' to 'server'
- config_resolver.py: docstrings updated to reflect new key names
- context_intelligence/config.py: _parse_settings_yaml() PyYAML and fallback
line-parser paths updated to read 'server' key
- tests: added coordinator-path tests for url and api_key
Closes task-4
…mantics Adds ConfigResolver.exclude property mirroring the include property pattern. Reads workspace exclusion patterns from three sources (union, deduped): 1. config["server"]["exclude"] 2. coordinator.config["server"]["exclude"] 3. AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_EXCLUDE env var Inserted after include and before allow_workspaces as specified. Closes task-6
…e and exclude Pin down design-specified behaviour for the three-layer union resolution of include and exclude: - all three layers contribute (config, coordinator config, env var) - result is deduplicated using dict.fromkeys (insertion order preserved) - whitespace-only and empty env var segments contribute nothing Adds TestIncludeUnionSemantics and TestExcludeUnionSemantics test classes to test_config_resolver.py, covering 7 scenarios each: - from_config_only - from_coordinator_only - from_env_var_only - union_all_three_layers - deduplication - empty_env_var_contributes_nothing - whitespace_only_env_var_ignored All 14 tests pass against the Tasks 2/6 implementation. Task: task-7
…estForwardingEnabled
- Delete TestAllowWorkspaces (obsolete old-API tests)
- Delete TestDenyWorkspaces (obsolete old-API tests)
- Replace TestForwardingEnabled with 10 tests using real workspace slugs
and new config shape: {"server": {"include": [...], "exclude": [...]}}
New tests cover the 4-step resolution chain:
- test_deny_all_no_include — empty include+exclude → False
- test_exact_match — exact slug match → True
- test_suffix_wildcard — -home-dicolomb-amplifier-bundle-* pattern → True
- test_prefix_wildcard — *-secrets pattern → True
- test_multi_segment_wildcard — multi-segment fnmatch → True
- test_wildcard_all — * matches default → True
- test_no_pattern_match — no match → False
- test_exclude_trims_include — include + exclude match → False
- test_exclude_without_include_deny_all — empty include → False
- test_include_with_non_matching_exclude — non-matching exclude → True
6 of 10 tests currently fail (resolver uses old allow_workspaces/deny_workspaces
keys — fixed in task-9)
…/exclude - Update class docstring: replace allow_workspaces/deny_workspaces references with new include/exclude terminology including env var names - Remove allow_workspaces and deny_workspaces properties (breaking removal) - Rewrite _evaluate_forwarding() to use self.include / self.exclude instead of the old self.allow_workspaces / self.deny_workspaces All 10 TestForwardingEnabled tests pass. allow_workspaces and deny_workspaces properties no longer exist on ConfigResolver.
…er' block Update stale test fixtures that were still using 'context_intelligence_server:' key format in settings.yaml YAML strings. config.py was already updated (task-10) to read from the 'server:' block instead; align these two tests to match the current expected behavior. Fixes regressions in: - TestParseSettingsYaml::test_returns_server_url_from_yaml_content - TestParseSettingsYaml::test_parses_both_keys_from_valid_yaml
…eny to include/exclude - Replace 'context_intelligence_server' config key with 'server' in behaviors/context-intelligence.yaml - Replace '# allow_workspaces' comment with '# include:' for opt-in workspace patterns - Replace '# deny_workspaces' comment with '# exclude:' for opt-out workspace patterns - Add tests/test_behavior_yaml_keys.py to verify the new key structure Refs: task-11
…nfig keys - Replace 'context_intelligence_server' → 'server' in all 3 scenario config dicts - Replace 'allow_workspaces' → 'include' throughout (headers, comments, configs, assertions) - Replace 'deny_workspaces' → 'exclude' in scenario 3 config dict - Update scenario names: scenario-2-allow-list-opt-in → scenario-2-include-list-opt-in and scenario-3-deny-trims-allow → scenario-3-exclude-trims-include - Update header comments: 'Deny trims allow' → 'Exclude trims include' - Update scenario 2 constants to real workspace slugs: SESSION_ID: dtu-allow-001 → dtu-include-001 WORKSPACE: ci-allowed-test → -home-dicolomb-amplifier-bundle-context-intelligence-dtu-test include pattern: ci-allowed-* → -home-dicolomb-amplifier-bundle-* - Update scenario 3 constants to real workspace slugs: SESSION_ID: dtu-deny-trim-001 → dtu-exclude-trim-001 WORKSPACE: ci-denied-test → -home-dicolomb-workspaces-cotnext-intelligence-configuration-secrets include pattern: ci-* → -home-dicolomb-workspaces-* exclude pattern: ci-denied-* → *-secrets - All failure messages and PASS prints updated to reflect new terminology Verifications: yaml.safe_load OK; grep finds no old keys (context_intelligence_server, allow_workspaces, deny_workspaces)
… vars Replace all occurrences of deprecated config keys: - context_intelligence_server → server - allow_workspaces → include (with new AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE env var) - deny_workspaces → exclude (with new AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_EXCLUDE env var) Updates: always-active table header, narrative dispatch text, both YAML config examples, Python config dict examples, and the configuration reference table (adding union semantics note for include and new env vars for include/exclude). Verified: grep finds no remaining stale references.
…nion semantics Replace deprecated config keys: - context_intelligence_server.allow_workspaces → server.include (with AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE env var union) - context_intelligence_server.deny_workspaces → server.exclude (with AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_EXCLUDE env var union) - context_intelligence_server.url → server.url - context_intelligence_server.api_key → server.api_key Update forwarding chain to use include/exclude terminology. Update path_rules description to reference server.include.
…ligence_server to server BREAKING CHANGE: config key context_intelligence_server renamed to server. Fields allow_workspaces and deny_workspaces renamed to include and exclude. include/exclude now use union semantics across config, coordinator config, and env vars. New env vars: AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE / _EXCLUDE (comma-separated). See migration guide in docs/designs/2026-05-28-context-intelligence-include-exclude.md
Add --include and --exclude repeatable flags to the upload CLI parser. Both use action='append', default=[] so they accumulate patterns and default to the empty list. Also introduce _ArgumentParser (a minimal ArgumentParser subclass) that overrides _parse_optional to treat single-dash strings that are not registered option strings as positional values rather than potential options. This is required because workspace slugs are derived from filesystem paths and therefore start with a single dash (e.g. -home-dicolomb-amplifier-*). Without this override, argparse's POSIX short-option concatenation heuristic would match such strings against the registered -h option, causing parse errors when they are passed as --include/--exclude values.
…+ per-session skip
…llow test Three required fixes from holistic code review: 1. Workspace extraction (critical): replace getattr(session, 'workspace') with dict-access on the tuple[Path, dict] shape that discover_and_sort() returns. getattr on a tuple always returns None, causing every specific --include pattern to silently filter out all real sessions. The dataclass/object fallback path is preserved for test doubles. 2. _DETAILED_HELP WORKSPACE BEHAVIOUR section: rewrite the section that falsely stated 'no workspace filtering is applied' to accurately document --include, --exclude, the deny-all default, env var sources, and the '*' escape hatch. 3. test_provided_job_id_not_auto_generated: add '--include', '*' so the test exercises job_id logic rather than being trivially short-circuited by the deny-all guard added in Task 8. Also moves 'import logging' to its correct alphabetical position in the stdlib import block (style fix, no behaviour change).
getattr(session, "workspace") on a tuple[Path, dict] always returns None, making workspace-specific --include patterns silently skip all sessions. Fix unpacks the tuple and reads workspace from the metadata dict, mirroring the pattern already used in uploader.py. Also: update _DETAILED_HELP workspace section (was still referencing CI_UPLOAD_INCLUDE_WORKSPACES env vars and colon-separated syntax instead of the correct AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE with comma-separated values), fix hollow test_provided_job_id_not_auto_generated (now passes --include "*"). Tests added: - test_tuple_session_workspace_extracted_correctly: verifies the fix via explicit tuple unpacking in the test body, documents correct approach - Regression guard in test_per_session_skip_logged_at_info: adds a real tuple[Path, dict] session whose skip-log must contain the workspace slug from the dict (not '' which would indicate a getattr regression)
…c messages Previously the one-shot warning always said "server.include is empty or workspace is not in the include list" even when the workspace WAS in include but was trimmed by an exclude pattern. Now the reason is specific: - "no include patterns configured (deny-all default)" - "workspace '...' does not match any server.include pattern" - "workspace '...' is excluded by server.exclude pattern '...'" Changes: - ConfigResolver.forwarding_blocked_reason: new property returning None when forwarding is enabled, or a human-readable string for each deny case. - LoggingHandler.__call__: replaces static warning message with the resolver's reason via getattr() (safe for legacy resolvers without the new property). - test_config_resolver.py: TestForwardingBlockedReason (6 tests, red→green). Updated _make_coordinator() to accept optional workspace kwarg. - test_logging_handler_server_dispatch.py: TestSilentDispatchWarning updated to assert new message format; 2 new tests verify deny-all and exclude-pattern reason strings. _FakeResolver extended with forwarding_blocked_reason parameter.
a530386 to
60770ff
Compare
…s, skills, and diagram Updates all remaining user-facing references from the breaking rename: - cli.py _DETAILED_HELP YAML example: context_intelligence_server → server, allow_workspaces → include - test_cli.py: update enforcement assertions to match new key names - agents/session-navigator.md: config path updated - agents/graph-analyst.md: 3 config path references updated - skills/context-intelligence-session-reconstruction/SKILL.md: settings.yaml example updated - context/config-resolution.dot: node label updated
Summary
This PR implements a config schema restructure and introduces workspace filtering to the Context Intelligence server dispatch system:
context_intelligence_server_url,context_intelligence_api_key) move into a nestedserver:block withurlandapi_keysubkeysincludeandexcludepattern lists underserver:, usingfnmatchglob syntax (e.g.-home-user-*,*-secrets)context-intelligence-uploadcommand gains repeatable--include/--excludeflags and corresponding env var supportforwarding_blocked_reasonproperty onConfigResolverreturns specific denial messages instead of generic onesWhat Changed in Detail
Config Restructure (Breaking)
Before (on main):
After (this PR):
Workspace Filtering (New)
Deny-all-by-default: Nothing dispatches to the server unless
includepatterns are configured. This is an intentional breaking change to support workspace opt-in security.Pattern matching: Patterns use
fnmatchglob syntax (e.g.-home-user-*,work-*,*-secrets). Workspace names are slugified working directory paths (e.g./home/alice/work/my-apibecomes-home-alice-work-my-api).Union semantics: All
includeandexcludepatterns are unioned across configuration sources:server.include/server.excludeAMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDE/_EXCLUDE(comma-separated)Resolution order (first match blocks):
includepatterns absent across all layersincludepatternexcludepatternUpload CLI Filtering (New)
context-intelligence-uploadgains new repeatable--include/--excludeflags:Environment variable support:
AMPLIFIER_CONTEXT_INTELLIGENCE_SERVER_INCLUDEand_EXCLUDE(comma-separated). Same deny-all default — no--include= nothing uploaded, with a clear warning to stderr.Improved Diagnostics
New
forwarding_blocked_reasonproperty onConfigResolverreturns a specific denial message:"no include patterns configured (deny-all default)"— no include patterns found in any layer"workspace 'X' does not match any server.include pattern"— workspace not in include list"workspace 'X' is excluded by server.exclude pattern 'Y'"— excluded by deny listBreaking Changes
Existing
settings.yamlfiles must migrate from the old flat schema to the new nested schema:Before (current main)
After (this PR)
Verification Status
All test suites passing:
DTU validation: All 3 filter scenarios confirmed live against the production CI server:
Code quality: ruff check and ruff format pass across all 27 changed files; pyright type checking clean.
Migration Guide
See
docs/designs/2026-05-28-context-intelligence-include-exclude.mdfor the complete migration guide.Quick migration (1-2 min):
settings.yamlfile (typically~/.amplifier/settings.yaml)server:blockinclude:patterns for workspaces you want to dispatchexclude:patterns to deny-trim your include listStats
Generated with Amplifier