Skip to content

Add implementation plan for Behaviors to Requirements rename - #2199

Closed
nicolai-rhesis wants to merge 3 commits into
mainfrom
proposal/rename-behaviors-to-requirements
Closed

Add implementation plan for Behaviors to Requirements rename#2199
nicolai-rhesis wants to merge 3 commits into
mainfrom
proposal/rename-behaviors-to-requirements

Conversation

@nicolai-rhesis

@nicolai-rhesis nicolai-rhesis commented Jul 20, 2026

Copy link
Copy Markdown
Member

Purpose

Lock the decisions for renaming the Behavior domain entity to Requirement, and replace the original phased proposal with an implementation plan we can execute against.

This PR is still docs-only. No product code is renamed. Full plan: docs/proposals/rename-behaviors-to-requirements.md.

What Changed

  • Rewrote the proposal as an implementation plan with locked decisions, a re-audited inventory, a stored-data migration list, an execution order, and verification gates
  • Reversed the rollout strategy from phased/compatibility-first to a single atomic cutover with the DB rename folded in
  • Added the surfaces missing from the first inventory (see below)

Decisions locked

# Decision Rationale
1 Single atomic cutover, not phases 0-7 Behavior is chained through org bootstrap seed data, the Garak taxonomy (backend and SDK copies), the MCP tool contract, and stats/insights. A deprecation window means holding two vocabularies in sync across that chain for months, and the failure mode is a wrong label rather than a crash, so drift sits unnoticed.
2 DB Option B folded in Deferring it leaves the ORM mapping Requirement onto table behavior indefinitely, and every raw-SQL site keeps the old name. Two migrations for one end state.
3 No aliases, no dual-read, no dual API paths /behaviors has no external announcement or SLA. Building a compatibility layer in one release and tearing it out in another is cost with no consumer.
4 Frontend /behaviors keeps a permanent redirect The one exception to #3. It is a UI route with user bookmarks, not an API contract, and it costs one line.
5 Entity values are not renamed Reliability, Robustness, Compliance are referenced by name from initial_data.json, garak/taxonomy.py, and garak/detectors.yaml. The entity is renamed; its instances are not.
6 Stored historical values get a backfill, not dual-read Enumerated in the plan. Every one lands in the same Alembic revision as the schema rename.

Measured size

Measure Count
Files containing behavio (excluding node_modules, build output) 668
Matching lines ~6,450
Of those, in frozen migrations and changelogs ~200

Gaps found in the second audit

Database objects a table rename does not follow

Postgres does not rewrite function bodies, policy names, index names, or view output aliases.

  • alembic/delete_user_and_organization_data.sql is a PL/pgSQL function with a hardcoded table-name array and DELETE FROM behavior. After a rename it references a table that no longer exists, and it fails only at org-deletion time. Needs CREATE OR REPLACE FUNCTION.
  • Stats views alias b.name AS behavior_name. That alias is baked into the view definition, so the views need DROP and CREATE, not a rename.
  • app/services/annotations.py runs raw SQL joining behavior with behavior_id / behavior_name aliases. No ORM rename touches it.
  • Also: RLS policy names, FK and index names embedding behavior, the auto-RLS trigger table list, and the reference copy in alembic/row_level_security.sql.

Stored data that breaks silently

  • test_set.attributes['metadata']['behaviors'] is a JSONB key read by the filter at crud.py:587 that excludes explorer test sets from the main test-sets list. Rename the key without a backfill and every historical explorer test set reappears in that list. Silent and user-visible.
  • architect_session.plan_data persists the Architect plan with keys behaviors, behavior_metric_mappings, behavior. Renaming the Pydantic fields in sdk/.../architect/plan.py without backfilling breaks deserialization of every saved session.
  • Preflight check id behavior_metric_coverage, MetricsSource.BEHAVIOR, stats mode behavior and metric key behavior_pass_rates, type_lookup row Behavior, PlanCategory.BEHAVIOR.
  • RBAC is narrower than first framed: built-in roles compute permission sets from code, so they follow the enum rename automatically. Custom roles have role_permission rows keyed on permission.id, so the migration must UPDATE permission SET name in place. Delete-and-insert orphans every custom grant.

Areas absent from the first inventory

  • skills/rhesis/ (21 files, ~200 refs). Published and installed by users via npx skills add rhesis-ai/rhesis. references/tool-catalog.md mirrors the MCP tool names and must move with mcp_tools.yaml.
  • penelope/ (19 files), a separate package with prompt templates and 9 examples.
  • Vocabulary collision. skills/rhesis/references/requirements-workflow.md and docs/content/docs/agent-skill/requirements.mdx already use "Requirements" to mean the input PRD, with behaviors as the derived entity. After the rename that reads as "build requirements from your requirements." Recommend renaming the input concept to "spec".
  • docs/sphinx/source/rhesis.entities.rst has .. autoclass:: rhesis.sdk.entities.Behavior, which breaks the SDK API docs build if missed.
  • docs/content/glossary/glossary-terms.jsonl (35 lines of structured data, separate from the glossary MDX pages), and docs/src/public/diagrams/user-journey.excalidraw, which needs re-export rather than a text edit.
  • docs/content/contribute/backend/odata-guide.mdx (39 refs). OData navigation property names come from ORM relationship names, so $filter=behavior/name is a user-facing contract that changes.
  • SDK _BEHAVIOR_TOOLS in agents/architect/agent.py hardcodes MCP tool names, so it must land in the same commit as mcp_tools.yaml or Architect silently stops recognizing entity tool calls.
  • apps/frontend/src/config/test-templates.generated.ts must be regenerated via scripts/generate-templates.js, not hand-edited.
  • NEXT_PUBLIC_BEHAVIORS_EMPTY_STATE_VIDEO_URL and ..._ARTICLE_URLS are set in the deploy environment, outside any diff.

Execution shape

668 files cannot be a 400-line PR. The plan proposes:

  1. 4 prep PRs, each small and behavior-preserving: resolve the vocabulary collision, extract hardcoded string literals into the existing frontend and backend constants modules, and add regression tests for the two silent-failure paths before touching them.
  2. One atomic cutover PR with one Alembic revision and ordered commits (migration, backend, MCP + SDK tools together, SDK, frontend, tests, tooling, contract docs). Not splittable without a broken intermediate state.
  3. Follow-ups for docs prose, glossary, notebooks, and prompt-text triage.

Plus a release checklist for the deploy-environment env vars, the migrate-before-deploy ordering, and the SDK major bump.

Verification gates

Line-by-line review cannot carry a 6,000-line diff, so the plan leans on mechanical checks: a source sweep against an explicit allowlist of things we deliberately do not rename, eight Postgres catalog queries (including pg_proc.prosrc, which is what catches the delete-org function), row-level assertions against a restored production snapshot, behavioral checks for the silent-failure paths, and a tested alembic downgrade rehearsal.

Additional Context

  • Supersedes the phased plan in the original description of this PR
  • Breaking for API and SDK consumers by design, per decision npm warn deprecated inflight@1.0.6 #3
  • The schema rename is not backward-compatible with old app code, so this needs a short maintenance window rather than a rolling deploy

Still open

  1. Vocabulary collision: "spec" for the PRD input, or accept the overload?
  2. test-templates.yml prompt English: change and regenerate, or leave?
  3. Metric prompt English ("refusal behavior", "forbidden behaviors"): separate evaluated change, or leave indefinitely?
  4. Confirm the maintenance window is acceptable.

Testing

N/A. Documentation only. The plan's own verification gates apply to the implementation PRs that follow.

Add a draft proposal for feedback covering inventory, phased migration,
compatibility strategy, and open decisions before any code changes.
@harry-rhesis

Copy link
Copy Markdown
Contributor

Thanks @nicolai-rhesis, this is a thorough proposal. The inventory and phase breakdown made it easy to audit for gaps, and the compatibility matrix is a genuinely useful reference regardless of which rollout approach we pick.

On phased vs. big bang

I'd like to make the case for doing this as a single PR (DB, backend, frontend, SDK together) rather than the phased, compatibility-first rollout in Phases 1-7.

The core issue is that Behavior isn't an isolated entity. It's woven through a chain: org bootstrap seed data defines the default behavior names, the Garak security-probe taxonomy hardcodes those same names on both the backend and the SDK, the MCP tool contract exposes them as tool names to external agents, and stats/insights read them back out. A phased rollout means holding two vocabularies in sync across that entire chain for months; every new org seeded during the deprecation window, every Garak probe mapping, every stats view has to agree on which name is authoritative at that moment. That's a lot of surface area for the old and new names to quietly drift out of sync, and the failure mode is subtle (wrong label somewhere, not a crash) so it can sit unnoticed for a while.

There's also a cost multiplier: Phase 2 stands up dual API paths and dual-read, and Phase 7 tears them back out. That's building the compatibility layer once and then unbuilding it later, for the same end state we'd reach directly with one migration. Given this is an internal product entity with no existing external announcement or SLA around /behaviors, I think we can afford a clean cutover instead of amortizing the risk over multiple releases.

Concretely I'd propose: rename tables, columns, routes, permission strings, MCP tool names, enums, and stored-value writers all in the same PR (DB Option B folded into the main change, not deferred), with no aliases or dual-read. It's a bigger single PR, but it's one atomic, testable change instead of two.

Blind spots found while auditing against the inventory

I went through the codebase looking for anywhere Behavior shows up that isn't in the current inventory. Flagging these so the plan accounts for them regardless of which rollout approach we go with:

Backend / data pipeline

  • app/services/initial_data.json seeds every new org with an entity_type catalog row "Behavior" and default behaviors ("Reliability", "Robustness", "Compliance") plus around 25 metric records referencing them by name. Not mentioned anywhere in the inventory.
  • Garak integration hardcodes behavior names on both sides: app/services/garak/taxonomy.py has around 30 behavior="Robustness"/"Compliance"/"Reliability" literals feeding sync.py, importer.py, and dynamic.py, and the SDK has a parallel copy in sdk/.../metrics/providers/garak/detectors.yaml and registry.py. These need to stay name-consistent with the seed data above.
  • The MCP tool contract in app/mcp_server/mcp_tools.yaml defines 7 production tools (list_behaviors, get_behavior, create_behavior, update_behavior, add_behavior_to_metric, remove_behavior_from_metric, get_metric_behaviors), a hardcoded entity_type: "Behavior" tag-assignment constraint, and planning-prompt text in server.py. This is a public tool surface for external AI agents, it deserves the same weight as the REST API rename, not just a one-line mention under Phase 5.
  • Celery execution code joins on behavior outside routers/services: tasks/execution/metrics_utils.py::get_behavior_metrics, tasks/execution/executors/data.py, tasks/test_set.py.
  • scripts/check_organization_filtering.py (repo root, multi-tenancy audit tooling) hardcodes 'Behavior' and behavior_id in its list of models checked for org scoping. If missed, it would silently stop auditing the renamed entity.

Frontend

  • apps/frontend/src/constants/capabilities.ts independently defines Capability.Behavior.*, separate from the EE copy in ee/frontend/src/rbac/capability-groups.ts that the inventory cites. Both need to move together.
  • Four separate TypeScript interfaces shape Behavior data (interfaces/behavior.ts, interfaces/test-results.ts, interfaces/metric.ts, interfaces/tests.ts). All four need consistent renaming.
  • React Query cache keys and hooks (behaviorKeys, useBehaviors, useBehaviorInsightsData) aren't covered by "routes + clients" framing, and a partial rename here fragments the query cache silently.
  • Form components (BehaviorDrawer.tsx, the create/edit dialog, and SelectBehaviorsDialog.tsx, a reusable picker used from other entity flows) live outside the /behaviors route tree.
  • CSV import UI text in FileImportDrawer.tsx is separate from the backend CSV column already in the inventory.
  • Env vars NEXT_PUBLIC_BEHAVIORS_EMPTY_STATE_VIDEO_URL / ..._ARTICLE_URLS are deployment config, easy to miss in a code only search.

Docs

  • The glossary has a dedicated docs/content/glossary/behavior/index.mdx page cross-referenced from the metric, tag, explorer, and synthesizer glossary entries. Needs a companion "Requirement" entry plus updated cross-references, separate from the docs/content/docs/behaviors/ folder already listed.
  • docs/content/docs/_meta.tsx (sidebar label) and docs/src/next.config.mjs (redirects array) don't have a /behaviors entry yet, that redirect rule needs to be added from scratch, not modified.
  • API docs are hand-written MDX, not generated from OpenAPI, so a backend rename will not auto-propagate. Every doc page referencing /behaviors/* needs a manual pass.

Tests / tooling

  • tests/sdk/integration/conftest.py runs raw SQL TRUNCATE TABLE metric, behavior, model CASCADE, a direct dependency on the physical table name.
  • tests/backend/routes/conftest.py has a parametrized fixture shared across 5 unrelated entities (behavior, topic, category, metric, dimension). Worth being careful here since touching it affects other entities' test coverage, not just Behavior's.

Happy to help turn this into the single-PR plan if that direction makes sense to the team.

@harry-rhesis harry-rhesis changed the title Propose renaming Behaviors to Requirements Add implementation plan for Behaviors to Requirements rename Aug 12, 2026
@harry-rhesis

Copy link
Copy Markdown
Contributor

Closing in favour of #2465, which carries the plan and the implementation on feat/rename-requirements (branched from current main).

The strategy discussion in this thread is what settled the approach, so it stays here as the record: phased/compatibility-first was rejected in favour of a single atomic cutover with the DB rename folded in and no aliases.

Two corrections came out of re-verifying the inventory against current main (d5d4b11d0) rather than the branch point of this PR:

  • app/crud.py is now the package app/crud/, so the file references in the earlier plan were stale. There is a crud/behavior.py to rename.
  • The explorer JSONB marker is already migrated to the test_set.explorer_row column (7dd69fe35db5), and ADAPTIVE_TESTING_BEHAVIOR is gone from the backend. That removes what was the worst silent-failure risk in the plan, and tests/backend/alembic/test_explorer_row_migration.py is now a worked example for the data migration we do need.

Footprint re-measured at 701 files / ~6,540 lines.

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.

2 participants