{
  "$schema": "./schema/workflow.schema.json",
  "specVersion": "1.0",
  "workflow": {
    "id": "bloom-sdlc",
    "name": "Bloom SDLC Product-Owner Workflow",
    "version": "0.1.0",
    "description": "End-to-end software development lifecycle as run by Bloom, the AI Product Owner: conversational inception -> living PRD -> approval -> milestone & ticket planning -> GitHub sync -> progress tracking -> PR review -> (post-MVP) deployment, testing, and feedback. Authored against the stack-agnostic workflow spec so the orchestration engine (currently LangGraph, tentative) can be swapped without rewriting the process.",
    "owner": "bloom",
    "tags": ["sdlc", "product-owner", "telegram", "github"]
  },

  "runtime": {
    "engine": "langgraph",
    "persistence": {
      "checkpointing": true,
      "threadKey": "state.thread_id"
    }
  },

  "integrations": {
    "telegram": {
      "kind": "messaging",
      "provider": "telegram",
      "config": { "botToken": "${env:TELEGRAM_BOT_TOKEN}" },
      "actions": ["send_message", "ask", "notify"]
    },
    "github": {
      "kind": "vcs",
      "provider": "github",
      "config": { "token": "${env:GITHUB_TOKEN}" },
      "actions": [
        "create_repository",
        "create_milestone",
        "create_issue",
        "update_issue",
        "close_issue",
        "read_issue_status",
        "read_pull_request",
        "list_project_status"
      ]
    },
    "llm": {
      "kind": "llm",
      "provider": "openclaw",
      "config": { "model": "${env:BLOOM_LLM_MODEL}", "fallbackProvider": "anthropic" }
    },
    "ci": {
      "kind": "ci",
      "provider": "github-actions",
      "config": {},
      "actions": ["trigger_pipeline", "read_pipeline_status"]
    },
    "deploy": {
      "kind": "deploy",
      "provider": "unset",
      "config": {},
      "actions": ["deploy", "read_deployment_status"]
    }
  },

  "triggers": [
    {
      "id": "user_message",
      "type": "message",
      "integration": "telegram",
      "binds": {
        "thread_id": "chat.id",
        "inbound_message": "message.text"
      },
      "description": "Any inbound Telegram message from the user."
    },
    {
      "id": "pr_event",
      "type": "webhook",
      "integration": "github",
      "filter": { "event": "pull_request", "action": ["opened", "synchronize", "reopened"] },
      "binds": {
        "pending_pull_requests": "pull_request"
      },
      "description": "A pull request was opened or updated on the tracked repository."
    },
    {
      "id": "issue_event",
      "type": "webhook",
      "integration": "github",
      "filter": { "event": "issues", "action": ["closed", "reopened"] },
      "binds": { "changed_issue": "issue" },
      "description": "An issue changed state (drives progress tracking)."
    }
  ],

  "state": {
    "fields": {
      "thread_id": { "type": "string", "reducer": "replace", "description": "Durable per-project run key. One chat can own several projects, so this is chat-scoped (e.g. \"<chat_id>:<seq>\"), not the raw chat id." },
      "chat_id": { "type": "integer", "reducer": "replace", "description": "The Telegram chat this project belongs to. Several projects in one chat share this; outbound messages are addressed to it." },
      "inbound_message": { "type": "string", "reducer": "replace", "description": "Latest user message text." },
      "conversation": { "type": "array", "reducer": "append", "initial": [], "description": "Rolling conversational history." },
      "requirements": { "type": "object", "reducer": "replace", "initial": {}, "description": "Discovered requirement facets (objective, users, features, constraints, success criteria). The orchestrator accumulates these additively before writing." },
      "requirements_complete": { "type": "boolean", "reducer": "replace", "initial": false, "description": "True once discovery has gathered enough to draft a useful PRD." },
      "readiness_holds": { "type": "integer", "reducer": "replace", "initial": 0, "description": "How many times the discovery readiness gate (M13-3) has held back a model-declared complete because essential facets were missing. After the cap, the owner's insistence wins (escape hatch) and the gaps are recorded as deferred_facets." },
      "deferred_facets": { "type": "array", "reducer": "replace", "initial": [], "description": "Essential requirement facets the owner chose to defer past the readiness gate. Recorded as PRD assumptions; the proactive tick follows up once real progress makes the gap matter (first completed milestone), and refreshes this list as facets get covered." },
      "deferred_facets_notified": { "type": "boolean", "reducer": "replace", "initial": false, "description": "Whether the one-time deferred-facets follow-up nudge has been sent, so the owner is never nagged repeatedly." },
      "credentials_ready_notified": { "type": "boolean", "reducer": "replace", "initial": false, "description": "Whether the one-time credentials-complete go-live nudge has been sent for the parked deployment (M19-6)." },
      "credentials_meta": { "type": "object", "reducer": "replace", "initial": {}, "description": "Credential METADATA (name -> provided-at ISO timestamp) for the project's deployment credentials (#282). Bloom brokers every save/removal, so it keeps the metadata itself and needs NO read access on the credential store - values live only in the store." },
      "prd": { "type": "object", "reducer": "merge", "initial": { "status": "draft", "revision": 0 }, "description": "The living PRD document + status (draft|awaiting_approval|approved|revising) and revision counter." },
      "approval": { "type": "object", "reducer": "replace", "initial": {}, "description": "Latest human approval decision: { decision: approved|changes_requested, notes }." },
      "milestones": { "type": "array", "reducer": "replace", "initial": [], "description": "Planned milestones." },
      "tickets": { "type": "array", "reducer": "replace", "initial": [], "description": "Generated implementation-agnostic tickets." },
      "github_sync": { "type": "object", "reducer": "merge", "initial": {}, "description": "Mapping of planned artifacts -> created GitHub ids." },
      "created_issues": { "type": "array", "reducer": "append", "initial": [], "description": "GitHub issues created for tickets (accumulated by the sync fan-out)." },
      "repo_target": { "type": "object", "reducer": "replace", "initial": {}, "description": "Where the project's GitHub repo lives and how Bloom is authorized (bloom-hosted default, or client-owned via a GitHub App installation)." },
      "pending_pull_requests": { "type": "array", "reducer": "replace", "initial": [], "description": "PRs from the current event awaiting review (replaced per event, drained after review)." },
      "reviews": { "type": "array", "reducer": "append", "initial": [], "description": "Completed PR review summaries." },
      "specialist_reviews": { "type": "object", "reducer": "merge", "initial": {}, "description": "Per-PR specialist review rounds (M7-2), keyed by PR number (string) -> {marker, round}. A repeated tick for an unchanged PR overwrites its entry rather than duplicating a record. Advisory only; never a merge gate." },
      "engine_runs": { "type": "object", "reducer": "merge", "initial": {}, "description": "Durable production-engineer run records (M6-6), keyed by issue number (string) -> {state, attempt, retries, failure_kind, failure_reason, last_attempt_at, ...}. Secret-free; the auditable trail of what the engine did per ticket." },
      "changed_issue": { "type": "object", "reducer": "replace", "initial": {}, "description": "Most recent issue-state change." },
      "notified_milestones": { "type": "array", "reducer": "union", "initial": [], "description": "Milestone numbers already announced as complete, so completion is notified at most once (FR-10)." },
      "progress": { "type": "object", "reducer": "merge", "initial": {}, "description": "Snapshot of open/closed milestones & issues, blocked work." },
      "lifecycle": { "type": "string", "reducer": "replace", "initial": "active", "description": "Project lifecycle state: active | archived | deleted." },
      "pending_lifecycle": { "type": "string", "reducer": "replace", "initial": "", "description": "A destructive lifecycle op (archive|delete) awaiting the owner's confirmation; empty when none." },
      "pending_plan_change": { "type": "object", "reducer": "replace", "initial": {}, "description": "A proposed post-planning plan revision (revised PRD + desired milestones/tickets) awaiting the owner's approval; empty when none (FR-3/FR-4, M4)." },
      "pending_decision": { "type": "object", "reducer": "replace", "initial": {}, "description": "A decision record drafted at an approval gate, awaiting the owner's approval before it is committed to docs/decisions/; empty when none (M14-3)." },
      "pending_client_approvals": { "type": "array", "reducer": "replace", "initial": [], "description": "Bloom-approved deliverables parked for the owner's sign-off (status:awaiting-client, M11-5), as {number, title, kind, pr_number}. Rewritten wholesale on every park/resolve, so the reducer is replace; empty when none." },
      "decision_log": { "type": "array", "reducer": "append", "initial": [], "description": "Decision records committed to docs/decisions/, as {id, path, title, gate, supersedes} (M14-4/5). The latest plan-scope entry (prd or plan_change gate) is what a superseding plan change links - and retires - via its supersedes edge; the entries' supersedes lists are how de-duplication tells retired conditions from current ones." },
      "last_checked_at": { "type": "string", "reducer": "replace", "initial": "", "description": "ISO timestamp of Bloom's last proactive maintenance tick for this project (M5)." },
      "last_digest_at": { "type": "string", "reducer": "replace", "initial": "", "description": "ISO timestamp of the last periodic status digest sent (M5)." },
      "digest_signature": { "type": "string", "reducer": "replace", "initial": "", "description": "Signature of the project state at the last digest, so an unchanged project is not re-digested (M5)." },
      "notified_stalls": { "type": "array", "reducer": "replace", "initial": [], "description": "Issue numbers already flagged as stalled, so a persistent stall is nudged at most once (M5)." },
      "deployment": { "type": "object", "reducer": "merge", "initial": {}, "description": "(Post-MVP) latest deployment status." },
      "feedback": { "type": "array", "reducer": "append", "initial": [], "description": "(Post-MVP) user/production feedback feeding the next cycle." },
      "notifications": { "type": "array", "reducer": "append", "initial": [], "description": "Outbound notifications queued/sent to the user." }
    }
  },

  "nodes": [
    {
      "id": "await_message",
      "type": "event_wait",
      "title": "Await user turn",
      "phase": "inception",
      "config": { "eventWait": "await_message", "trigger": "user_message" }
    },
    {
      "id": "requirement_discovery",
      "type": "agent",
      "title": "Interview & discover requirements",
      "phase": "inception",
      "reads": ["state.conversation", "state.requirements", "state.inbound_message"],
      "writes": ["requirements", "requirements_complete", "conversation", "readiness_holds", "deferred_facets"],
      "config": {
        "agent": "product_owner",
        "model": "llm",
        "instructions": "${ref:prompts/requirement_discovery}",
        "input": { "history": "state.conversation", "known": "state.requirements", "message": "state.inbound_message" },
        "tools": [],
        "output": {
          "assign": { "requirements": "result.requirements", "requirements_complete": "result.complete" },
          "schema": {
            "type": "object",
            "required": ["requirements", "complete"],
            "properties": {
              "requirements": { "type": "object" },
              "complete": { "type": "boolean" },
              "reply": { "type": "string" }
            }
          }
        }
      }
    },
    {
      "id": "ask_clarification",
      "type": "human",
      "title": "Ask a clarifying question",
      "phase": "inception",
      "config": {
        "human": "clarification",
        "channel": "telegram",
        "prompt": "${ref:prompts/clarifying_question}",
        "input": { "known": "state.requirements" },
        "awaits": { "type": "object", "properties": { "text": { "type": "string" } } },
        "output": { "assign": { "inbound_message": "result.text", "conversation": "result" } }
      }
    },
    {
      "id": "prd_authoring",
      "type": "agent",
      "title": "Author / update the living PRD",
      "phase": "planning",
      "reads": ["state.requirements", "state.prd"],
      "writes": ["prd"],
      "config": {
        "agent": "product_owner",
        "model": "llm",
        "instructions": "${ref:prompts/prd_authoring}",
        "input": { "requirements": "state.requirements", "current_prd": "state.prd" },
        "output": {
          "assign": { "prd": "result" },
          "schema": {
            "type": "object",
            "required": ["document", "status", "revision"],
            "properties": {
              "document": { "type": "object" },
              "status": { "type": "string", "enum": ["awaiting_approval"] },
              "revision": { "type": "integer" }
            }
          }
        }
      }
    },
    {
      "id": "request_prd_approval",
      "type": "human",
      "title": "Request PRD approval",
      "phase": "planning",
      "config": {
        "human": "prd_approval",
        "channel": "telegram",
        "prompt": "${ref:prompts/request_prd_approval}",
        "input": { "prd": "state.prd" },
        "awaits": {
          "type": "object",
          "required": ["decision"],
          "properties": {
            "decision": { "type": "string", "enum": ["approved", "changes_requested"] },
            "notes": { "type": "string" }
          }
        },
        "output": { "assign": { "approval": "result" } }
      }
    },
    {
      "id": "freeze_prd",
      "type": "passthrough",
      "title": "Freeze approved PRD revision",
      "phase": "planning",
      "writes": ["prd"],
      "config": { "note": "Marks prd.status=approved and pins the revision; future changes create a new revision (FR-4)." }
    },
    {
      "id": "milestone_planning",
      "type": "agent",
      "title": "Plan milestones",
      "phase": "planning",
      "reads": ["state.prd"],
      "writes": ["milestones"],
      "config": {
        "agent": "product_owner",
        "model": "llm",
        "instructions": "${ref:prompts/milestone_planning}",
        "input": { "prd": "state.prd" },
        "output": {
          "assign": { "milestones": "result.milestones" },
          "schema": { "type": "object", "required": ["milestones"], "properties": { "milestones": { "type": "array" } } }
        }
      }
    },
    {
      "id": "ticket_generation",
      "type": "agent",
      "title": "Generate implementation-agnostic tickets",
      "phase": "planning",
      "reads": ["state.milestones", "state.prd"],
      "writes": ["tickets"],
      "config": {
        "agent": "product_owner",
        "model": "llm",
        "instructions": "${ref:prompts/ticket_generation}",
        "input": { "milestones": "state.milestones", "prd": "state.prd" },
        "output": {
          "assign": { "tickets": "result.tickets" },
          "schema": {
            "type": "object",
            "required": ["tickets"],
            "properties": {
              "tickets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["title", "description", "acceptanceCriteria"],
                  "properties": {
                    "title": { "type": "string" },
                    "description": { "type": "string" },
                    "acceptanceCriteria": { "type": "array", "items": { "type": "string" } },
                    "labels": { "type": "array", "items": { "type": "string" } },
                    "dependencies": { "type": "array", "items": { "type": "string" } },
                    "complexity": { "type": "string" },
                    "milestone": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    {
      "id": "sync_milestones",
      "type": "tool",
      "title": "Create GitHub milestones",
      "phase": "planning",
      "reads": ["state.milestones"],
      "writes": ["github_sync"],
      "config": {
        "tool": "create_github_milestones",
        "integration": "github",
        "action": "create_milestone",
        "params": { "milestones": "state.milestones" },
        "output": { "assign": { "github_sync.milestones": "result" } }
      }
    },
    {
      "id": "sync_tickets",
      "type": "map",
      "title": "Create a GitHub issue per ticket",
      "phase": "planning",
      "reads": ["state.tickets"],
      "writes": ["created_issues"],
      "config": {
        "map": "sync_tickets",
        "over": "state.tickets",
        "as": "ticket",
        "body": "create_issue_for_ticket",
        "concurrency": 4,
        "collect": "created_issues"
      }
    },
    {
      "id": "create_issue_for_ticket",
      "type": "tool",
      "title": "Create one GitHub issue",
      "phase": "planning",
      "reads": ["item.title", "item.description"],
      "config": {
        "tool": "create_issue",
        "integration": "github",
        "action": "create_issue",
        "params": {
          "title": "item.title",
          "body": "item.description",
          "labels": "item.labels",
          "milestone": "item.milestone"
        },
        "output": { "assign": { "result": "result" } }
      }
    },
    {
      "id": "notify_planning_ready",
      "type": "tool",
      "title": "Notify: planning synced to GitHub",
      "phase": "planning",
      "config": {
        "tool": "notify_planning_ready",
        "integration": "telegram",
        "action": "notify",
        "params": { "template": "planning_ready", "milestones": "state.github_sync.milestones", "issues": "state.created_issues" },
        "output": { "assign": { "notifications": "result" } }
      }
    },
    {
      "id": "await_project_event",
      "type": "event_wait",
      "title": "Await project activity",
      "phase": "execution",
      "description": "Parks the long-lived run between planning and ongoing lifecycle events (PRs, issue changes, or a new user message).",
      "config": { "eventWait": "await_project_event", "trigger": "pr_event" }
    },
    {
      "id": "review_pull_requests",
      "type": "map",
      "title": "Review each pending pull request",
      "phase": "review",
      "reads": ["state.pending_pull_requests"],
      "writes": ["reviews"],
      "config": {
        "map": "review_pull_requests",
        "over": "state.pending_pull_requests",
        "as": "pr",
        "body": "review_one_pull_request",
        "concurrency": 2,
        "collect": "reviews"
      }
    },
    {
      "id": "review_one_pull_request",
      "type": "agent",
      "title": "Review one PR against acceptance criteria",
      "phase": "review",
      "reads": ["item.number", "state.tickets"],
      "config": {
        "agent": "reviewer",
        "model": "llm",
        "instructions": "${ref:prompts/pr_review}",
        "input": { "pull_request": "item", "tickets": "state.tickets" },
        "tools": ["github.read_pull_request"],
        "output": {
          "assign": { "result": "result" },
          "schema": {
            "type": "object",
            "required": ["summary", "recommendation"],
            "properties": {
              "summary": { "type": "string" },
              "recommendation": { "type": "string", "enum": ["approve", "request_changes"] },
              "missingRequirements": { "type": "array", "items": { "type": "string" } },
              "documentationGaps": { "type": "array", "items": { "type": "string" } }
            }
          }
        }
      }
    },
    {
      "id": "notify_review",
      "type": "tool",
      "title": "Notify: PR review complete",
      "phase": "review",
      "config": {
        "tool": "notify_review",
        "integration": "telegram",
        "action": "notify",
        "params": { "template": "review_complete", "reviews": "state.reviews" },
        "output": { "assign": { "notifications": "result" } }
      }
    },
    {
      "id": "track_progress",
      "type": "tool",
      "title": "Refresh project progress snapshot",
      "phase": "execution",
      "writes": ["progress"],
      "config": {
        "tool": "track_progress",
        "integration": "github",
        "action": "list_project_status",
        "params": {},
        "output": { "assign": { "progress": "result" } }
      }
    },
    {
      "id": "deploy",
      "type": "tool",
      "title": "Deploy (post-MVP)",
      "phase": "deployment",
      "mvp": false,
      "writes": ["deployment"],
      "config": {
        "tool": "deploy",
        "integration": "deploy",
        "action": "deploy",
        "params": {},
        "output": { "assign": { "deployment": "result" } }
      }
    },
    {
      "id": "run_tests",
      "type": "tool",
      "title": "Run test/QA pipeline (post-MVP)",
      "phase": "testing",
      "mvp": false,
      "config": {
        "tool": "run_tests",
        "integration": "ci",
        "action": "trigger_pipeline",
        "params": {},
        "output": { "assign": { "progress.tests": "result" } }
      }
    },
    {
      "id": "gather_feedback",
      "type": "agent",
      "title": "Synthesize feedback into next-cycle requirements (post-MVP)",
      "phase": "feedback",
      "mvp": false,
      "reads": ["state.feedback", "state.reviews"],
      "writes": ["requirements", "feedback"],
      "config": {
        "agent": "product_owner",
        "model": "llm",
        "instructions": "${ref:prompts/feedback_synthesis}",
        "input": { "feedback": "state.feedback", "reviews": "state.reviews", "prd": "state.prd" },
        "output": { "assign": { "requirements": "result.requirements" } }
      }
    }
  ],

  "edges": [
    { "from": "await_message", "to": "requirement_discovery" },

    {
      "from": "requirement_discovery",
      "to": "prd_authoring",
      "when": { "op": "eq", "left": { "ref": "state.requirements_complete" }, "right": { "const": true } },
      "priority": 10,
      "label": "enough gathered"
    },
    {
      "from": "requirement_discovery",
      "to": "ask_clarification",
      "priority": 0,
      "label": "need more info"
    },
    { "from": "ask_clarification", "to": "requirement_discovery" },

    { "from": "prd_authoring", "to": "request_prd_approval" },

    {
      "from": "request_prd_approval",
      "to": "freeze_prd",
      "when": { "op": "eq", "left": { "ref": "state.approval.decision" }, "right": { "const": "approved" } },
      "priority": 10,
      "label": "approved"
    },
    {
      "from": "request_prd_approval",
      "to": "requirement_discovery",
      "priority": 0,
      "label": "changes requested -> new revision"
    },

    { "from": "freeze_prd", "to": "milestone_planning" },
    { "from": "milestone_planning", "to": "ticket_generation" },
    { "from": "ticket_generation", "to": "sync_milestones" },
    { "from": "sync_milestones", "to": "sync_tickets" },
    { "from": "sync_tickets", "to": "notify_planning_ready" },
    { "from": "notify_planning_ready", "to": "await_project_event" },

    {
      "from": "await_project_event",
      "to": "review_pull_requests",
      "when": { "op": "exists", "left": { "ref": "state.pending_pull_requests[0]" } },
      "priority": 10,
      "label": "PR activity"
    },
    {
      "from": "await_project_event",
      "to": "track_progress",
      "priority": 0,
      "label": "other project activity"
    },

    { "from": "review_pull_requests", "to": "notify_review" },
    { "from": "notify_review", "to": "track_progress" },
    { "from": "track_progress", "to": "await_project_event", "label": "keep tracking (MVP loop)" },

    {
      "from": "track_progress",
      "to": "deploy",
      "when": { "predicate": "post_mvp_enabled", "args": { "capability": "deployment" } },
      "priority": 5,
      "label": "post-MVP: deploy when milestone complete"
    },
    { "from": "deploy", "to": "run_tests" },
    { "from": "run_tests", "to": "gather_feedback" },
    { "from": "gather_feedback", "to": "prd_authoring", "label": "feedback -> next PRD revision" }
  ],

  "entrypoint": "await_message",
  "terminals": []
}
