Implement AI workspace and mitigation workflow

This commit is contained in:
2026-02-15 04:27:28 +00:00
parent de2b9a9bbb
commit 2d3b8fdac6
64 changed files with 7669 additions and 769 deletions

View File

@@ -1,7 +1,8 @@
# Generated by Django 5.1.5 on 2025-02-06 21:57
import django.db.models.deletion
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

View File

@@ -1,7 +1,8 @@
# Generated by Django 5.1.5 on 2025-02-07 12:05
import django.db.models.deletion
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

View File

@@ -1,7 +1,8 @@
# Generated by Django 5.1.5 on 2025-02-07 13:56
import django.db.models.deletion
import uuid
import django.db.models.deletion
from django.db import migrations, models

View File

@@ -1,7 +1,8 @@
# Generated by Django 5.1.5 on 2025-02-08 16:07
import django.db.models.deletion
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

View File

@@ -0,0 +1,95 @@
# Generated by Django 5.2.11 on 2026-02-14 22:52
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0015_manipulation_filter_enabled_alter_manipulation_mode'),
]
operations = [
migrations.CreateModel(
name='AIRequest',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('window_spec', models.JSONField(default=dict)),
('message_ids', models.JSONField(blank=True, default=list)),
('user_notes', models.TextField(blank=True, default='')),
('operation', models.CharField(choices=[('summarise', 'Summarise'), ('draft_reply', 'Draft Reply'), ('critique', 'Critique'), ('repair', 'Repair'), ('extract_patterns', 'Extract Patterns'), ('memory_propose', 'Memory Propose')], max_length=32)),
('policy_snapshot', models.JSONField(blank=True, default=dict)),
('status', models.CharField(choices=[('queued', 'Queued'), ('running', 'Running'), ('done', 'Done'), ('failed', 'Failed')], default='queued', max_length=16)),
('error', models.TextField(blank=True, default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('started_at', models.DateTimeField(blank=True, null=True)),
('finished_at', models.DateTimeField(blank=True, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='AIResult',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('summary_m3', models.TextField(blank=True, default='')),
('draft_replies', models.JSONField(blank=True, default=list)),
('risk_flags', models.JSONField(blank=True, default=list)),
('memory_proposals', models.JSONField(blank=True, default=list)),
('citations', models.JSONField(blank=True, default=list)),
('created_at', models.DateTimeField(auto_now_add=True)),
('ai_request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='result', to='core.airequest')),
],
),
migrations.CreateModel(
name='WorkspaceConversation',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('title', models.CharField(blank=True, default='', max_length=255)),
('platform_type', models.CharField(choices=[('signal', 'Signal'), ('instagram', 'Instagram')], default='signal', max_length=255)),
('platform_thread_id', models.CharField(blank=True, default='', max_length=255)),
('last_event_ts', models.BigIntegerField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('participants', models.ManyToManyField(blank=True, related_name='workspace_conversations', to='core.person')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_conversations', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='MessageEvent',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('ts', models.BigIntegerField(db_index=True)),
('direction', models.CharField(choices=[('in', 'Inbound'), ('out', 'Outbound')], max_length=8)),
('sender_uuid', models.CharField(blank=True, db_index=True, default='', max_length=255)),
('text', models.TextField(blank=True, default='')),
('attachments', models.JSONField(blank=True, default=list)),
('raw_payload_ref', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='core.workspaceconversation')),
],
options={
'ordering': ['ts'],
},
),
migrations.CreateModel(
name='MemoryItem',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('type', models.CharField(choices=[('M1', 'Durable Fact/Preference'), ('M2', 'Relationship State'), ('M3', 'Conversation Working Summary')], max_length=2)),
('status', models.CharField(choices=[('proposed', 'Proposed'), ('active', 'Active'), ('deprecated', 'Deprecated')], default='proposed', max_length=16)),
('content', models.JSONField(blank=True, default=dict)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('source_request', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.airequest')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memory_items', to='core.workspaceconversation')),
],
),
migrations.AddField(
model_name='airequest',
name='conversation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ai_requests', to='core.workspaceconversation'),
),
]

View File

@@ -0,0 +1,359 @@
# Generated by Django 5.2.11 on 2026-02-15 00:14
import core.models
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0016_airequest_airesult_workspaceconversation_and_more'),
]
operations = [
migrations.RemoveField(
model_name='airesult',
name='risk_flags',
),
migrations.RemoveField(
model_name='airesult',
name='summary_m3',
),
migrations.RemoveField(
model_name='memoryitem',
name='type',
),
migrations.AddField(
model_name='airesult',
name='interaction_signals',
field=models.JSONField(blank=True, default=list, help_text="Structured positive/neutral/risk signals inferred for this run. Example item: {'label':'repair_attempt','valence':'positive','message_event_ids':[...]}."),
),
migrations.AddField(
model_name='airesult',
name='user',
field=models.ForeignKey(default=core.models.get_default_workspace_user_pk, help_text='Owner of this AI result row (required for restricted CRUD filtering).', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_ai_results', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='airesult',
name='working_summary',
field=models.TextField(blank=True, default='', help_text='Conversation working summary generated for this run.'),
),
migrations.AddField(
model_name='memoryitem',
name='memory_kind',
field=models.CharField(choices=[('fact', 'Durable Fact/Preference'), ('state', 'Relationship State'), ('summary', 'Conversation Working Summary')], default=1, help_text='Memory kind: fact/state/summary.', max_length=16),
preserve_default=False,
),
migrations.AddField(
model_name='messageevent',
name='source_system',
field=models.CharField(choices=[('signal', 'Signal'), ('xmpp', 'XMPP'), ('workspace', 'Workspace'), ('ai', 'AI')], default='signal', help_text='System that produced this event record.', max_length=32),
),
migrations.AddField(
model_name='messageevent',
name='user',
field=models.ForeignKey(default=core.models.get_default_workspace_user_pk, help_text='Owner of this message event row (required for restricted CRUD filtering).', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_message_events', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='workspaceconversation',
name='commitment_confidence',
field=models.FloatField(default=0.0, help_text='Confidence in commitment scores (0.0-1.0).'),
),
migrations.AddField(
model_name='workspaceconversation',
name='commitment_inbound_score',
field=models.FloatField(blank=True, help_text='Estimated commitment score for counterpart -> user direction (0-100). Null while calibrating.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='commitment_last_computed_at',
field=models.DateTimeField(blank=True, help_text='Timestamp of the latest commitment computation.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='commitment_outbound_score',
field=models.FloatField(blank=True, help_text='Estimated commitment score for user -> counterpart direction (0-100). Null while calibrating.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='last_ai_run_at',
field=models.DateTimeField(blank=True, help_text='Last time any AIRequest finished for this conversation.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='participant_feedback',
field=models.JSONField(blank=True, default=dict, help_text="Per-person interaction feedback map keyed by person UUID. Example: {'<person_uuid>': {'state': 'withdrawing', 'note': 'short replies'}}."),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_confidence',
field=models.FloatField(default=0.0, help_text='Confidence in stability_score (0.0-1.0).'),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_last_computed_at',
field=models.DateTimeField(blank=True, help_text='Timestamp of the latest stability computation.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_sample_days',
field=models.PositiveIntegerField(default=0, help_text='How many calendar days of data were used for stability.'),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_sample_messages',
field=models.PositiveIntegerField(default=0, help_text='How many messages were used to compute stability.'),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_score',
field=models.FloatField(blank=True, help_text='Relationship stability score (0-100). Null while calibrating.', null=True),
),
migrations.AddField(
model_name='workspaceconversation',
name='stability_state',
field=models.CharField(choices=[('calibrating', 'Calibrating'), ('stable', 'Stable'), ('watch', 'Watch'), ('fragile', 'Fragile')], default='calibrating', help_text='UI label for relationship stability, baseline-aware.', max_length=32),
),
migrations.AlterField(
model_name='airequest',
name='conversation',
field=models.ForeignKey(help_text='Conversation analyzed by this request.', on_delete=django.db.models.deletion.CASCADE, related_name='ai_requests', to='core.workspaceconversation'),
),
migrations.AlterField(
model_name='airequest',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Request creation timestamp.'),
),
migrations.AlterField(
model_name='airequest',
name='error',
field=models.TextField(blank=True, default='', help_text="Error details when status='failed'."),
),
migrations.AlterField(
model_name='airequest',
name='finished_at',
field=models.DateTimeField(blank=True, help_text='Worker completion timestamp.', null=True),
),
migrations.AlterField(
model_name='airequest',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this AI request.', primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='airequest',
name='message_ids',
field=models.JSONField(blank=True, default=list, help_text='Resolved ordered MessageEvent IDs included in this run.'),
),
migrations.AlterField(
model_name='airequest',
name='operation',
field=models.CharField(choices=[('summarise', 'Summarise'), ('draft_reply', 'Draft Reply'), ('critique', 'Critique'), ('repair', 'Repair'), ('extract_patterns', 'Extract Patterns'), ('memory_propose', 'Memory Propose'), ('state_detect', 'State Detect'), ('rewrite_style', 'Rewrite Style'), ('send_readiness', 'Send Readiness'), ('timeline_brief', 'Timeline Brief')], help_text='Requested AI operation type.', max_length=32),
),
migrations.AlterField(
model_name='airequest',
name='policy_snapshot',
field=models.JSONField(blank=True, default=dict, help_text='Effective manipulation/policy values captured at request time, so results remain auditable even if policies change later.'),
),
migrations.AlterField(
model_name='airequest',
name='started_at',
field=models.DateTimeField(blank=True, help_text='Worker start timestamp.', null=True),
),
migrations.AlterField(
model_name='airequest',
name='status',
field=models.CharField(choices=[('queued', 'Queued'), ('running', 'Running'), ('done', 'Done'), ('failed', 'Failed')], default='queued', help_text='Worker lifecycle state for this request.', max_length=16),
),
migrations.AlterField(
model_name='airequest',
name='user',
field=models.ForeignKey(help_text='User who initiated this request.', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_ai_requests', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='airequest',
name='user_notes',
field=models.TextField(blank=True, default='', help_text='Optional user intent/context notes injected into the prompt.'),
),
migrations.AlterField(
model_name='airequest',
name='window_spec',
field=models.JSONField(default=dict, help_text='Selection spec (last_n/since_ts/between_ts/include_attachments/etc). Should be dynamically resolved by available context/token budget.'),
),
migrations.AlterField(
model_name='airesult',
name='ai_request',
field=models.OneToOneField(help_text='Owning AI request for this result.', on_delete=django.db.models.deletion.CASCADE, related_name='result', to='core.airequest'),
),
migrations.AlterField(
model_name='airesult',
name='citations',
field=models.JSONField(blank=True, default=list, help_text='Referenced MessageEvent IDs supporting generated claims.'),
),
migrations.AlterField(
model_name='airesult',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Result creation timestamp.'),
),
migrations.AlterField(
model_name='airesult',
name='draft_replies',
field=models.JSONField(blank=True, default=list, help_text='Draft reply candidates, typically with tone and rationale.'),
),
migrations.AlterField(
model_name='airesult',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this AI result payload.', primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='airesult',
name='memory_proposals',
field=models.JSONField(blank=True, default=list, help_text='Proposed memory entries, typically requiring user approval.'),
),
migrations.AlterField(
model_name='memoryitem',
name='content',
field=models.JSONField(blank=True, default=dict, help_text='Structured memory payload (schema can evolve by type).'),
),
migrations.AlterField(
model_name='memoryitem',
name='conversation',
field=models.ForeignKey(help_text='Conversation scope this memory item belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='memory_items', to='core.workspaceconversation'),
),
migrations.AlterField(
model_name='memoryitem',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.'),
),
migrations.AlterField(
model_name='memoryitem',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this memory item.', primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='memoryitem',
name='source_request',
field=models.ForeignKey(blank=True, help_text='AIRequest that originated this memory, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.airequest'),
),
migrations.AlterField(
model_name='memoryitem',
name='status',
field=models.CharField(choices=[('proposed', 'Proposed'), ('active', 'Active'), ('deprecated', 'Deprecated')], default='proposed', help_text='Lifecycle state, especially for approval-gated memories.', max_length=16),
),
migrations.AlterField(
model_name='memoryitem',
name='updated_at',
field=models.DateTimeField(auto_now=True, help_text='Last update timestamp.'),
),
migrations.AlterField(
model_name='memoryitem',
name='user',
field=models.ForeignKey(help_text='Owner of the memory item.', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_memory_items', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='messageevent',
name='attachments',
field=models.JSONField(blank=True, default=list, help_text='Attachment metadata list associated with this message.'),
),
migrations.AlterField(
model_name='messageevent',
name='conversation',
field=models.ForeignKey(help_text='AI workspace conversation this message belongs to. This is not the transport-native thread object.', on_delete=django.db.models.deletion.CASCADE, related_name='events', to='core.workspaceconversation'),
),
migrations.AlterField(
model_name='messageevent',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.'),
),
migrations.AlterField(
model_name='messageevent',
name='direction',
field=models.CharField(choices=[('in', 'Inbound'), ('out', 'Outbound')], help_text="Direction relative to workspace owner: 'in' from counterpart(s), 'out' from user/bot side.", max_length=8),
),
migrations.AlterField(
model_name='messageevent',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this message event.', primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='messageevent',
name='raw_payload_ref',
field=models.JSONField(blank=True, default=dict, help_text='Raw source payload or reference pointer for audit/debug.'),
),
migrations.AlterField(
model_name='messageevent',
name='sender_uuid',
field=models.CharField(blank=True, db_index=True, default='', help_text='Source sender UUID/identifier for correlation.', max_length=255),
),
migrations.AlterField(
model_name='messageevent',
name='text',
field=models.TextField(blank=True, default='', help_text='Normalized message text body.'),
),
migrations.AlterField(
model_name='messageevent',
name='ts',
field=models.BigIntegerField(db_index=True, help_text='Event timestamp (unix ms) as reported by source_system.'),
),
migrations.AlterField(
model_name='personidentifier',
name='service',
field=models.CharField(choices=[('signal', 'Signal'), ('xmpp', 'XMPP'), ('instagram', 'Instagram')], max_length=255),
),
migrations.AlterField(
model_name='workspaceconversation',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.'),
),
migrations.AlterField(
model_name='workspaceconversation',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this workspace conversation.', primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='workspaceconversation',
name='last_event_ts',
field=models.BigIntegerField(blank=True, help_text='Latest message timestamp (unix ms) currently known.', null=True),
),
migrations.AlterField(
model_name='workspaceconversation',
name='participants',
field=models.ManyToManyField(blank=True, help_text='Resolved people participating in this conversation.', related_name='workspace_conversations', to='core.person'),
),
migrations.AlterField(
model_name='workspaceconversation',
name='platform_thread_id',
field=models.CharField(blank=True, default='', help_text='Platform-native thread/group identifier when available.', max_length=255),
),
migrations.AlterField(
model_name='workspaceconversation',
name='platform_type',
field=models.CharField(choices=[('signal', 'Signal'), ('xmpp', 'XMPP'), ('instagram', 'Instagram')], default='signal', help_text='Primary transport for this conversation (reuses SERVICE_CHOICES).', max_length=255),
),
migrations.AlterField(
model_name='workspaceconversation',
name='title',
field=models.CharField(blank=True, default='', help_text='Human-friendly label shown in the workspace sidebar.', max_length=255),
),
migrations.AlterField(
model_name='workspaceconversation',
name='user',
field=models.ForeignKey(help_text='Owner of this conversation workspace.', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_conversations', to=settings.AUTH_USER_MODEL),
),
migrations.CreateModel(
name='AIResultSignal',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this result signal.', primary_key=True, serialize=False)),
('label', models.CharField(help_text="Short signal label, e.g. 'withdrawing', 'repair_attempt'.", max_length=128)),
('valence', models.CharField(choices=[('positive', 'Positive'), ('neutral', 'Neutral'), ('risk', 'Risk')], default='neutral', help_text='Signal polarity: positive, neutral, or risk.', max_length=16)),
('score', models.FloatField(blank=True, help_text='Optional model confidence/strength (0.0-1.0).', null=True)),
('rationale', models.TextField(blank=True, default='', help_text='Human-readable explanation for this signal.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('ai_result', models.ForeignKey(help_text='AI result that produced this signal.', on_delete=django.db.models.deletion.CASCADE, related_name='signals', to='core.airesult')),
('message_event', models.ForeignKey(blank=True, help_text='Optional specific message event referenced by this signal.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ai_signals', to='core.messageevent')),
('user', models.ForeignKey(help_text='Owner of this signal row (required for restricted CRUD filtering).', on_delete=django.db.models.deletion.CASCADE, related_name='workspace_ai_result_signals', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,84 @@
# Generated by Django 5.2.11 on 2026-02-15 00:58
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0017_remove_airesult_risk_flags_and_more'),
]
operations = [
migrations.CreateModel(
name='PatternMitigationPlan',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this mitigation plan.', primary_key=True, serialize=False)),
('title', models.CharField(blank=True, default='', help_text='Display title for this plan.', max_length=255)),
('objective', models.TextField(blank=True, default='', help_text='High-level objective this plan is meant to achieve.')),
('fundamental_items', models.JSONField(blank=True, default=list, help_text='Foundational agreed items/principles for this plan.')),
('creation_mode', models.CharField(choices=[('auto', 'Auto'), ('guided', 'Guided')], default='auto', help_text='Whether plan artifacts were generated automatically or user-guided.', max_length=16)),
('status', models.CharField(choices=[('draft', 'Draft'), ('active', 'Active'), ('archived', 'Archived')], default='draft', help_text='Lifecycle status of the plan.', max_length=16)),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('updated_at', models.DateTimeField(auto_now=True, help_text='Last update timestamp.')),
('conversation', models.ForeignKey(help_text='Workspace conversation this plan belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='mitigation_plans', to='core.workspaceconversation')),
('source_ai_result', models.ForeignKey(blank=True, help_text='AI result that initiated this plan, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mitigation_plans', to='core.airesult')),
('user', models.ForeignKey(help_text='Owner of this plan.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_plans', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='PatternMitigationMessage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this mitigation message.', primary_key=True, serialize=False)),
('role', models.CharField(choices=[('user', 'User'), ('assistant', 'Assistant'), ('system', 'System')], help_text='Message speaker role.', max_length=16)),
('text', models.TextField(help_text='Message content.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('user', models.ForeignKey(help_text='Owner of this message.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_messages', to=settings.AUTH_USER_MODEL)),
('plan', models.ForeignKey(help_text='Parent mitigation plan.', on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='core.patternmitigationplan')),
],
options={
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='PatternMitigationGame',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this game.', primary_key=True, serialize=False)),
('title', models.CharField(help_text='Game title.', max_length=255)),
('instructions', models.TextField(blank=True, default='', help_text='Gameplay/instruction text.')),
('enabled', models.BooleanField(default=True, help_text='Whether this game is currently enabled.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('user', models.ForeignKey(help_text='Owner of this game.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_games', to=settings.AUTH_USER_MODEL)),
('plan', models.ForeignKey(help_text='Parent mitigation plan.', on_delete=django.db.models.deletion.CASCADE, related_name='games', to='core.patternmitigationplan')),
],
),
migrations.CreateModel(
name='PatternArtifactExport',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this export artifact.', primary_key=True, serialize=False)),
('artifact_type', models.CharField(choices=[('rulebook', 'Rulebook'), ('rules', 'Rules'), ('games', 'Games')], help_text='Artifact category being exported.', max_length=32)),
('export_format', models.CharField(choices=[('markdown', 'Markdown'), ('json', 'JSON'), ('text', 'Text')], default='markdown', help_text='Serialized output format.', max_length=16)),
('protocol_version', models.CharField(default='artifact-v1', help_text='Artifact export protocol version.', max_length=32)),
('payload', models.TextField(blank=True, default='', help_text='Serialized artifact body/content.')),
('meta', models.JSONField(blank=True, default=dict, help_text='Additional export metadata (counts, hints, source IDs).')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('user', models.ForeignKey(help_text='Owner of this export artifact.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_artifact_exports', to=settings.AUTH_USER_MODEL)),
('plan', models.ForeignKey(help_text='Source mitigation plan.', on_delete=django.db.models.deletion.CASCADE, related_name='exports', to='core.patternmitigationplan')),
],
),
migrations.CreateModel(
name='PatternMitigationRule',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this rule.', primary_key=True, serialize=False)),
('title', models.CharField(help_text='Rule title.', max_length=255)),
('content', models.TextField(blank=True, default='', help_text='Rule definition/details.')),
('enabled', models.BooleanField(default=True, help_text='Whether this rule is currently enabled.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('plan', models.ForeignKey(help_text='Parent mitigation plan.', on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='core.patternmitigationplan')),
('user', models.ForeignKey(help_text='Owner of this rule.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_rules', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.11 on 2026-02-15 01:13
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0018_patternmitigationplan_patternmitigationmessage_and_more'),
]
operations = [
migrations.CreateModel(
name='PatternMitigationCorrection',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this correction item.', primary_key=True, serialize=False)),
('title', models.CharField(help_text='Correction title.', max_length=255)),
('clarification', models.TextField(blank=True, default='', help_text='Joint clarification text intended to reduce interpretation drift.')),
('enabled', models.BooleanField(default=True, help_text='Whether this correction item is currently enabled.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('plan', models.ForeignKey(help_text='Parent mitigation plan.', on_delete=django.db.models.deletion.CASCADE, related_name='corrections', to='core.patternmitigationplan')),
('user', models.ForeignKey(help_text='Owner of this correction item.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_corrections', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.11 on 2026-02-15 01:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0019_patternmitigationcorrection'),
]
operations = [
migrations.AddField(
model_name='patternmitigationcorrection',
name='language_style',
field=models.CharField(choices=[('same', 'Same Language'), ('adapted', 'Adapted Language')], default='adapted', help_text='Whether to keep wording identical or adapt it per recipient.', max_length=16),
),
migrations.AddField(
model_name='patternmitigationcorrection',
name='perspective',
field=models.CharField(choices=[('third_person', 'Third Person'), ('first_person', 'First Person')], default='third_person', help_text='Narrative perspective used when framing this correction.', max_length=32),
),
migrations.AddField(
model_name='patternmitigationcorrection',
name='share_target',
field=models.CharField(choices=[('self', 'Self'), ('other', 'Other Party'), ('both', 'Both Parties')], default='both', help_text='Who this insight is intended to be shared with.', max_length=16),
),
migrations.AddField(
model_name='patternmitigationcorrection',
name='source_phrase',
field=models.TextField(blank=True, default='', help_text="Situation/message fragment this correction responds to, e.g. 'she says ...'."),
),
migrations.AlterField(
model_name='patternartifactexport',
name='artifact_type',
field=models.CharField(choices=[('rulebook', 'Rulebook'), ('rules', 'Rules'), ('games', 'Games'), ('corrections', 'Corrections')], help_text='Artifact category being exported.', max_length=32),
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2.11 on 2026-02-15 02:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0020_patternmitigationcorrection_language_style_and_more'),
]
operations = [
migrations.AlterField(
model_name='patternmitigationcorrection',
name='clarification',
field=models.TextField(blank=True, default='', help_text='Joint clarification text intended to reduce interpretation drift. Example: \'When you say "you ignore me", I hear fear of disconnection, not blame.\''),
),
migrations.AlterField(
model_name='patternmitigationcorrection',
name='language_style',
field=models.CharField(choices=[('same', 'Same Language'), ('adapted', 'Adapted Language')], default='adapted', help_text='Whether to keep wording identical or adapt it per recipient. Example: same text for both parties, or softened/adapted wording for recipient.', max_length=16),
),
migrations.AlterField(
model_name='patternmitigationcorrection',
name='perspective',
field=models.CharField(choices=[('third_person', 'Third Person'), ('second_person', 'Second Person'), ('first_person', 'First Person')], default='third_person', help_text="Narrative perspective used when framing this correction. Examples: third person ('she says'), second person ('you say'), first person ('I say').", max_length=32),
),
migrations.AlterField(
model_name='patternmitigationcorrection',
name='share_target',
field=models.CharField(choices=[('self', 'Self'), ('other', 'Other Party'), ('both', 'Both Parties')], default='both', help_text='Who this insight is intended to be shared with. Example: self, other, or both.', max_length=16),
),
migrations.AlterField(
model_name='patternmitigationcorrection',
name='source_phrase',
field=models.TextField(blank=True, default='', help_text='Situation/message fragment this correction responds to. Example: \'she says: "you never listen"\' or \'you say: "you are dismissing me"\'.'),
),
migrations.AlterField(
model_name='patternmitigationcorrection',
name='title',
field=models.CharField(help_text="Correction title. Example: 'Assumption vs intent mismatch'.", max_length=255),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.11 on 2026-02-15 02:38
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0021_alter_patternmitigationcorrection_clarification_and_more'),
]
operations = [
migrations.CreateModel(
name='PatternMitigationAutoSettings',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='Stable identifier for this automation settings row.', primary_key=True, serialize=False)),
('enabled', models.BooleanField(default=False, help_text='Master toggle for mitigation automation in this conversation.')),
('auto_pattern_recognition', models.BooleanField(default=True, help_text='Run pattern/violation recognition automatically when triggered.')),
('auto_create_mitigation', models.BooleanField(default=False, help_text='Create a baseline mitigation plan automatically when missing.')),
('auto_create_corrections', models.BooleanField(default=False, help_text='Create correction items automatically from detected violations.')),
('auto_notify_enabled', models.BooleanField(default=False, help_text='Send NTFY notifications when new violations are detected.')),
('ntfy_topic_override', models.CharField(blank=True, help_text='Optional NTFY topic override for automation notifications.', max_length=255, null=True)),
('ntfy_url_override', models.CharField(blank=True, help_text='Optional NTFY server URL override for automation notifications.', max_length=255, null=True)),
('sample_message_window', models.PositiveIntegerField(default=40, help_text='How many recent messages to include in each automation check.')),
('check_cooldown_seconds', models.PositiveIntegerField(default=300, help_text='Minimum seconds between automatic checks for this conversation.')),
('last_checked_event_ts', models.BigIntegerField(blank=True, help_text='Latest source message timestamp included in automation checks.', null=True)),
('last_run_at', models.DateTimeField(blank=True, help_text='Timestamp when automation last ran.', null=True)),
('last_result_summary', models.TextField(blank=True, default='', help_text='Human-readable summary from the last automation run.')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Row creation timestamp.')),
('updated_at', models.DateTimeField(auto_now=True, help_text='Last update timestamp.')),
('conversation', models.OneToOneField(help_text='Conversation scope this automation config applies to.', on_delete=django.db.models.deletion.CASCADE, related_name='mitigation_auto_settings', to='core.workspaceconversation')),
('user', models.ForeignKey(help_text='Owner of this automation settings row.', on_delete=django.db.models.deletion.CASCADE, related_name='pattern_mitigation_auto_settings', to=settings.AUTH_USER_MODEL)),
],
),
]