Skip to content
Merged
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
93 changes: 83 additions & 10 deletions apps/decodex/src/program_intake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@ struct GoalIntakeAnchor {

struct GoalIssueBriefInput<'a> {
contract: &'a DecisionContract,
program_id: &'a str,
node_id: &'a str,
objective: &'a str,
dependencies: &'a [String],
conflict_domains: &'a [ExecutionConflictDomain],
Expand Down Expand Up @@ -654,16 +652,14 @@ fn goal_issue_plans(contract: &DecisionContract, program_id: &str) -> Result<Vec
let dependencies = Vec::new();
let description = render_goal_issue_brief(GoalIssueBriefInput {
contract,
program_id,
node_id: &node_id,
objective,
dependencies: &dependencies,
conflict_domains: &conflict_domains,
acceptance: &acceptance,
validation: &validation,
})?;

validate_generated_issue_text(&title, &description)?;
validate_generated_issue_text(&title, &description, &[program_id, &node_id])?;

plans.push(GoalIssuePlan {
node_id,
Expand Down Expand Up @@ -986,8 +982,6 @@ fn render_goal_issue_brief(input: GoalIssueBriefInput<'_>) -> Result<String> {
&mut output,
&format!("Accepted Decision Contract: `{}`", input.contract.contract_id()),
);
append_item(&mut output, &format!("Execution Program: `{}`", input.program_id));
append_item(&mut output, &format!("Execution Program node: `{}`", input.node_id));
append_optional_item(
&mut output,
"Source issue",
Expand Down Expand Up @@ -1051,18 +1045,47 @@ fn append_items_or_none(output: &mut String, items: &[String]) {
}
}

fn validate_generated_issue_text(title: &str, description: &str) -> Result<()> {
fn validate_generated_issue_text(
title: &str,
description: &str,
private_identifiers: &[&str],
) -> Result<()> {
public_text::validate_public_text_field("generated issue title", title)
.map_err(|error| eyre::eyre!(error))?;

validate_public_issue_description(description)
ensure_no_generated_issue_private_identifier(
"generated issue title",
title,
private_identifiers,
)?;
validate_public_issue_description(description)?;

ensure_no_generated_issue_private_identifier(
"generated issue description",
description,
private_identifiers,
)
}

fn validate_public_issue_description(description: &str) -> Result<()> {
public_text::validate_public_text_field("generated issue description", description)
.map_err(|error| eyre::eyre!(error))
}

fn ensure_no_generated_issue_private_identifier(
field: &str,
text: &str,
private_identifiers: &[&str],
) -> Result<()> {
for identifier in private_identifiers {
if !identifier.is_empty() && text.contains(identifier) {
eyre::bail!("{field} contains a private Program Intake identifier.");
}
}

Ok(())
}

fn goal_acceptance(contract: &DecisionContract, objective: &str) -> Vec<String> {
let mut acceptance = vec![format!("Deliver this generated issue objective: {objective}")];

Expand Down Expand Up @@ -2089,13 +2112,63 @@ mod tests {
.expect("updated issue should exist");

assert!(updated.description.contains("## Objective"));
assert!(updated.description.contains("## Authority"));
assert!(updated.description.contains("Accepted Decision Contract: `goal-intake-contract`"));
assert!(updated.description.contains("Execution Program node:"));
assert!(updated.description.contains("Source issue: `XY-852`"));
assert!(updated.description.contains("## Dependencies"));
assert!(updated.description.contains("## Acceptance"));
assert!(updated.description.contains(
"Deliver this generated issue objective: Implement goal intake CLI/API behavior."
));
assert!(updated.description.contains("## Validation"));
assert!(updated.description.contains("Run cargo make test before handoff."));
assert!(updated.description.contains("## Stop Conditions"));
assert!(
updated
.description
.contains("Stop when promotion authority or required decisions are missing.")
);
assert!(!updated.description.contains("Execution Program: `"));
assert!(!updated.description.contains("Execution Program node:"));
assert!(!updated.description.contains(&report.program_id));

for issue in &report.issues {
assert!(!updated.description.contains(&issue.node_id));
}

assert!(!updated.description.contains("```"));
assert!(!updated.description.contains("private_evidence_refs"));
}

#[test]
fn generated_issue_text_validation_rejects_private_program_identifiers() {
let title_error = program_intake::validate_generated_issue_text(
"Expose goal-decodex-contract-private",
"## Objective\nUse normal public text.",
&["goal-decodex-contract-private"],
)
.expect_err("title must reject private program id");

assert!(
title_error
.to_string()
.contains("generated issue title contains a private Program Intake identifier")
);

let description_error = program_intake::validate_generated_issue_text(
"Use normal public text.",
"## Objective\nExpose goal:contract:01-private-node.",
&["goal:contract:01-private-node"],
)
.expect_err("description must reject private node id");

assert!(
description_error.to_string().contains(
"generated issue description contains a private Program Intake identifier"
)
);
}

#[test]
fn goal_intake_apply_persists_links_after_each_successful_issue_mutation() {
let store = StateStore::open_in_memory().expect("store should open");
Expand Down
3 changes: 3 additions & 0 deletions docs/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@
- Added `docs/evidence/index.md` for reusable public-safe proof concepts and durable
semantic-drift audit evidence.
- Recorded plugin-eval evidence for the Decodex plugin and new docs skill family.
- Clarified the Program Intake public/private boundary so generated Linear issue
descriptions omit internal Program and node identifiers while SQLite/operator
readback keeps private mappings.
10 changes: 8 additions & 2 deletions docs/runbook/research-to-execution-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ status: active
authority: procedural
owner: automation
tags: [runbook]
last_verified: 2026-06-16
code_refs: [apps/decodex/src/program_intake.rs, apps/decodex/src/execution_program.rs]
related: [../spec/loop-runtime.md]
drift_watch: [decodex intake goal, program_issue_mappings]
last_verified: 2026-06-17
---
# Research-To-Execution Loop

Expand Down Expand Up @@ -94,7 +97,10 @@ evidence.

Review improvement candidates in the summarized evidence. Use raw payload
readback only for local debugging, and do not paste private execution payloads into
public tracker comments or PR descriptions.
public tracker comments or PR descriptions. Generated Linear issue descriptions
should already omit internal Program and node identifiers; use SQLite-backed
Program Intake and issue-mapping readback when an operator needs to correlate a
public issue with its private runtime node.

## Dogfood Assessment

Expand Down
31 changes: 19 additions & 12 deletions docs/spec/loop-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner: runtime
tags: [spec]
code_refs: [apps/decodex/src/orchestrator/execution.rs, apps/decodex/src/orchestrator/prompting.rs, apps/decodex/src/research_design.rs, apps/decodex/src/loop_contract.rs, apps/decodex/src/execution_program.rs, apps/decodex/src/program_intake.rs]
drift_watch: [phase_goal, docs_impact, decodex.decision_contract/1, execution_program, decodex research compile, decodex research promote, decodex intake goal]
last_verified: 2026-06-16
last_verified: 2026-06-17
---
# Loop Runtime Specification

Expand Down Expand Up @@ -349,9 +349,11 @@ The durable intake-planning payload is versioned as
runtime SQLite as part of, or directly adjacent to, the internal
`decodex.execution_program/1` record. The adjacent runtime readback rows are
`program_intake_plans` and `program_issue_mappings`; they are derived from the
versioned program payload and exist so operator status, scheduling, and tests can query intake state without
copying private graph payloads into Linear. Linear issue descriptions, Linear comments,
and operator summaries may expose only sparse public projections.
versioned program payload and exist so operator status, scheduling, and tests can query
intake state without copying private graph payloads into Linear. Linear issue
descriptions and comments must not expose Execution Program ids, Program node ids, or
raw graph mechanics; public operator summaries may expose only sparse projections such
as counts, public issue identifiers, and coarse readiness reason codes.

The payload carries:

Expand All @@ -375,12 +377,15 @@ states, validation expectations, and Decodex lifecycle writeback.

Every mapped normal issue must carry a generic dispatch briefing that a cold-start
implementation lane can execute without replaying chat or reading private runtime
state. A complete Decodex-planned briefing names one outcome, required reading,
in-scope work, explicit non-goals, current-tree landing zone, ownership boundary,
acceptance criteria, validation expectations, and any real dependencies, blockers, or
conflict domains. At minimum, runtime eligibility rejects a machine-only fenced block
as the issue description; private pointers, progress checkpoints, review summaries,
PR bodies, or runtime events do not substitute for the issue briefing.
state. A complete Decodex-planned briefing names one outcome, public authority
summary, required reading, in-scope work, explicit non-goals, current-tree landing
zone, ownership boundary, acceptance criteria, validation expectations, stop
conditions, and any real dependencies, blockers, or conflict domains. The public
authority summary may cite the accepted Decision Contract or source issue, but it must
not render the internal Execution Program id or node id. At minimum, runtime
eligibility rejects a machine-only fenced block as the issue description; private
pointers, progress checkpoints, review summaries, PR bodies, or runtime events do not
substitute for the issue briefing.

The operator CLI surface for promoted goals is
`decodex intake goal --project <service-id> <CONTRACT_ID> --dry-run`, or the same
Expand All @@ -389,8 +394,10 @@ and existing generated-issue links, then prints the proposed normal issue briefs
dependencies, conflict domains, and dispatch plan without mutating Linear and without
persisting local runtime rows. `--apply` is the explicit mutation boundary: it creates
or updates generated normal Linear issue descriptions, links generated issue ids and
node ids back to the Decision Contract, and stores the paired Program Intake Plan and
Execution Program in runtime SQLite. Apply must not run implementation inline and
internal node ids in runtime state, and stores the paired Program Intake Plan and
Execution Program in runtime SQLite. The generated Linear description remains a
natural-language issue brief and does not include those internal ids. Apply must not
run implementation inline and
must not apply or remove `decodex:queued:<service-id>`; the persisted Program is then
eligible for direct scheduler dispatch. If the contract is latent, rejected, still needs a human
decision, carries unresolved missing decisions, or lacks proposed issue summaries,
Expand Down
16 changes: 9 additions & 7 deletions docs/spec/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ status: active
authority: normative
owner: runtime
tags: [spec]
code_refs: [apps/decodex/src/agent/tracker_tool_bridge.rs, apps/decodex/src/agent/tracker_tool_bridge/tools.rs, apps/decodex/src/orchestrator/execution.rs, apps/decodex/src/orchestrator/run_cycle.rs, apps/decodex/src/orchestrator/status.rs]
drift_watch: [issue_progress_checkpoint, issue_terminal_finalize, docs_impact, manual_attention, review_handoff, review_repair, closeout, phase_goal]
last_verified: 2026-06-16
code_refs: [apps/decodex/src/agent/tracker_tool_bridge.rs, apps/decodex/src/agent/tracker_tool_bridge/tools.rs, apps/decodex/src/orchestrator/execution.rs, apps/decodex/src/orchestrator/run_cycle.rs, apps/decodex/src/orchestrator/status.rs, apps/decodex/src/program_intake.rs, apps/decodex/src/execution_program.rs]
drift_watch: [issue_progress_checkpoint, issue_terminal_finalize, docs_impact, manual_attention, review_handoff, review_repair, closeout, phase_goal, decodex intake goal, program_issue_mappings]
last_verified: 2026-06-17
---
# Runtime Specification

Expand Down Expand Up @@ -85,10 +85,12 @@ state or this state machine.
proposed normal Linear issue split, dependencies, conflict domains, and dispatch plan
without mutating Linear or persisting Program Intake rows. `--apply` creates or
updates generated normal Linear issue briefs, links generated issue ids and internal
node ids back into the Decision Contract and Execution Program records, and persists
the local Program Intake Plan plus Execution Program state. It must not start
implementation inline and must not apply queue labels; direct Program dispatch is
performed by the scheduler after the Program is persisted.
node ids back into private runtime records, and persists the local Program Intake
Plan plus Execution Program state. The Linear description stays a natural-language
issue brief with objective, public authority summary, acceptance, validation, and
stop conditions; it must not include Execution Program ids or node ids. It must not
start implementation inline and must not apply queue labels; direct Program dispatch
is performed by the scheduler after the Program is persisted.
- `decodex intake issues <ISSUE>... --dry-run` is a tracker-read-only operator
surface for existing Linear issues. It classifies the supplied batch as ready,
held, blocked, stale, or unmapped and builds the same internal program model used
Expand Down