Reference
Rye Conventions Catalog
Adopt consistent conventions so Rye stays extensible without schema migrations.
Source file: docs/conventions-catalog.md
Rye Conventions Catalog
Type Conventions
Types are open conventions — write a new value and it exists, no migration required.
- Node types (
node_type):person,org,project,task,opportunity,pipeline,ticket,parcel,document,incident,release,component,product - Edge types (
edge_type):employs,assigned_to,project_member,blocks,depends_on,regarding,applied_to,targets,references,affects,triggered_by,contains,impacted,owns,adjacent_to - Assertion types (
assertion_type):project_status,task_status,deal_stage,health_score,churn_risk,sentiment,ownership,title_opinion,interview_feedback,candidate_stage,ticket_status,decision_status - Event types (
event_type):meeting,phone_call,email,escalation,incident_update,interview,agent_query,domain_change,task_created,status_change,comment,time_log,dispute_raised,dispute_resolved,opportunity_created,node_merge
Run SELECT rye_catalog() to see which types are in use in a given instance.
Assertion Key Convention
Use assertion_key to define uniqueness scope for active facts:
- Single-valued facts:
assertion_key = 'default'- Examples:
task_status,deal_stage,project_status,health_score,churn_risk - Supersede with
supersede_assertion(...)
- Examples:
- Multi-valued facts: use a stable domain key
ownershipkeyed byowner:<node_id>candidate_stagekeyed byrole:<opportunity_code>interview_feedbackkeyed byround:<round_name>
The unique index on (assertion_type, assertion_key, subject_node_id) WHERE superseded_at IS NULL prevents duplicate active facts for the same key.
Assertion Write Convention
- INSERT assertions directly (subject to RLS and assertion-type gating).
- Assertion content is immutable after insert. Only
superseded_atandsuperseded_bymay be updated. - Use
supersede_assertion(...)to replace single-valued facts when you’re certain the new value is correct. - Use
contest_assertion(...)when contradictory information arrives but you’re uncertain which is correct. Both claims coexist until resolved. - Use
resolve_dispute(...)to pick a winner among competing assertions. - Do not run direct
UPDATE assertions— RLS blocks it outside the supersession function context.
Assertion Dispute Convention
When new information contradicts an existing assertion but the correct answer is uncertain:
- Call
contest_assertion(existing_id, new_claim, source, confidence, reason). - The competing assertion uses
assertion_key = 'contested:<source>'so both coexist. - A
dispute_raisedevent is recorded for audit. - Query
active_disputesto find all unresolved conflicts. - Call
resolve_dispute(winning_assertion_id, reason, actor)to resolve. - A
dispute_resolvedevent is recorded. Losers are superseded. If the winner had acontested:key, it’s promoted todefault.
-- Contest an existing ownership assertion
SELECT contest_assertion(
p_existing_assertion_id := '<assertion_uuid>',
p_new_claim := '{"fraction": 0.125}',
p_source := 'county_filing_2024_0892',
p_confidence := 0.7,
p_reason := 'County filing shows different fraction'
);
-- Find unresolved disputes
SELECT * FROM active_disputes;
-- Resolve in favor of the contested assertion
SELECT resolve_dispute(
p_winning_assertion_id := '<contested_assertion_uuid>',
p_reason := 'Confirmed by title examiner',
p_actor := 'user:alice'
);
Artifact Convention
- Use
record_artifact()for all artifact creation. - Pass
p_content_hashto prevent duplicate artifacts from the same source material. - If a matching artifact exists (same type and content hash), returns the existing ID.
- Agents can create artifacts (INSERT is allowed for all roles).
SELECT record_artifact(
p_artifact_type := 'document_parse',
p_content := '{"title": "Q4 Report", "sections": [...]}',
p_source_event_id := '<parse_event_uuid>',
p_source_node_id := '<document_node_uuid>',
p_content_hash := 'sha256:abc123...'
);
Event Convention
- Use
record_event()for all event creation. Never insert intoeventsandevent_participantsseparately. - Every event should have at least one participant. Events with no participants are invisible under RLS.
- Use
p_actorto identify who or what caused the event (e.g.,'user:alice','agent:triage-bot','system:cdc'). - Events are immutable — never update or delete them.
Classification Convention
- Nodes with
attrs->'teams'(non-empty array) must haveattrs->>'classification'set. - The classification enforcement trigger rejects nodes with teams but no classification.
- Valid classification values:
internal,confidential,restricted(or other domain-appropriate values). - Nodes without teams or classification are public — visible to all users.
-- Team-scoped node (classification required)
INSERT INTO nodes (node_type, label, attrs)
VALUES ('task', 'Build feature X',
'{"classification": "internal", "teams": ["engineering"]}');
-- Public node (no classification needed)
INSERT INTO nodes (node_type, label, properties)
VALUES ('pipeline', 'Sales Pipeline', '{"code": "PL-SALES"}');
Domain Integration Convention
Domain tables are the system of record. Rye connects them without modifying them.
- Use
link_record(schema, table, id, node_type, label, properties)to connect domain table rows to the graph. Each distinctsource_idcreates a new node; same(schema, table, source_id)updates the existing one. Looks upnode_source_mapfirst, thenexternal_id/external_source. - Use
link_records_batch(schema, table, ids[], type, labels[], properties[])for bulk imports — processes multiple records in a single call. - Use
track_table(schema, table)to attach CDC triggers for automatic change tracking. Supports tables with any PK column name. - CDC events have type
domain_changeand includechanged_fieldswith before/after diffs. - Only rows with a linked node (in
node_source_map) produce CDC events. Unlinked rows are silently skipped.
Human-Readable Code Convention
Codes follow the format {PREFIX}-{YYMM}-{SEQ} (e.g., OPP-2403-0042, TSK-2403-0187).
- Generated by
generate_crm_code(prefix). - Counters reset per prefix per month.
- Concurrency-safe via
INSERT ... ON CONFLICT DO UPDATE.
Edge Temporal Convention
- Active edges:
archived_at IS NULL. - Temporal bounds:
effective_from/effective_to. - To end a relationship, set
archived_atoreffective_to. Do not delete edges.
Soft Delete Convention
- All core tables use
archived_atfor soft deletion. archived_at IS NULL= active.- Views and queries filter on
archived_at IS NULLby default.
Session Variable Convention
Authorization uses session variables, not database roles:
SET LOCAL "app.current_user_id" = 'user:alice';
SET LOCAL "app.current_teams" = 'engineering,sales';
SET LOCAL "app.current_role" = 'team_member';
Role hierarchy: admin > manager > team_member > viewer > agent.
View Convention
All views must use security_invoker = true (PostgreSQL 15+) so that RLS policies are evaluated using the calling session’s permissions.
| View | Purpose |
|---|---|
current_assertions | Non-superseded assertions only |
node_context | Full node context with edges and assertions |
nodes_secure | Nodes with field-level redaction applied |
active_disputes | Subjects with competing active assertions |
Materialized View Convention
Profile materialized views (opportunities_active, contacts_directory, task_board) need periodic refreshing.
- Use
refresh_materialized_views()to refresh all installed profile views. - Uses
CONCURRENTLY— reads continue during refresh. - Safe to call regardless of which profiles are installed.
Security Configuration Convention
Security gating is data-driven. To add a new sensitive assertion type or role:
- Insert into
assertion_type_accessto gate read/write on specific assertion types. - Insert into
role_classification_accessto define which classification levels a role can access. - No SQL policy changes or migrations needed for new types or roles.
Profiles
- CRM profile:
schema/migrations/0100_profile_crm.sql - PM profile:
schema/migrations/0110_profile_pm.sql - Profile migrations use
*_profile_<name>.sqlnaming. - Enable during install with
--profiles crm,pm.