Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
459 changes: 431 additions & 28 deletions apps/decodex/src/execution_program.rs

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions apps/decodex/src/orchestrator/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6511,16 +6511,21 @@ fn append_rendered_execution_programs(output: &mut String, snapshot: &OperatorSt
.map_or_else(String::new, |warning| format!(" readback_warning={warning}"));

output.push_str(&format!(
"- program_id: {} source_contract_id: {} nodes={} ready={} blocked={} paused={} active={} completed={} stale={} queue_label_eligible={} mapped_issues={}{}\n",
"- program_id: {} source_contract_id: {} nodes={} planned={} mapped={} ready={} queued={} blocked={} held={} active={} attention={} completed={} stale={} superseded={} queue_label_eligible={} mapped_issues={}{}\n",
program.program_id,
program.source_contract_id,
program.node_count,
program.planned_count,
program.mapped_count,
program.ready_count,
program.queued_count,
program.blocked_count,
program.paused_count,
program.held_count,
program.active_count,
program.needs_attention_count,
program.completed_count,
program.stale_count,
program.superseded_count,
program.queue_label_eligible_count,
mapped_issues,
readback_warning,
Expand Down
9 changes: 7 additions & 2 deletions apps/decodex/src/orchestrator/tests/operator/status/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,17 @@ fn operator_status_text_surfaces_execution_program_summary() {
program_id: String::from("program-853"),
source_contract_id: String::from("contract-852"),
node_count: 3,
planned_count: 0,
mapped_count: 0,
ready_count: 1,
queued_count: 0,
blocked_count: 1,
paused_count: 0,
held_count: 0,
active_count: 0,
needs_attention_count: 0,
completed_count: 1,
stale_count: 0,
superseded_count: 0,
queue_label_eligible_count: 1,
mapped_issue_identifiers: vec![String::from("XY-853")],
readback_warning: None,
Expand All @@ -201,7 +206,7 @@ fn operator_status_text_surfaces_execution_program_summary() {
assert!(rendered.contains("Execution programs: 1"));
assert!(rendered.contains("Execution Programs"));
assert!(rendered.contains(
"program_id: program-853 source_contract_id: contract-852 nodes=3 ready=1 blocked=1 paused=0 active=0 completed=1 stale=0 queue_label_eligible=1 mapped_issues=XY-853"
"program_id: program-853 source_contract_id: contract-852 nodes=3 planned=0 mapped=0 ready=1 queued=0 blocked=1 held=0 active=0 attention=0 completed=1 stale=0 superseded=0 queue_label_eligible=1 mapped_issues=XY-853"
));
}

Expand Down
9 changes: 6 additions & 3 deletions apps/decodex/src/orchestrator/tests/runtime/loop_scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,12 @@ impl LoopScenarioHarness {
assert_eq!(evaluation.ready_node_ids(), vec!["node-ready"]);
assert_eq!(evaluation.startable_node_ids(), vec!["node-ready"]);
assert_eq!(summary.ready_count, 1);
assert_eq!(summary.queued_count, 0);
assert_eq!(summary.blocked_count, 1);
assert_eq!(summary.paused_count, 1);
assert_eq!(summary.mapped_count, 1);
assert_eq!(summary.held_count, 2);
assert_eq!(summary.active_count, 1);
assert_eq!(summary.needs_attention_count, 0);
assert_eq!(summary.queue_label_eligible_count, 1);
assert_eq!(
loop_scenario_queue_action(evaluation, "node-ready"),
Expand Down Expand Up @@ -609,7 +612,7 @@ fn loop_scenario_active_node() -> crate::execution_program::ExecutionProgramNode
.with_linear_issue(
ExecutionLinearIssueMapping::new("linear-node-active", "XY-864", "Todo")
.expect("issue mapping should build")
.with_queue_label(true)
.with_program_owned_queue_label(true)
.with_active_label(true),
)
.expect("active issue should attach")
Expand Down Expand Up @@ -759,7 +762,7 @@ fn loop_scenario_node(
"Todo",
)
.expect("issue mapping should build")
.with_queue_label(has_queue_label),
.with_program_owned_queue_label(has_queue_label),
)
.expect("issue mapping should attach")
}
Expand Down
21 changes: 18 additions & 3 deletions apps/decodex/src/orchestrator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,12 +1293,17 @@ struct OperatorExecutionProgramStatus {
program_id: String,
source_contract_id: String,
node_count: usize,
planned_count: usize,
mapped_count: usize,
ready_count: usize,
queued_count: usize,
blocked_count: usize,
paused_count: usize,
held_count: usize,
active_count: usize,
needs_attention_count: usize,
completed_count: usize,
stale_count: usize,
superseded_count: usize,
queue_label_eligible_count: usize,
mapped_issue_identifiers: Vec<String>,
readback_warning: Option<String>,
Expand All @@ -1312,12 +1317,17 @@ impl OperatorExecutionProgramStatus {
program_id: summary.program_id,
source_contract_id: record.source_contract_id().to_owned(),
node_count: record.program().nodes().len(),
planned_count: summary.planned_count,
mapped_count: summary.mapped_count,
ready_count: summary.ready_count,
queued_count: summary.queued_count,
blocked_count: summary.blocked_count,
paused_count: summary.paused_count,
held_count: summary.held_count,
active_count: summary.active_count,
needs_attention_count: summary.needs_attention_count,
completed_count: summary.completed_count,
stale_count: summary.stale_count,
superseded_count: summary.superseded_count,
queue_label_eligible_count: summary.queue_label_eligible_count,
mapped_issue_identifiers: summary.mapped_issue_identifiers,
readback_warning: None,
Expand All @@ -1331,12 +1341,17 @@ impl OperatorExecutionProgramStatus {
program_id: record.program_id().to_owned(),
source_contract_id: record.source_contract_id().to_owned(),
node_count,
planned_count: 0,
mapped_count: 0,
ready_count: 0,
queued_count: 0,
blocked_count: 0,
paused_count: 0,
held_count: 0,
active_count: 0,
needs_attention_count: 0,
completed_count: 0,
stale_count: node_count,
superseded_count: 0,
queue_label_eligible_count: 0,
mapped_issue_identifiers: Vec::new(),
readback_warning: Some(String::from("source_decision_contract_missing")),
Expand Down
10 changes: 6 additions & 4 deletions docs/reference/operator-control-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ Decodex currently runs as a local, single-machine control plane:
promotion and internal Execution Program state are governed by
[`../spec/loop-runtime.md`](../spec/loop-runtime.md), not by dashboard graph editing
or user-visible DAG commands.
- Execution Program readback is intentionally summarized: operators may see counts of
ready, blocked, paused, active, completed, stale, and queue-label-eligible nodes plus
mapped issue identifiers. Low-level node edges and graph operations remain internal
- Program intake readback is intentionally summarized: operators may see counts of
ready, queued, blocked, held, stale, attention, completed, and
queue-label-eligible nodes plus mapped issue identifiers. Optional planned, mapped,
active, and superseded counts may appear when the runtime has that detail. Low-level
node edges, graph operations, and queue-label ownership records remain internal
runtime state.

Decodex App is a native shell over the same local runtime and account-pool state. On
Expand Down Expand Up @@ -164,7 +166,7 @@ hidden unless `--include-payload` is explicitly requested for local repair.

| Surface | Owns | Does Not Own |
| --- | --- | --- |
| Runtime SQLite DB | active leases, attempts, run-control channels, protocol events, private execution events, Decision Contracts, internal Execution Programs, worktree mappings, retry state, retained PR state, review-policy checkpoints with structured independent-review detail, loop-guardrail checkpoints, phase-goal signals, phase timing, connector backoff, project registry | human backlog grooming or durable team-visible issue history |
| Runtime SQLite DB | active leases, attempts, run-control channels, protocol events, private execution events, Decision Contracts, Program Intake Plans, internal Execution Programs, program-owned queue-label evidence, worktree mappings, retry state, retained PR state, review-policy checkpoints with structured independent-review detail, loop-guardrail checkpoints, phase-goal signals, phase timing, connector backoff, project registry | human backlog grooming or durable team-visible issue history |
| Central project config | `service_id`, repo root, worktree root, tracker/GitHub credential env-var names, enabled project registration | per-run state or issue ownership |
| Project `WORKFLOW.md` | repo policy, validation gate, state names, retry/review policy | runtime ownership, queue labels, credentials, model overrides |
| Linear | team-visible issue state, queue/active/manual-attention labels, coarse execution ledger comments, progress/failure/handoff/closeout summaries | high-frequency runtime truth, heartbeat, token pressure, raw attempts, private execution evidence, connector retry budgets |
Expand Down
120 changes: 91 additions & 29 deletions docs/spec/loop-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,45 @@ Promotion may create or update normal Linear issues, dependencies, labels, or qu
intent. It must not expose an ordinary user workflow that depends on graph ids,
manual DAG edge editing, or hidden Codex goal state.

## Program Intake Plan

Program intake is first-class loop-runtime behavior after a Decision Contract is
accepted or after the user supplies an explicit executable issue-batch intake. The
ordinary user workflow stays natural language: the user may ask Decodex to push an
accepted goal forward or provide a batch of issue briefs, but the user does not edit a
DAG, set queue labels by hand, or operate hidden graph commands.

The durable intake-planning payload is versioned as
`decodex.program_intake_plan/1` with `record_version = 1`. The runtime stores it in
runtime SQLite as part of, or directly adjacent to, the internal
`decodex.execution_program/1` record. Linear issue descriptions, Linear comments, and
operator summaries may expose only sparse public projections.

The payload carries:

| Field | Meaning |
| --- | --- |
| `plan_id` | Stable runtime id for this intake plan. |
| `service_id` | Registered service that owns program reconciliation for this plan. |
| `intake_kind` | `goal_intake` for promoted natural-language goals, or `issue_batch_intake` for a supplied batch of executable issue briefs. |
| `source_contract_id` | Accepted Decision Contract id for goal intake, when present. |
| `accepted_contract_fingerprint` | Fingerprint used to detect drift from the accepted contract or batch boundary. |
| `public_summary` | Public-safe objective/readiness summary suitable for status readback. |
| `node_projection` | Optional public-safe node summary or count metadata. The full internal nodes, dependencies, conflict domains, issue mapping, queue-label ownership metadata, and lifecycle evaluation inputs live in the paired `decodex.execution_program/1` payload. |

Goal intake and issue-batch intake both materialize normal Linear issues. A goal
intake starts from an accepted Decision Contract and shapes one or more issue briefs.
An issue-batch intake starts from a supplied batch of issue briefs and records the
batch boundary as the accepted authority. In both cases, dependencies and ordering may
be represented internally as a DAG, but executable work still enters Decodex as
ordinary Linear issue lanes with generic natural-language descriptions, tracker
states, validation expectations, and Decodex lifecycle writeback.

## Internal Execution Program

An Execution Program is internal loop-runtime state derived from accepted Decision
Contracts. It may use DAG semantics, but the graph is backstage state rather than the
user-facing workflow.
An Execution Program is internal loop-runtime state derived from an accepted Program
Intake Plan. It may use DAG semantics, but the graph is backstage state rather than
the user-facing workflow.

The runtime-facing Execution Program payload is versioned as
`decodex.execution_program/1` with `record_version = 1`. It is stored in runtime
Expand All @@ -308,56 +342,84 @@ SQLite and carries:
| Field | Meaning |
| --- | --- |
| `program_id` | Stable runtime identifier for this internal program. |
| `service_id` | Registered Decodex service that owns queue-label decisions. |
| `source_contract_id` | Accepted Decision Contract that authorized the program. |
| `accepted_contract_fingerprint` | Fingerprint of the accepted contract used for drift detection. |
| `service_id` | Registered Decodex service that owns program reconciliation. |
| `source_contract_id` | Accepted Decision Contract that authorized the program, when the program came from goal intake. |
| `accepted_contract_fingerprint` | Fingerprint of the accepted contract or batch authority used for drift detection. |
| `program_intake_plan` | The embedded or linked `decodex.program_intake_plan/1` payload. |
| `nodes` | Internal executable nodes. |

Each program node carries:

- objective lineage back to the accepted Decision Contract
- objective lineage back to the accepted Decision Contract or issue-batch authority
- executable stage: `research`, `design`, `spec`, `schema`, `runtime`, `plugin`,
`eval`, or `handoff`
- explicit dependencies with optional terminal-state requirements; when omitted, the
registered `WORKFLOW.md` terminal states satisfy the dependency
- conflict domains for `file`, `module`, `state`, `credentials`,
`tracker_ownership`, and `review_surface`
- acceptance expectations and validation expectations
- runtime-derived lifecycle state: `planned`, `mapped`, `ready`, `queued`, `active`,
`blocked`, `needs_attention`, `completed`, `stale`, or `superseded`
- queue intent: `not_ready`, `ready_to_queue`, `queued`, `active`, `paused`, `done`,
or `canceled`
or `canceled`; `paused` is legacy queue intent and renders as held lifecycle
readback, not as a user-facing graph state
- linked normal Linear issue identity and startability facts when the node becomes
executable
- accepted-contract fingerprint used to detect node-level drift
- queue-label ownership metadata for labels applied by program reconciliation
- accepted authority fingerprint used to detect node-level drift

Normal Linear issues remain the executable Decodex lanes. A program node may become
eligible only by creating or updating a normal issue with enough natural-language
briefing for generic dispatch and by applying the configured queue policy. The
Execution Program does not replace Linear as the team-visible backlog or the runtime
lane model.

Readiness evaluation is runtime-owned. It classifies nodes as:
Lifecycle evaluation is runtime-owned. It classifies nodes as:

| State | Meaning |
| --- | --- |
| `not_ready` | The node is intentionally not startable yet. |
| `ready` | Dependencies, conflicts, acceptance expectations, validation expectations, and issue mapping allow normal execution. |
| `blocked` | A dependency, conflict domain, missing expectation, or Linear issue mapping blocks execution. |
| `paused` | The accepted program intentionally paused the node. |
| `active` | The node already has an active lane and must not retain the queue label. |
| `completed` | The node is `done` or `canceled`. |
| `stale` | The node or program no longer matches the accepted Decision Contract. |

Queue-label action is derived from readiness, not from graph presence alone. Only a
`ready` node whose queue intent is `ready_to_queue` or `queued`, and whose mapped
Linear issue is in a registered startable state with no opt-out, needs-attention,
active-label, missing-briefing, or terminal-state blocker, may receive or retain
`decodex:queued:<service-id>`. Non-startable, blocked, stale, active, paused,
completed, or unmapped nodes must not receive or retain that queue label.

Operator readback may summarize program progress as counts of ready, blocked, paused,
active, completed, stale, and queue-label-eligible nodes plus the mapped issue
identifiers. It must not turn graph ids, edge editing, or DAG commands into the
primary user workflow.
| `planned` | The node exists only inside the Program Intake Plan and has no normal Linear issue mapping yet. |
| `mapped` | The node has a normal Linear issue mapping but is intentionally held from queueing. |
| `ready` | Dependencies, conflicts, acceptance expectations, validation expectations, and issue mapping allow normal execution, but the queue label has not been applied or retained yet. |
| `queued` | The node is ready and its mapped issue currently carries a program-owned service queue label. |
| `active` | The mapped issue already has an active lane and must not retain program-owned queue labels. |
| `blocked` | A dependency, conflict domain, missing expectation, non-startable issue state, missing issue mapping, or missing briefing blocks execution. |
| `needs_attention` | The mapped issue carries the configured human-attention label or equivalent human-required stop. |
| `completed` | The node is done or canceled under the accepted program. |
| `stale` | The node or program no longer matches the accepted authority fingerprint. |
| `superseded` | A later accepted contract or batch authority replaced this node. |

Queue-label action is derived from lifecycle readiness, not from graph presence alone.
Only a `ready` node whose queue intent is `ready_to_queue` or `queued`, and whose
mapped Linear issue is in a registered startable state with no opt-out,
needs-attention, active-label, missing-briefing, or terminal-state blocker, may
receive or retain `decodex:queued:<service-id>`.

Queue-label ownership is fail-closed:

- Apply the service queue label only when the node is `ready`, the mapped issue lacks
the label, and program reconciliation records ownership for that node, issue, label,
service id, and program id.
- Retain the label only while the node remains `ready` or `queued`, the mapped issue
remains startable, and the program-owned label record still matches the same node
and service label. A human-owned queued label may be left in place but must not be
converted into program-owned removal authority.
- Remove the label only when a matching program-owned label record proves Decodex
applied it through program reconciliation and the node is no longer queueable, for
example `blocked`, `active`, `needs_attention`, `completed`, `stale`,
`superseded`, unmapped, terminal, or missing a generic dispatch briefing.
- If the queue label is present but no matching program-owned ownership record exists,
treat it as human-owned or ownership-unknown. Do not remove it during program
reconciliation; surface a public-safe ownership warning or local operator reason
instead.

Operator readback must summarize program progress without exposing graph operations as
workflow. Public-safe readback fields are: intake kind, public summary, ready count,
queued count, blocked count, held count, stale count, attention count, completed
count, optional planned/mapped/active/superseded counts, queue-label-eligible count,
mapped issue identifiers, and coarse next action. It must not expose internal graph
ids, edge lists, raw dependency payloads, host-local paths, transcripts, credentials,
or private evidence references as public/team-visible status.

## Drift Handling

Expand Down
Loading