# Workflow Schema — v2

> Source: `src/orchestrator/workflow/WorkflowSchema.ts`
> Parser: `src/orchestrator/workflow/WorkflowParser.ts`
> Engine: `src/orchestrator/workflow/WorkflowEngine.ts`

Workflows are YAML-declared executable sequences. v1.3 extends the v1.2
schema with 15 optional Hermes-mandated fields. **All v1.2 YAMLs parse
unchanged via the v2 parser** (asserted by
`test_workflow_schema_backward_compat.ts`).

## v1.2 baseline (still required)

```yaml
workflow:
  id: my-workflow
  name: My Workflow
  type: custom            # greenfield | brownfield | story-cycle | qa-loop | custom
  version: "1.0"
  sequence:
    - id: step-1
      agent: dev
      action: build-feature
```

## v1.3 extensions (all optional)

```yaml
workflow:
  id: research-workflow
  name: Research Workflow
  type: custom
  version: "1.0"

  # ── lifecycle ──
  status: tested          # draft | tested | active | deprecated
  mode: task              # task (default) | service (must opt-in)

  # ── trigger surface ──
  triggers: [manual, cron, webhook, event]

  # ── intent ──
  objective: "produce verifiable research with cited sources"
  success_criteria:
    - { id: cites-all-claims, description: "all claims cite a source" }
    - { id: contradiction-considered, description: ">=3 contradicting sources" }

  # ── I/O contract ──
  inputs:
    - { id: brief, type: string, required: true }
  outputs:
    - { id: report, type: string, description: "path to final report" }

  # ── context ──
  required_context:
    - "docs/research-policy.md"

  # ── catalog refs ──
  squads: [research-squad]
  skills: [source-credibility-scoring]

  # ── tool gating ──
  tools:
    allowed: [file, web, memory]
    forbidden: [terminal]

  # ── governance tags ──
  policies: [no-uncited-claims]

  # ── boundary validation ──
  validation:
    - { id: report-exists, description: "report path must exist", kind: artifact_exists }

  # ── evidence ──
  evidence: ["report.md", "sources.json"]

  # ── failure / retry ──
  failure_handling:
    on_step_failure: retry    # abort | continue | retry
    max_retries: 2

  # ── audit ──
  audit_events: ["workflow.completed", "policy.violation"]

  # ── learning ──
  learning_rules:
    - { id: store-success, trigger: on_success, description: "..." }

  # ── existing sequence ──
  sequence:
    - id: step-1
      agent: research-lead
      action: plan
```

## Mode contract — explicit service only

- `mode: 'task'` (or omitted) — `WorkflowEngine.run()` works as normal.
- `mode: 'service'` — `WorkflowEngine.run()` THROWS unless caller passes
  `{ allowServiceMode: true }`.

```ts
// Caller must opt in:
await engine.run(serviceWorkflow, ctx, { allowServiceMode: true });

// Silent promotion is refused with stable code:
//   'service_mode_requires_explicit_opt_in'
```

`ServiceState.promoteTaskToService()` enforces the same contract for
ad-hoc promotions:

```ts
serviceStore.promoteTaskToService(missionState, 'svc-id', objective, {
  actor: 'rafa',                       // required, not 'system'/'auto'
  consentScope: 'task_promotion',      // required
  reason: 'long-running research',
});
// Stable error codes:
//   'task_promotion_requires_human_actor'
//   'task_promotion_requires_consent_scope'
```

## Stable error codes (parser)

v1.2 codes (still emitted):
`workflow_invalid_yaml`, `workflow_invalid_schema`, `workflow_missing_root`,
`workflow_missing_required_field`, `workflow_invalid_type`,
`workflow_duplicate_step_id`, `workflow_circular_requires`,
`workflow_unknown_requires`, `workflow_agent_and_squad`,
`workflow_neither_agent_nor_squad`.

v1.3 additions:
- `workflow_invalid_mode` — mode not in {task, service}
- `workflow_invalid_tools_policy` — tools.allowed/restricted/forbidden not arrays
- `workflow_invalid_triggers` — triggers not array OR contains unknown name
- `workflow_invalid_success_criteria` — entry missing {id, description}

## Step-level fields (unchanged)

```yaml
- id: <unique>
  agent: <name>  |  squad: <name>     # exactly one
  action: <action-name>
  task: <task-file.md>                # optional, looked up in templates
  creates: [<artifact>, ...]
  updates: [<artifact>, ...]
  requires: <step-id-or-artifact>     # dependency
  duration_estimate: "30-60 min"
  condition: <boolean-literal>        # v1.2 supports only literal flags
  optional: true | false
  elicit: true | false                # pauses run with status=awaiting_input
  idempotent: true | false            # safe to re-execute on resume
  notes: |
    Free-text operator notes carried into the run log.
```

## What's NOT in v1.3

- **Expression-language `condition`** — still literal boolean flag only.
  v1.4 introduces a tiny expression parser.
- **Cross-workflow `requires`** — references must stay within the same
  workflow.
- **`on_step_failure: parallel`** — only sequential retry/abort/continue.

## Migration checklist (v1.2 → v1.3 YAMLs)

- ✓ No changes required. All fields are additive.
- ✓ Adopt `mode: 'service'` only when you're ready for the explicit-opt-in contract.
- ✓ Adopt `tools:` policy when your operators want runtime gating.

## Test references

- `test_workflow_parser.ts` — v1.2 parser regression
- `test_workflow_schema_backward_compat.ts` — v1.3 schema additions + v1.2 fixtures
- `test_workflow_engine.ts` — engine integration
- `test_service_mode_explicit_only.ts` — mode contract
