Skip to content

feat(correct): add applies_to= binding token per sim21 trust-crisis scenario#57

Merged
Gradata merged 1 commit into
mainfrom
wt-scope-tagging
Apr 15, 2026
Merged

feat(correct): add applies_to= binding token per sim21 trust-crisis scenario#57
Gradata merged 1 commit into
mainfrom
wt-scope-tagging

Conversation

@Gradata

@Gradata Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner

Summary

Adds optional applies_to: str | None = None to Brain.correct and siblings, persisting the binding token alongside the correction event and the resulting lesson.

Persistence-only. Injection-side filtering is a follow-up that will touch _types.py and inject_brain_rules.py, so it waits for worktree-agent-a081f7ba.

Motivation

Sim21 — #1 product gap (80%+ of posts): a single high-edit-distance correction graduates globally even when user meant it for one client. Users asked (~40 mentions) for scope-tagging at correction time.

Key design decision: applies_to, not scope

Brain.correct already has a scope enum (universal/domain/project/one_off) governing graduation breadth — a different axis from sim21's ask. Reusing the name would silently break the existing CorrectionScope contract. So this PR adds applies_to for free-form binding tokens (client:acme, task:emails) and leaves scope untouched.

Files changed (6)

  • src/gradata/brain.py, _core.py, context_wrapper.py, mcp_tools.py, cloud/client.py
  • tests/test_scope_tagging.py — 4 new round-trip tests

Tests

  • 2221 → 2225 pass, 23 skip unchanged
  • uv run ruff check on all 5 edited source files: clean

Follow-up needed (not in this PR)

  • Injection filter (requires _types.py edit; conflicts with a081f7ba until it lands)
  • Cloud endpoint doesn't yet consume applies_to in payload
  • Index applies_to on hot path (JSON parse of scope_json is too slow for selection)
  • Extend record_correction for symmetry

Test plan

  • Full pytest suite green (2225 pass)
  • Ruff clean
  • Cloud endpoint consumes applies_to

Co-Authored-By: Gradata noreply@gradata.ai

Sim21 found that a single high-edit-distance correction could bypass
the 3-signal rule and graduate GLOBALLY even when the user intended
it for one client. Users asked for scope-tagging at correction time.

The existing `scope` parameter is a 4-value enum
(universal/domain/project/one_off) that governs graduation breadth.
This commit adds a complementary free-form `applies_to` token (e.g.
`client:acme`, `task:emails`) that binds a correction to a specific
context. None preserves existing global behaviour — fully backward
compatible.

Persistence-only in this commit. Graduation + injection-side filtering
by applies_to is a follow-up, intentionally deferred to keep this
patch small and avoid touching _types.py / rule_engine.py / meta_rules_storage.py
(owned by pending branches).

Wiring:
- Brain.correct(applies_to=...) -> _core.brain_correct -> event data,
  event tags (`applies_to:<token>`), and lesson.scope_json.
- ContextWrapper.correct, mcp_tools.correct, CloudClient.correct all
  forward the token through to Brain.correct / cloud payload.

Tests: 4 new round-trip tests in test_scope_tagging.py covering
event persistence, backward compatibility (None), empty-string
normalisation, and propagation into lesson.scope_json.

Co-Authored-By: Gradata <noreply@gradata.ai>

@greptile-apps greptile-apps 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.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
📝 Walkthrough
  • Added optional applies_to: str | None parameter to Brain.correct() and related call sites (_core.brain_correct(), CloudClient.correct(), BrainContextState.correct(), mcp_tools.correct())
  • Persists binding token with correction events and lessons via scope_json, event data, and tags
  • Normalizes empty/whitespace applies_to values to None for consistency
  • Free-form binding token format (e.g., client:acme, task:emails) distinct from existing CorrectionScope enum to maintain backward compatibility
  • Injection-side filtering and graduation behavior deferred to follow-up work
  • Four new round-trip tests covering persistence, backward compatibility, normalization, and lesson propagation
  • No breaking changes; optional parameter with None default preserves prior behavior

Walkthrough

This change introduces a new optional applies_to parameter to the brain correction workflow, threading it through multiple layers: core correction logic, brain API, cloud client, context wrapper, and MCP tools. The parameter is normalized and persisted in correction metadata, event tags, and lesson scopes.

Changes

Cohort / File(s) Summary
Core Correction API
src/gradata/_core.py, src/gradata/brain.py, src/gradata/cloud/client.py
Added applies_to: str | None parameter to correction methods. Core layer normalizes whitespace and passes through to cloud; brain and cloud layers forward parameter through call chain. Metadata conditionally includes applies_to in scope data, tags (as applies_to:<value>), and event objects.
Wrapper and Tool Layers
src/gradata/context_wrapper.py, src/gradata/mcp_tools.py
Extended BrainContextState.correct() and correct() tool to accept and forward applies_to parameter to underlying Brain.correct() calls.
Tests
tests/test_scope_tagging.py
Added four test cases validating applies_to persistence in correction results, event data, tags, and lesson scope; plus backward-compatibility and normalization coverage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

feature, breaking-change

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding an applies_to binding token parameter to the Brain.correct() method and related functions for the sim21 trust-crisis scenario.
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the motivation (sim21 product gap), design decisions (why applies_to instead of scope), files changed, test results, and follow-up work needed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 wt-scope-tagging

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 82f9837
Status: ✅  Deploy successful!
Preview URL: https://ecc1a911.gradata-dashboard.pages.dev
Branch Preview URL: https://wt-scope-tagging.gradata-dashboard.pages.dev

View logs

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/cloud/client.py`:
- Around line 109-110: The payload currently accepts applies_to as-is in
CloudClient.correct; normalize it first by trimming whitespace and filtering out
empty/whitespace-only tokens (e.g., strip strings and remove entries that become
empty) before setting payload["applies_to"], ensuring the same normalization
behavior as the local-path code path in CloudClient.correct so only non-empty,
trimmed tokens are sent to the server.

In `@src/gradata/mcp_tools.py`:
- Line 97: The call to brain.correct(draft, final, applies_to=applies_to) drops
the correction's category metadata; update the call in mcp_tools to forward the
local/category parameter (e.g., brain.correct(draft, final,
applies_to=applies_to, category=category)) so the persisted correction keeps the
same category reported by the tool, and ensure the local variable name matches
the function parameter if it differs.

In `@tests/test_scope_tagging.py`:
- Line 226: Replace the vague truthiness check `assert tagged` with an explicit
cardinality assertion: compute a concrete count (e.g., count = sum(1 for l in
lessons if l.applies_to) or count = len([l for l in lessons if l.applies_to]))
and then assert the expected number (for example `assert count ==
EXPECTED_COUNT` or at minimum `assert count > 0`) so the test checks a specific
numeric expectation rather than truthiness; update the assertion line that
currently references `tagged` and keep the diagnostic
`scope_jsons={[l.scope_json for l in lessons]}` in the failure message.
- Around line 185-204: Combine the two tests
(test_applies_to_none_is_backward_compatible and
test_applies_to_empty_string_collapses_to_none) into one parametrized pytest
test that exercises the boundary cases for applies_to normalization and reduces
duplication: add a pytest.mark.parametrize over cases (e.g. an "omitted"
sentinel to call brain.correct without the applies_to kwarg, and
whitespace/empty-string values to pass as applies_to) and inside the test call
brain = init_brain(tmp_path) and invoke brain.correct either with or without
applies_to depending on the parameter, then assert the same invariants against
result ("applies_to" not in result, not in result.get("data", {}), and no tags
starting with "applies_to:") using the brain.correct symbol to locate the code
to change.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: e1df89aa-af24-453e-9747-285e6f568c22

📥 Commits

Reviewing files that changed from the base of the PR and between 52a735e and 82f9837.

📒 Files selected for processing (6)
  • src/gradata/_core.py
  • src/gradata/brain.py
  • src/gradata/cloud/client.py
  • src/gradata/context_wrapper.py
  • src/gradata/mcp_tools.py
  • tests/test_scope_tagging.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Test (Python 3.13)
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.11
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/cloud/client.py
  • src/gradata/_core.py
  • src/gradata/context_wrapper.py
  • src/gradata/mcp_tools.py
  • src/gradata/brain.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_scope_tagging.py
🔇 Additional comments (3)
src/gradata/context_wrapper.py (1)

177-183: applies_to pass-through is clean and backward-compatible.

Signature, docs, and forwarding all line up correctly here.

Also applies to: 207-207

src/gradata/_core.py (1)

108-113: Good end-to-end persistence wiring for applies_to.

Normalization, event payload, tags, and lesson scope_json propagation are all implemented consistently.

Also applies to: 170-172, 184-185, 192-193, 199-200, 326-327

src/gradata/brain.py (1)

376-387: Public API update is correctly threaded to core.

Brain.correct(..., applies_to=...) is documented clearly and forwarded correctly.

Also applies to: 403-403

Comment on lines +109 to +110
if applies_to:
payload["applies_to"] = applies_to

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

Normalize applies_to before adding it to the payload.

Direct callers of CloudClient.correct() can currently send whitespace-only tokens to the server. Normalize here for consistency with local-path behavior.

Suggested patch
-        if applies_to:
-            payload["applies_to"] = applies_to
+        if applies_to is not None:
+            applies_to = str(applies_to).strip() or None
+        if applies_to:
+            payload["applies_to"] = applies_to
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/gradata/cloud/client.py` around lines 109 - 110, The payload currently
accepts applies_to as-is in CloudClient.correct; normalize it first by trimming
whitespace and filtering out empty/whitespace-only tokens (e.g., strip strings
and remove entries that become empty) before setting payload["applies_to"],
ensuring the same normalization behavior as the local-path code path in
CloudClient.correct so only non-empty, trimmed tokens are sent to the server.

Comment thread src/gradata/mcp_tools.py

brain = Brain(brain_dir)
brain.correct(draft, final)
brain.correct(draft, final, applies_to=applies_to)

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

category is dropped when persisting the correction.

This call ignores the function’s category value, so stored correction metadata can mismatch what this tool reports back.

Suggested patch
-            brain.correct(draft, final, applies_to=applies_to)
+            brain.correct(draft, final, category=category, applies_to=applies_to)
📝 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
brain.correct(draft, final, applies_to=applies_to)
brain.correct(draft, final, category=category, applies_to=applies_to)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/gradata/mcp_tools.py` at line 97, The call to brain.correct(draft, final,
applies_to=applies_to) drops the correction's category metadata; update the call
in mcp_tools to forward the local/category parameter (e.g., brain.correct(draft,
final, applies_to=applies_to, category=category)) so the persisted correction
keeps the same category reported by the tool, and ensure the local variable name
matches the function parameter if it differs.

Comment on lines +185 to +204
def test_applies_to_none_is_backward_compatible(tmp_path: Path):
"""Omitting applies_to leaves the event clear of the token (legacy behaviour)."""
from tests.conftest import init_brain

brain = init_brain(tmp_path)
result = brain.correct("bad output", "good output")
assert "applies_to" not in result
assert "applies_to" not in result.get("data", {})
assert not any(t.startswith("applies_to:") for t in result.get("tags", []))


def test_applies_to_empty_string_collapses_to_none(tmp_path: Path):
"""Empty / whitespace-only applies_to is normalised away."""
from tests.conftest import init_brain

brain = init_brain(tmp_path)
result = brain.correct("bad output", "good output", applies_to=" ")
assert "applies_to" not in result
assert "applies_to" not in result.get("data", {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Parametrize boundary-value cases for applies_to normalization.

These boundary checks are good, but parametrizing them will reduce duplication and make edge coverage easier to extend.

Suggested refactor
+@pytest.mark.parametrize(
+    "kwargs",
+    [{}, {"applies_to": None}, {"applies_to": ""}, {"applies_to": "   "}],
+)
+def test_applies_to_absent_or_empty_not_persisted(tmp_path: Path, kwargs: dict):
+    """Absent/empty applies_to should not persist token fields/tags."""
+    from tests.conftest import init_brain
+
+    brain = init_brain(tmp_path)
+    result = brain.correct("bad output", "good output", **kwargs)
+    assert "applies_to" not in result
+    assert "applies_to" not in result.get("data", {})
+    assert not any(t.startswith("applies_to:") for t in result.get("tags", []))
-
-def test_applies_to_none_is_backward_compatible(tmp_path: Path):
-    """Omitting applies_to leaves the event clear of the token (legacy behaviour)."""
-    from tests.conftest import init_brain
-
-    brain = init_brain(tmp_path)
-    result = brain.correct("bad output", "good output")
-    assert "applies_to" not in result
-    assert "applies_to" not in result.get("data", {})
-    assert not any(t.startswith("applies_to:") for t in result.get("tags", []))
-
-
-def test_applies_to_empty_string_collapses_to_none(tmp_path: Path):
-    """Empty / whitespace-only applies_to is normalised away."""
-    from tests.conftest import init_brain
-
-    brain = init_brain(tmp_path)
-    result = brain.correct("bad output", "good output", applies_to="   ")
-    assert "applies_to" not in result
-    assert "applies_to" not in result.get("data", {})
As per coding guidelines `tests/**`: "parametrized tests preferred for boundary conditions."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_scope_tagging.py` around lines 185 - 204, Combine the two tests
(test_applies_to_none_is_backward_compatible and
test_applies_to_empty_string_collapses_to_none) into one parametrized pytest
test that exercises the boundary cases for applies_to normalization and reduces
duplication: add a pytest.mark.parametrize over cases (e.g. an "omitted"
sentinel to call brain.correct without the applies_to kwarg, and
whitespace/empty-string values to pass as applies_to) and inside the test call
brain = init_brain(tmp_path) and invoke brain.correct either with or without
applies_to depending on the parameter, then assert the same invariants against
result ("applies_to" not in result, not in result.get("data", {}), and no tags
starting with "applies_to:") using the brain.correct symbol to locate the code
to change.

l for l in lessons
if l.scope_json and "applies_to" in l.scope_json
]
assert tagged, f"No lesson carried applies_to. scope_jsons={[l.scope_json for l in lessons]}"

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

Use an explicit cardinality assertion instead of truthiness.

assert tagged is less specific than asserting expected count semantics.

Suggested patch
-    assert tagged, f"No lesson carried applies_to. scope_jsons={[l.scope_json for l in lessons]}"
+    assert len(tagged) >= 1, f"No lesson carried applies_to. scope_jsons={[l.scope_json for l in lessons]}"
As per coding guidelines `tests/**`: "assertions check specific values not just truthiness."
📝 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
assert tagged, f"No lesson carried applies_to. scope_jsons={[l.scope_json for l in lessons]}"
assert len(tagged) >= 1, f"No lesson carried applies_to. scope_jsons={[l.scope_json for l in lessons]}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_scope_tagging.py` at line 226, Replace the vague truthiness check
`assert tagged` with an explicit cardinality assertion: compute a concrete count
(e.g., count = sum(1 for l in lessons if l.applies_to) or count = len([l for l
in lessons if l.applies_to])) and then assert the expected number (for example
`assert count == EXPECTED_COUNT` or at minimum `assert count > 0`) so the test
checks a specific numeric expectation rather than truthiness; update the
assertion line that currently references `tagged` and keep the diagnostic
`scope_jsons={[l.scope_json for l in lessons]}` in the failure message.

Gradata added a commit that referenced this pull request Apr 15, 2026
…vents

Add env-var-based platform detection (claude-code, cursor, windsurf,
mcp-server, anthropic-sdk, openai-sdk, raw-python) and enrich every
event emitted via _events.emit() with a platform_source key on its
data dict. No public API signatures change; detection runs at
event-emit time so correction signatures stay untouched and this
stacks cleanly on top of the applies_to scope work in PR #57.

- src/gradata/_platform.py: new detect_platform_source() helper,
  ordered checks (IDE beats SDK), GRADATA_PLATFORM_SOURCE override.
- src/gradata/_events.py: copy caller data dict, inject
  platform_source unless already present, preserve existing value
  (so callers/backfill tools can force it).
- tests/test_platform_source.py: 16 tests covering env detection,
  override, fallback, non-mutation of caller dicts, and coexistence
  with applies_to-style scope metadata.

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata Gradata merged commit 13549f6 into main Apr 15, 2026
16 checks passed
Gradata added a commit that referenced this pull request Apr 15, 2026
…vents (#66)

* feat(events): auto-detect and persist platform_source on correction events

Add env-var-based platform detection (claude-code, cursor, windsurf,
mcp-server, anthropic-sdk, openai-sdk, raw-python) and enrich every
event emitted via _events.emit() with a platform_source key on its
data dict. No public API signatures change; detection runs at
event-emit time so correction signatures stay untouched and this
stacks cleanly on top of the applies_to scope work in PR #57.

- src/gradata/_platform.py: new detect_platform_source() helper,
  ordered checks (IDE beats SDK), GRADATA_PLATFORM_SOURCE override.
- src/gradata/_events.py: copy caller data dict, inject
  platform_source unless already present, preserve existing value
  (so callers/backfill tools can force it).
- tests/test_platform_source.py: 16 tests covering env detection,
  override, fallback, non-mutation of caller dicts, and coexistence
  with applies_to-style scope metadata.

Co-Authored-By: Gradata <noreply@gradata.ai>

* refactor(platform): simplify hot-path import and collapse env-check tables

- _events.emit: hoist detect_platform_source import to module top so it
  isn't re-looked-up on every event emit; drop the impossible
  try/except-ImportError fallback (sibling module, can't fail to import).
  Collapse the two-line "check-then-set" to data.setdefault().
- _platform: collapse _PLATFORM_ENV_CHECKS + _SDK_ENV_CHECKS into one
  ordered tuple — iteration is identical and IDE-beats-SDK is already
  expressed by tuple order. Simplify override handling: strip once,
  return if truthy, else fall through. Extract _OVERRIDE_ENV / _FALLBACK
  constants.

No behavior change. 2273 tests pass, ruff clean.

Co-Authored-By: Gradata <noreply@gradata.ai>

---------

Co-authored-by: Gradata <noreply@gradata.ai>
@Gradata Gradata deleted the wt-scope-tagging branch April 17, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant