Synnovator
File-based data engine for the Synnovator platform. All records are .md files with YAML frontmatter + Markdown body, stored under PROJECT_DIR/.synnovator/.
Quick Start
# Initialize data directory uv run python .claude/skills/synnovator/scripts/engine.py --init # CRUD operations uv run python .claude/skills/synnovator/scripts/engine.py [--user USER_ID] COMMAND TYPE [OPTIONS]
Commands: create, read, update, delete
Data Model
7 content types stored as .md files in .synnovator/<type>/:
- •
category- Activities/competitions (YAML + Markdown body) - •
post- User posts with tags (YAML + Markdown body) - •
resource- File attachments (YAML frontmatter only) - •
rule- Activity rules with scoring criteria (YAML + Markdown body) - •
user- User accounts (YAML frontmatter only) - •
group- Teams/groups (YAML frontmatter only) - •
interaction- Likes, comments, ratings (YAML frontmatter only). Target info is stored intarget_interactionrelation, not on the interaction itself.
9 relationship types stored in .synnovator/relations/<type>/:
- •
category_rule- Activity-to-rule bindings - •
category_post- Activity-to-post submissions - •
category_group- Team activity registration - •
category_category- Activity association (stage / track / prerequisite) - •
post_post- Post references/embeds/replies - •
post_resource- Post-to-attachment links - •
group_user- Group membership (with approval workflow) - •
user_user- User follow/block relationships - •
target_interaction- Content-to-interaction bindings
See references/schema.md for complete field definitions.
Content CRUD
Create
engine.py [--user UID] create <type> --data '<json>' [--body 'markdown content']
Auto-generates: id, created_at, updated_at, deleted_at. Sets created_by from --user.
Read
engine.py read <type> --id <record_id> # Single record engine.py read <type> --filters '<json>' # Filtered list engine.py read <type> --include-deleted # Include soft-deleted
Update
engine.py update <type> --id <record_id> --data '<json>'
For tags: "+tagname" appends, "-tagname" removes. Body: "_body": "new markdown".
Delete
engine.py delete <type> --id <record_id> # Soft delete (default) engine.py delete <type> --id <record_id> --hard # Hard delete
Cascades: deleting a post removes its relations and soft-deletes linked interactions.
Relation CRUD
engine.py create <rel_type> --data '<json>' # Create engine.py read <rel_type> --filters '<json>' # Read engine.py update <rel_type> --filters '<json>' --data '<json>' # Update engine.py delete <rel_type> --filters '<json>' # Delete (hard)
Use _ or : separator: category_rule or category:rule.
Key Behaviors
- •Soft delete: Sets
deleted_at, record stays on disk. Default for content types. - •Hard delete: Removes file. Default for relations.
- •Cache stats:
like_count,comment_count,average_ratingon posts are read-only — auto-recalculated whentarget_interactionrelations are created/deleted. Manual updates to these fields are silently ignored. - •Two-step interactions: Creating an interaction requires: (1)
create interactionfor the record, (2)create target_interactionto link it to a target. Cache updates, duplicate-like checks, and target validation happen at step 2. - •Group approval:
require_approval=truesets join status topending; owner approves viaUPDATE group_user. - •Uniqueness: Enforced for user
(username),(email); like(user, target)attarget_interactioncreation; relation duplicates; user-per-category-per-group (a user can only belong to one group per category);user_user(source_user_id, target_user_id, relation_type);category_category(source_category_id, target_category_id). - •Self-reference prevention:
user_userandcategory_categorycannot reference the same entity as both source and target. - •Block enforcement: If user B blocks user A, A cannot follow B.
- •Circular dependency detection:
category_categorywithstageorprerequisiterelation types prevents cycles (A→B→C→A). - •Prerequisite enforcement: Creating a
category_groupregistration checks that all prerequisite categories (viacategory_categoryprerequisite) are closed. - •Cascades: Deleting content cascades to relations and interactions per the schema spec. Notably:
- •Deleting a group cascades to both
group_userandcategory_grouprelations. - •Deleting a user cascades to
group_useranduser_userrelations and soft-deletes all user interactions. - •Deleting a category cascades to
category_rule,category_post,category_group, andcategory_categoryrelations and soft-deletes linked interactions.
- •Deleting a group cascades to both
Rule Enforcement
Rules linked to a category via category_rule are automatically enforced via a hook system. Rules support two definition styles: fixed fields (backward-compatible shorthand) and declarative checks (extensible condition-action pairs). All rules must pass (AND logic); any single violation rejects the operation.
Hook Points
| Operation | Phase | Validations |
|---|---|---|
create category_post | pre | Time window, max submissions, submission format, min team size, resource requirements, entry prerequisites |
create category_post | post | Post-submission actions (notifications, etc.) |
create group_user | pre | Max team size (checked against all categories the group is registered for) |
create group_user | post | Post-join actions |
create category_group | pre | Prerequisite categories closed, entry conditions (team/proposal existence) |
create category_group | post | Post-registration actions |
update post.status | pre | allow_public / require_review path enforcement |
update post.status | post | Post-status-change actions |
update category.status | pre | Pre-closure validation |
update category.status | post | Closure actions: disqualification flagging, ranking computation, certificate awarding |
Phases
- •pre: Runs before the operation.
on_fail: denyblocks the operation.on_fail: warnallows with warning.on_fail: flagmarks but allows. - •post: Runs after a successful operation. Never blocks. Used for compute/award/notify actions.
Validation Chain
category_post: category → category_rule → rule.checks[trigger=create_relation(category_post)] group_user: group → category_group → category → category_rule → rule.checks[trigger=create_relation(group_user)] category_group: category → category_category(prerequisite) + category_rule → rule.checks[trigger=create_relation(category_group)] post.status: post → category_post → category → category_rule → rule.checks[trigger=update_content(post.status)] category.status: category → category_rule → rule.checks[trigger=update_content(category.status)]
Fixed Fields as Syntactic Sugar
Fixed fields on Rule (max_submissions, min_team_size, submission_format, etc.) are automatically expanded into equivalent checks entries by the engine. User-defined checks are appended after expanded entries.
| Fixed Field | Expands To |
|---|---|
submission_start / submission_deadline | time_window check on create_relation(category_post) pre |
max_submissions | count check on create_relation(category_post) pre |
submission_format | resource_format check on create_relation(category_post) pre |
min_team_size | count check on create_relation(category_post) pre |
max_team_size | count check on create_relation(group_user) pre |
allow_public / require_review | field_match check on update_content(post.status) pre |
Condition Types
| Type | Description |
|---|---|
time_window | Current time within [start, end] |
count | Entity/relation count satisfies op + value |
exists | Entity/relation exists (or must not exist) |
field_match | Entity field matches a predicate |
resource_format | Post resources match allowed formats |
resource_required | Post has minimum required resources |
unique_per_scope | Uniqueness within a scope |
aggregate | Aggregated value (count/sum/avg) meets threshold |
Action Types (post phase only)
| Action | Description |
|---|---|
flag_disqualified | Mark entities that fail post-closure checks |
compute_ranking | Calculate rankings from average_rating |
award_certificate | Auto-create certificate resources and posts per rank range |
notify | Send notifications (reserved, not yet implemented) |
Behavior
- •No rules linked → no constraints, operation proceeds normally.
- •Rule field absent → that constraint is skipped (e.g., no
submission_deadlinemeans no time limit). - •Violation (pre) → operation rejected with error:
Rule '<name>': <reason>. - •Violation (post) → action executes (flag/compute/award), does not rollback.
- •Posts not linked to any category are unconstrained (rules only apply through
category_post).
See docs/rule-engine.md for the full specification.
File Format
Each record is a .md file:
--- name: 2025 AI Hackathon type: competition status: published id: cat_abc123 created_by: user_alice created_at: '2025-01-01T00:00:00Z' --- ## Activity Description Markdown content here.
Testing
Tests are organized as modular files under scripts/tests/, one per test-case spec in specs/testcases/.
# Run all tests uv run python .claude/skills/synnovator/scripts/tests/run_all.py # Run specific test files by number uv run python .claude/skills/synnovator/scripts/tests/run_all.py 3 4 11 # Run a single test file directly uv run python .claude/skills/synnovator/scripts/tests/test_11_user_journeys.py
Test File Map
| # | File | TC Prefix | Coverage |
|---|---|---|---|
| 1 | test_01_user.py | TC-USER-* | User CRUD |
| 2 | test_02_category.py | TC-CAT-* | Category CRUD |
| 3 | test_03_rule.py | TC-RULE-* | Rule CRUD + enforcement |
| 4 | test_04_group.py | TC-GRP-* | Group CRUD + approval |
| 5 | test_05_post.py | TC-POST-* | Post CRUD + visibility |
| 6 | test_06_resource.py | TC-RES-* | Resource CRUD |
| 7 | test_07_interaction.py | TC-IACT-* | Interaction CRUD |
| 8 | test_08_relations.py | TC-REL-* | All 9 relation types |
| 9 | test_09_cascade_delete.py | TC-DEL-* | Cascade delete |
| 10 | test_10_permissions.py | TC-PERM-* | Access control |
| 11 | test_11_user_journeys.py | TC-JOUR-* | Integration (sequential) |
| 12 | test_12_resource_transfer.py | TC-TRANSFER-* | Resource transfer |
| 13 | test_13_user_follow.py | TC-FRIEND-* | Follow/block |
| 14 | test_14_category_association.py | TC-STAGE/TRACK/PREREQ/CATREL-* | Category associations |
| 15 | test_15_entry_rules.py | TC-ENTRY-* | Entry preconditions |
| 16 | test_16_closure_rules.py | TC-CLOSE-* | Closure rules |
| 17 | test_17_rule_engine.py | TC-ENGINE-* | Rule engine conditions/actions |
Each file uses isolated data directories for parallel-safe execution. Shared fixtures are in scripts/tests/base.py.
See references/endpoints.md for detailed API examples. See references/schema.md for complete field and enum definitions.
Documentation Sync
This skill's data model is the implementation of the canonical specs in docs/ and specs/. When modifying the skill's schema, engine logic, or data model, must sync the following docs:
| Skill file | Canonical docs | Sync what |
|---|---|---|
references/schema.md | docs/data-types.md, docs/relationships.md | Field definitions, enums, required fields, relation schemas |
scripts/engine.py (CRUD logic) | docs/crud-operations.md, specs/data-integrity.md | CRUD operations, cascade strategies, uniqueness constraints |
scripts/engine.py (cache) | specs/cache-strategy.md | Cache stats triggers, read-only enforcement |
scripts/tests/test_11_user_journeys.py | docs/user-journeys.md | User journey steps, data operation sequences |
scripts/tests/test_*.py | specs/testcases/*.md | Test cases aligned 1:1 with spec numbering |
Checklist when updating the skill:
- •Update skill code (
engine.py) and tests (scripts/tests/test_*.py) - •Update skill reference docs (
references/schema.md,references/endpoints.md) - •Update canonical docs (
docs/data-types.md,docs/relationships.md,docs/crud-operations.md,docs/user-journeys.md) - •Run
uv run python .claude/skills/synnovator/scripts/tests/run_all.pyto verify