diff --git a/apps/decodex/src/program_intake.rs b/apps/decodex/src/program_intake.rs index cb4da0aba..22c1a394c 100644 --- a/apps/decodex/src/program_intake.rs +++ b/apps/decodex/src/program_intake.rs @@ -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], @@ -654,8 +652,6 @@ fn goal_issue_plans(contract: &DecisionContract, program_id: &str) -> Result Result) -> Result { &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", @@ -1051,11 +1045,26 @@ 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<()> { @@ -1063,6 +1072,20 @@ fn validate_public_issue_description(description: &str) -> Result<()> { .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 { let mut acceptance = vec![format!("Deliver this generated issue objective: {objective}")]; @@ -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"); diff --git a/docs/log.md b/docs/log.md index 59e65e003..ed400050e 100644 --- a/docs/log.md +++ b/docs/log.md @@ -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. diff --git a/docs/runbook/research-to-execution-loop.md b/docs/runbook/research-to-execution-loop.md index dd561bf08..df9c55d9d 100644 --- a/docs/runbook/research-to-execution-loop.md +++ b/docs/runbook/research-to-execution-loop.md @@ -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 @@ -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 diff --git a/docs/spec/loop-runtime.md b/docs/spec/loop-runtime.md index ab4ba5276..ac8bca685 100644 --- a/docs/spec/loop-runtime.md +++ b/docs/spec/loop-runtime.md @@ -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 @@ -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: @@ -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 --dry-run`, or the same @@ -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:`; 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, diff --git a/docs/spec/runtime.md b/docs/spec/runtime.md index 9873a0fac..c8a49ab45 100644 --- a/docs/spec/runtime.md +++ b/docs/spec/runtime.md @@ -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 @@ -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 ... --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