# Sofiia Dialog Graph — Canonical Contract v1.0

## Core Invariants

Every meaningful artifact in the Sofiia system MUST be represented in the Dialog Graph:

```
1. Every artifact has a node.
2. Every action has an edge.
3. No artifact exists without graph presence.
```

---

## Node Types

| node_type       | ref_id points to      | Created by                         |
|-----------------|-----------------------|------------------------------------|
| `message`       | message_id (session)  | session message handler            |
| `task`          | tasks.task_id         | `create_task()` — atomically       |
| `meeting`       | meetings.meeting_id   | `create_meeting()` — atomically    |
| `doc`           | documents.doc_id      | document upload/create             |
| `agent_run`     | run_id (supervisor)   | `create_evidence_pack()`           |
| `ops_run`       | job_id (ops)          | ops job completion hook            |
| `repo_changeset`| changeset_id          | repo diff / PR tracking            |
| `pull_request`  | PR number/id          | PR flow integration                |
| `decision`      | decision_id           | explicit decision recording        |
| `goal`          | goal_id               | strategic goal setting             |

---

## Edge Types

| edge_type           | Meaning                                   | Example                            |
|---------------------|-------------------------------------------|------------------------------------|
| `references`        | A mentions/cites B                        | message → doc                     |
| `summarizes`        | A is a summary of B                       | doc → session                     |
| `derives_task`      | A produced task B                         | message → task                    |
| `updates_doc`       | A updates/modifies doc B                  | ops_run → doc                     |
| `schedules_meeting` | A scheduled meeting B                     | message → meeting                  |
| `resolves`          | A resolves/closes B                       | task → task (blocker resolved)    |
| `blocks`            | A blocks B                                | task → task                       |
| `relates_to`        | A is related to B                         | any → any                         |
| `produced_by`       | B was produced by run A                   | agent_run → task/doc              |
| `executed_as`       | plan A was executed as ops_run B          | decision → ops_run                |

---

## Atomic Creation Rules

When creating an artifact, the node MUST be created in the same SQLite transaction:

```python
# CORRECT: task + node in one BEGIN...COMMIT
await db.execute("BEGIN")
await db.execute("INSERT INTO tasks ...")
await db.execute("INSERT INTO dialog_nodes ... ON CONFLICT DO UPDATE")
await db.commit()

# WRONG: two separate commits
await create_task(...)       # commit 1
await upsert_dialog_node(...)  # commit 2 — can diverge
```

Functions that guarantee atomicity:
- `db.create_task()` — always upserts task node
- `db.create_meeting()` — always upserts meeting node
- `db.create_evidence_pack()` — creates agent_run node + derived task nodes + edges

---

## Evidence Pack

After every Supervisor run, an Evidence Pack MUST be recorded:

```json
{
  "run_id": "<uuid>",
  "graph_name": "release_check|incident_triage|...",
  "status": "completed",
  "summary": "...",
  "findings": [...],
  "recommendations": [...],
  "follow_up_tasks": [
    {"title": "...", "description": "...", "priority": "normal|high|urgent"}
  ]
}
```

This creates:
1. `agent_run` dialog node
2. `doc_version` with evidence markdown (if evidence_log.md exists in project)
3. `task` nodes for each follow_up_task (in `backlog` with label `evidence`)
4. `produced_by` edges: agent_run → each task node

---

## Integrity Checks

Run `GET /api/projects/{id}/graph/integrity` to verify:

| Check                   | Description                                          |
|-------------------------|------------------------------------------------------|
| `orphaned_edge_from`    | Edges referencing non-existent from_node             |
| `orphaned_edge_to`      | Edges referencing non-existent to_node               |
| `dangling_task_nodes`   | `node_type=task` nodes with no matching task row     |
| `dangling_meeting_nodes`| `node_type=meeting` nodes with no matching meeting   |
| `self_loop_edges`       | Edges where from_node_id == to_node_id               |

**Expected**: `{"ok": true, "violations": []}`

---

## DDL Freeze

As of v1.0, the schema is **frozen**. Any schema changes require:
1. A migration file in `services/sofiia-console/migrations/`
2. Update to this contract document
3. Update to `tests/test_graph_integrity.py`

Current canonical DDL: `services/sofiia-console/app/db.py` (init_db function)

---

## Quality Gates

Before merging any feature that touches artifacts:

| Gate                | Check                                        |
|---------------------|----------------------------------------------|
| **Reproducibility** | Does the feature create a node + edge?       |
| **Safety**          | Is creation atomic (single transaction)?     |
| **Observability**   | Does `GET /graph/integrity` stay `ok: true`? |
