ticket: use base32 project record ids

This commit is contained in:
2026-06-09 22:10:47 +09:00
parent 0803bc3725
commit 4203988d74
798 changed files with 477 additions and 105 deletions
@@ -0,0 +1,144 @@
# Implementation report: ticket-config-role-profile-mapping
## Worktree / branch
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping`
- Branch: `work/ticket-config-role-profile-mapping`
## Commits
- `767870a ticket: add workspace ticket config`
- `8fab67b ticket: reject nix profile selectors`
## Summary
Implemented `.yoi/ticket.config.toml` as workspace-local Ticket orchestration configuration with fixed Ticket role slots and wired the configured backend root into the existing Ticket built-in feature adapter.
The implementation keeps Ticket role configuration narrow:
- fixed roles only: `intake`, `orchestrator`, `coder`, `reviewer`, `investigator`;
- role fields: `profile`, optional `launch_prompt`, optional `workflow`;
- no `system_instruction` role field;
- durable role/system behavior remains owned by the selected Profile.
## Final module/API layout
Added `crates/ticket/src/config.rs`, exported as `ticket::config`.
Main public API:
- `TicketConfig`
- `load_workspace(workspace_root)`
- `default_for_workspace(workspace_root)`
- `backend_root()`
- `role(role)`
- `profile_for(role)`
- `launch_prompt_for(role)`
- `workflow_for(role)`
- `TicketBackendConfig`
- `TicketBackendKind`
- `TicketRole`
- `ProfileSelectorRef`
- `PromptRef`
- `WorkflowRef`
- `TicketConfigError`
- `TICKET_CONFIG_RELATIVE_PATH = ".yoi/ticket.config.toml"`
The `ticket` crate keeps lightweight string refs and does not depend on `pod` or `manifest`.
## Schema/defaults implemented
Config path:
```toml
.yoi/ticket.config.toml
```
Backend:
```toml
[backend]
kind = "local"
root = "work-items"
```
Role example:
```toml
[roles.coder]
profile = "project:coder"
launch_prompt = "$workspace/prompts/ticket-coder"
workflow = "multi-agent-workflow"
```
Defaults when the config file is missing:
- backend: local `<workspace>/work-items`;
- all role profiles: `inherit`;
- launch prompts: none;
- workflows:
- intake: `ticket-intake-workflow`;
- orchestrator: `ticket-orchestrator-routing`;
- coder: `multi-agent-workflow`;
- reviewer: `multi-agent-workflow`;
- investigator: `ticket-orchestrator-routing`.
Validation rejects unknown top-level fields, unknown backend fields, unknown role fields, unknown roles, unsupported backend kinds, malformed/empty refs, path-like profile selector values, `.lua`, and `.nix` profile selector values.
## Pod Ticket feature adapter wiring
Updated `crates/pod/src/feature/builtin/ticket.rs` so `TicketFeature::for_workspace(...)` loads `ticket::config::TicketConfig`.
Behavior:
- missing config uses documented defaults, preserving previous `<workspace>/work-items` behavior;
- valid config uses configured `[backend].root`;
- malformed config fails closed: Ticket tools are not registered and a feature diagnostic is emitted;
- missing/unusable backend root preserves existing no-register behavior;
- tool authority continues to use `HostAuthority::TicketBackend { root }` for the configured backend root.
## Changed files
- `Cargo.lock`
- `crates/pod/src/feature/builtin/ticket.rs`
- `crates/ticket/Cargo.toml`
- `crates/ticket/src/config.rs`
- `crates/ticket/src/lib.rs`
- `package.nix`
## Review status
External sibling review initially requested one blocker fix:
- `ProfileSelectorRef` accepted `*.nix` profile selectors while existing `SpawnPod.profile` validation rejects them.
The blocker was fixed by commit `8fab67b` and re-review approved with no blockers.
Remaining non-blocker follow-ups:
- `HostAuthority::TicketBackend { root }` is derived from the configured path while the actual backend uses a canonicalized usable root; future explicit grant/audit comparisons should normalize consistently.
- Pod adapter root usage could be strengthened with an execution-level tool test against the configured root.
## Validation
Coder-reported validation for the main implementation passed:
- `cargo test -p ticket`
- `cargo test -p pod ticket --lib`
- `cargo test -p pod feature --lib`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check`
- `./tickets.sh doctor`
- `nix build .#yoi --no-link`
Coder-reported validation for the blocker fix passed:
- `cargo test -p ticket config`
- `cargo test -p ticket`
- `cargo fmt --check`
- `git diff --check`
## Ready for merge
Yes.
@@ -0,0 +1,292 @@
# Investigation and plan: Ticket config role profile mapping
## Conclusion
Implement `.yoi/ticket.config.toml` as Ticket orchestration configuration with fixed Ticket role slots. Do not build a generic Role registry.
The initial implementation should parse/validate the config, provide defaults, expose role-to-profile and prompt/workflow references, and wire the configured backend root into the existing Ticket built-in feature adapter. Pod spawning, TUI actions, and workflow state should remain follow-up work.
## Design position
Use fixed Ticket roles:
- `intake`
- `orchestrator`
- `coder`
- `reviewer`
- `investigator`
These are not arbitrary user-defined roles. They are the roles required by the Ticket feature/workflows.
Keep the boundary:
- Profile: Pod runtime recipe, including durable role behavior/system instruction when using role-specific profiles.
- Ticket role config: binds a fixed Ticket role to a Profile selector and optional launch prompt/workflow refs.
- Launch prompt: first committed task/user message for a concrete Ticket/action.
- Workflow: procedural flow, later possibly stateful.
## Current code map
### Ticket backend/tools
- `crates/ticket/src/lib.rs`
- Owns Ticket domain/backend and `LocalTicketBackend`.
- Current backend root is supplied by callers.
- `crates/ticket/src/tool.rs`
- Owns Ticket tool input/output and `llm_worker::Tool` implementations.
- `crates/pod/src/feature/builtin/ticket.rs`
- Thin built-in feature adapter.
- Currently resolves `<workspace>/work-items` directly.
- This is the first integration point for `.yoi/ticket.config.toml` backend root.
### Feature/host authority
- `crates/pod/src/feature.rs`
- Defines `HostAuthority::TicketBackend { root }`.
- Feature descriptor/install path already supports requested host authority and tool contribution wiring.
### Profile selection
- `crates/manifest/src/profile.rs`
- Owns Profile registry/selector resolution.
- `SpawnPod.profile` already accepts selectors such as `inherit`, default/source-qualified/unambiguous registry names.
- `crates/pod/src/spawn/tool.rs`
- Implements `SpawnPod` tool input with optional profile selector and `inherit` semantics.
- This should remain the actual spawning boundary; config should not duplicate full profile resolution behavior.
### Workflow resources
- `.yoi/workflow/*.md`
- Current workflow files are project-authored resources.
- `ticket-intake-workflow.md`, `ticket-orchestrator-routing.md`, `ticket-preflight-workflow.md`, and `multi-agent-workflow.md` are the relevant workflow refs.
- `crates/workflow/src/workflow.rs`
- Parses workflow frontmatter/body records.
- No stateful workflow runner exists yet.
### Prompt resources / launch prompts
- `crates/pod/src/prompt/loader.rs`
- Resolves instruction-file references like `$yoi/...` and `$user/...` for current startup/instruction use.
- Prompt catalog/resources are currently separate from workflow state.
- There is no implemented role-specific launch prompt engine yet.
- Role-specific durable system behavior should remain in the selected Profile for the MVP; this config should not override Profile system instruction.
## Important constraint
Do not make `ticket` depend on `pod`.
Possible dependency choices:
1. Put config parsing in `ticket` crate with raw profile/prompt/workflow string refs.
- Pros: Ticket config is close to Ticket backend concept.
- Cons: `ticket` learns about profile/prompt/workflow reference strings, but not their runtime resolution.
2. Put config parsing in `pod`.
- Pros: avoids exposing prompt/profile concepts from `ticket`.
- Cons: Ticket config becomes less reusable by future CLI/TUI code unless those crates also depend on `pod`.
Recommended MVP:
- Add config domain/parser to `crates/ticket`, using lightweight string wrapper types such as `ProfileSelectorRef`, `PromptRef`, and `WorkflowRef` without depending on `manifest` or `pod`.
- In the MVP, `PromptRef` is for launch prompts only. Do not add role-level `system_instruction` here; the selected Profile owns durable role system behavior.
- `pod` consumes this config and performs runtime interpretation where needed.
This preserves:
```text
ticket -> llm-worker / serde / toml only
pod -> ticket
```
and avoids:
```text
ticket -> pod
```
## Proposed schema
```toml
[backend]
kind = "local"
root = "work-items"
[roles.intake]
profile = "project:intake"
launch_prompt = "$workspace/ticket/intake/launch"
workflow = "ticket-intake-workflow"
[roles.orchestrator]
profile = "project:orchestrator"
launch_prompt = "$workspace/ticket/orchestrator/launch"
workflow = "ticket-orchestrator-routing"
[roles.coder]
profile = "inherit"
launch_prompt = "$workspace/ticket/coder/launch"
workflow = "multi-agent-workflow"
[roles.reviewer]
profile = "project:reviewer"
launch_prompt = "$workspace/ticket/reviewer/launch"
workflow = "multi-agent-workflow"
[roles.investigator]
profile = "inherit"
launch_prompt = "$workspace/ticket/investigator/launch"
workflow = "ticket-orchestrator-routing"
```
The specific prompt ref syntax should be accepted as opaque strings in this ticket. Runtime prompt resolution belongs to the later role launcher.
## Defaults
When `.yoi/ticket.config.toml` is missing:
- backend kind: `local`
- backend root: `work-items`
- role profiles: `inherit`
- workflow defaults:
- intake: `ticket-intake-workflow`
- orchestrator: `ticket-orchestrator-routing`
- coder: `multi-agent-workflow`
- reviewer: `multi-agent-workflow`
- investigator: `ticket-orchestrator-routing`
- launch prompt: none
When a role section exists but omits optional prompt/workflow refs:
- keep configured profile;
- fill workflow default for the fixed role;
- leave prompt refs as none.
## Implementation plan
### Phase 1: Config model/parser in `ticket`
Add a module such as `crates/ticket/src/config.rs`.
Types:
```rust
pub struct TicketConfig {
pub backend: TicketBackendConfig,
pub roles: TicketRoleProfiles,
}
pub struct TicketBackendConfig {
pub kind: TicketBackendKind,
pub root: PathBuf,
}
pub enum TicketBackendKind {
Local,
}
pub enum TicketRole {
Intake,
Orchestrator,
Coder,
Reviewer,
Investigator,
}
pub struct TicketRoleProfile {
pub profile: ProfileSelectorRef,
pub launch_prompt: Option<PromptRef>,
pub workflow: WorkflowRef,
}
```
Use string wrapper types for selectors/refs to avoid depending on `manifest`/`pod`.
Parsing behavior:
- `TicketConfig::load_workspace(workspace_root: &Path)` reads `.yoi/ticket.config.toml` if present.
- Missing file returns defaults.
- Relative backend root resolves against workspace root.
- Unknown roles are errors.
- Unknown top-level fields should be diagnostics/errors rather than silently ignored.
- Backend kind supports only `local` for now.
### Phase 2: Wire backend root into Pod Ticket feature adapter
Update `crates/pod/src/feature/builtin/ticket.rs`:
- Load `TicketConfig` from workspace root.
- Use `config.backend.root` instead of hard-coded `workspace/work-items`.
- Preserve current fail-closed behavior if root is missing/unusable.
- Keep `HostAuthority::TicketBackend { root }` consistent with the validated/canonical root where practical.
This directly improves existing Ticket tools without introducing role spawning yet.
### Phase 3: Tests
Ticket crate tests:
- missing config -> defaults;
- full config parses;
- partial role config uses role workflow defaults;
- unknown role rejects;
- unsupported backend kind rejects;
- relative backend root resolves against workspace;
- malformed profile/ref diagnostics are bounded.
Pod tests:
- Ticket built-in feature uses configured backend root;
- missing/unusable configured backend root does not register tools;
- default missing config still uses `<workspace>/work-items`.
### Phase 4: Documentation/example
Add one of:
- a short `.yoi/ticket.config.example.toml`, or
- a documented snippet under the ticket implementation report / docs if adding tracked config now is too early.
For this repository, adding actual `.yoi/ticket.config.toml` should be considered carefully. If added, defaults should likely use `inherit` profiles until dedicated profiles exist.
## Deferred follow-ups
### `ticket-role-pod-launcher`
- Take TicketRole + Ticket context + role config.
- Build `SpawnPod` requests.
- Resolve selected Profile using existing Profile registry; role-specific system behavior comes from that Profile.
- Resolve launch prompt separately from the selected Profile's system instruction.
- Commit launch prompt as the first user/task message, not hidden context.
- Include workflow ref in launch/task context.
### `tui-ticket-role-actions`
- Add TUI actions for fixed Ticket roles:
- Intake/refine Ticket;
- Route Ticket;
- Investigate;
- Implement;
- Review.
- Use the launcher rather than building SpawnPod requests inside UI code.
### Stateful workflow engine
- Persist workflow phase/state.
- Gate allowed tools by phase.
- Inject phase prompts only by committing them to history first.
- Keep Profile/SystemInstruction role-stable and task/phase prompts dynamic.
## Validation for implementation
Required:
- `cargo test -p ticket`
- `cargo test -p pod ticket --lib`
- `cargo test -p pod feature --lib`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check`
- `./tickets.sh doctor`
Optional if feasible:
- `nix build .#yoi --no-link`
@@ -0,0 +1,91 @@
# External review: ticket-config-role-profile-mapping
## 1. Result: request changes
Request changes. The implementation is otherwise close to the ticket, but one validation gap fails the requested alignment with existing `SpawnPod.profile` selector rules and should be fixed before merge.
## 2. Summary of implementation
The coder commit `767870a4fbf12f942a8b270e1cc316d7f35d3ef6` adds `crates/ticket/src/config.rs` and exports it from the `ticket` crate. The new parser reads `.yoi/ticket.config.toml`, defaults missing config to `<workspace>/work-items` plus fixed role defaults, models the fixed roles `intake`, `orchestrator`, `coder`, `reviewer`, and `investigator`, and stores profile / launch prompt / workflow references as lightweight strings without introducing `pod` or `manifest` dependencies.
The Pod built-in Ticket feature now loads `TicketConfig` from the Pod working directory, uses the configured backend root for `LocalTicketBackend`, and refuses to register Ticket tools when the config is malformed or the backend root is unusable. The implementation does not add Pod spawning, TUI actions, workflow state, system-instruction overlays, role registries, external trackers, or scheduler behavior.
## 3. Requirement-by-requirement assessment
- `.yoi/ticket.config.toml` path and schema: mostly satisfied. The parser uses the fixed path `.yoi/ticket.config.toml`, supports `[backend] kind/root`, and uses fixed `[roles.<role>]` sections with `profile`, optional `launch_prompt`, and optional `workflow`.
- Fixed roles only: satisfied. Unknown role names are rejected during config resolution.
- No `system_instruction` role field: satisfied. `deny_unknown_fields` rejects it and a test checks this.
- Missing config defaults: satisfied. Missing file returns local backend `<workspace>/work-items`, all role profiles `inherit`, no launch prompts, and the documented workflow defaults.
- Relative backend roots: satisfied. Relative roots are joined to the workspace root.
- Backend directories not auto-created: satisfied in the Pod adapter path. The adapter canonicalizes/checks the root and required `open/`, `pending/`, and `closed/` directories before registering tools.
- Unknown roles/fields and malformed refs: mostly satisfied, but see blocker below for an accepted path-like profile selector that `SpawnPod.profile` rejects.
- Crate dependency boundary: satisfied. `ticket` adds `toml` but does not depend on `pod` or `manifest`; profile/prompt/workflow refs remain string wrappers.
- Pod adapter configured root / fail-closed behavior: satisfied. Config parse errors and unusable roots produce diagnostics and no Ticket tools are registered.
- HostAuthority root consistency: acceptable but imperfect. The backend uses the canonicalized usable root, while `HostAuthority::TicketBackend { root }` is built from the pre-canonicalized configured path; see follow-up.
- Explicit non-goals: satisfied. I found no added Pod spawning, TUI action, workflow engine, prompt injection, Profile semantic change, `system_instruction` overlay, arbitrary role registry, storage rename, external tracker, or scheduler work.
- `Cargo.lock` / `package.nix`: changes are limited to adding the existing workspace `toml` dependency to `ticket` and updating the Nix cargo hash. That is necessary and looks safe.
- Tests: broadly cover missing/full/partial config, unknown role/field, relative root, unsupported backend kind, malformed profile path, and Pod adapter root/no-register behavior. They do not cover the blocker case below.
## 4. Blockers
1. `ProfileSelectorRef` accepts `legacy.nix`/`*.nix` as a valid role profile selector, but `SpawnPod.profile` explicitly rejects `*.nix` as path-like.
The ticket requires role profile selector syntax to stay aligned with existing `SpawnPod/profile` selectors where possible, and the review checklist asks that malformed refs be rejected or clearly reported. `crates/pod/src/spawn/tool.rs` rejects path-like profile values including `legacy.nix`, while `crates/ticket/src/config.rs` currently rejects `path:`, dot-prefixed values, values containing `/`, and `*.lua`, but not `*.nix`. Because role config values are meant to be later usable by role launch code, accepting a selector that the existing launch boundary rejects is a config-validation failure.
Expected fix: reject `*.nix` in `ProfileSelectorRef::new` and add a focused test alongside the existing malformed ref test.
## 5. Non-blockers / follow-ups
- `HostAuthority::TicketBackend { root }` is derived from `self.backend_root.display()` before canonicalization, while the actual `LocalTicketBackend` is built from `usable_root` after `canonicalize()`. This can make the granted/audited authority root differ from the root used by tools when the configured path includes `..` components or symlinks. The current implementation still requires matching host authority on the contributed tools and fail-closes on unusable roots, so I am not blocking on it, but the adapter should prefer a validated/canonical authority root where practical.
- The Pod adapter test for configured backend root checks feature root selection and tool registration count. It does not execute a tool against the configured root. The code path is straightforward (`LocalTicketBackend::new(usable_root)`), so this is acceptable, but an execution-level regression test would be stronger.
## 6. Validation assessed or rerun
Rerun/read-only checks:
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --stat develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --name-status develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --check develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline --decorate 767870a4fbf12f942a8b270e1cc316d7f35d3ef6`
Assessed by inspection:
- Ticket requirements, investigation/plan, and thread.
- `crates/ticket/src/config.rs`
- `crates/ticket/src/lib.rs`
- `crates/ticket/Cargo.toml`
- `crates/pod/src/feature/builtin/ticket.rs`
- `Cargo.lock`
- `package.nix`
- Relevant existing `SpawnPod.profile` selector validation in `crates/pod/src/spawn/tool.rs`.
Not rerun: `cargo test`, `cargo check`, `cargo fmt --check`, `./tickets.sh doctor`, or `nix build`. The review request allowed focused read-only validation, and rerunning these would write build/test artifacts outside the review artifact path in this scoped sibling review.
## 7. Residual risk
After the `*.nix` selector rejection is fixed, residual risk is mainly around future launch integration: prompt/workflow refs are intentionally lightweight strings and will need runtime validation when the role launcher resolves them. The configured backend root is wired into the current Ticket tools, but authority-root canonicalization should be tightened before relying on HostAuthority root strings for security/audit semantics beyond this feature gate.
---
## Re-review of blocker fix: 8fab67b
### Result: approve
The blocker is resolved, and I found no new blocker in the focused fix commit.
### Assessment
- `ProfileSelectorRef::new` now rejects values ending in `.nix` alongside other path-like selectors (`path:`, dot-prefixed selectors, slash-containing selectors, and `.lua`). This aligns the Ticket role profile config validation with the existing `SpawnPod.profile` path-selector rejection boundary for the reported case.
- A focused test, `nix_profile_selector_refs_are_rejected`, was added for `profile = "legacy.nix"` and asserts that the config load fails with the path-selector rejection message.
- The fix is limited to `crates/ticket/src/config.rs` and does not introduce source-boundary, runtime behavior, dependency, or scope expansion changes.
### Validation assessed
Rerun/read-only checks:
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline HEAD && git diff develop...HEAD -- crates/ticket/src/config.rs`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline HEAD && git show --unified=8 -- crates/ticket/src/config.rs`
### Blockers
None.
+146
View File
@@ -0,0 +1,146 @@
---
title: "Ticket config role profile mapping"
state: "closed"
created_at: "2026-06-05T17:33:22Z"
updated_at: "2026-06-05T18:48:15Z"
---
## Background
Ticket orchestration now has typed Ticket backend/tools and workflows for Intake and Orchestrator routing. The next step before TUI role actions is to make the workspace's Ticket orchestration configuration explicit.
The project should not introduce an arbitrary Role registry. The roles needed here are fixed by the Ticket feature/workflows:
- intake
- orchestrator
- coder
- reviewer
- investigator
Each fixed role needs to select a Profile and, later, a first launch prompt and workflow binding. Role-specific durable behavior should live in the selected Profile, not in this config file. This is Ticket orchestration configuration, not a generic Profile replacement.
## Goal
Add workspace-local Ticket configuration at `.yoi/ticket.config.toml` and a typed parser/resolver for fixed Ticket role profile mappings.
The MVP should establish the configuration file, fixed role schema, backend root configuration, validation, and role-to-profile selector resolution. It should not yet spawn Pods or add TUI actions.
## Requirements
- Add typed Ticket orchestration config support for `.yoi/ticket.config.toml`.
- Keep roles fixed, not arbitrary:
- `intake`
- `orchestrator`
- `coder`
- `reviewer`
- `investigator`
- Support backend configuration:
- local backend kind;
- root path, defaulting to `work-items` relative to the workspace.
- Support per-role configuration:
- `profile` selector string;
- optional launch/initial prompt reference;
- optional workflow slug/reference.
- Keep `profile` selector syntax aligned with existing SpawnPod/profile selectors where possible:
- `inherit`
- `default`
- `builtin:<name>`
- `user:<name>`
- `project:<name>`
- unqualified registry selector when accepted by existing profile resolution.
- Preserve the conceptual separation:
- Profile = Pod runtime recipe, including durable role behavior/system instruction when using role-specific profiles.
- launch prompt = first committed task/user message for a specific Ticket/action.
- workflow = procedural flow, later potentially stateful.
- Validate known fields and reject/diagnose unknown roles or malformed fields.
- Resolve relative backend roots against workspace root.
- Do not auto-create backend directories in this ticket.
- Update the existing Ticket built-in feature adapter to use the configured backend root when available, falling back to `work-items`.
- Expose a reusable resolver API for later Pod launch/TUI code:
- role -> profile selector;
- role -> optional launch prompt ref;
- role -> optional workflow slug;
- backend root.
## Non-goals
- Arbitrary role registry.
- Pod spawning or role launcher implementation.
- TUI action implementation.
- Stateful workflow engine.
- Per-phase workflow prompt injection.
- Changing Profile authoring/resolution semantics.
- Replacing `profiles.toml`.
- Renaming `work-items/`.
- External tracker integration.
- Scheduler/lease/queue automation.
## Suggested schema
```toml
[backend]
kind = "local"
root = "work-items"
[roles.intake]
profile = "project:intake"
launch_prompt = "$workspace/ticket/intake/launch"
workflow = "ticket-intake-workflow"
[roles.orchestrator]
profile = "project:orchestrator"
launch_prompt = "$workspace/ticket/orchestrator/launch"
workflow = "ticket-orchestrator-routing"
[roles.coder]
profile = "inherit"
launch_prompt = "$workspace/ticket/coder/launch"
workflow = "multi-agent-workflow"
[roles.reviewer]
profile = "project:reviewer"
launch_prompt = "$workspace/ticket/reviewer/launch"
workflow = "multi-agent-workflow"
[roles.investigator]
profile = "inherit"
launch_prompt = "$workspace/ticket/investigator/launch"
workflow = "ticket-orchestrator-routing"
```
MVP may make all role fields optional except `profile` when a role section is present. Missing file and missing role sections should fall back to builtin defaults.
## Default behavior
When `.yoi/ticket.config.toml` is absent:
- backend kind: local
- backend root: `<workspace>/work-items`
- all role profiles: `inherit`
- workflow defaults:
- intake: `ticket-intake-workflow`
- orchestrator: `ticket-orchestrator-routing`
- coder: `multi-agent-workflow`
- reviewer: `multi-agent-workflow`
- investigator: `ticket-orchestrator-routing`
- launch prompt refs: none
## Acceptance criteria
- `.yoi/ticket.config.toml` can be parsed from a workspace root.
- Missing config falls back to documented defaults.
- Fixed role sections parse correctly.
- Unknown roles are rejected or reported as configuration errors.
- Relative backend root resolves against workspace root.
- Backend root from config is used by the Ticket built-in feature adapter.
- Role profile selector strings are retained/parsed in a form later usable by role launching code.
- Optional `launch_prompt` and `workflow` refs are parsed and exposed without trying to run a workflow engine.
- Tests cover missing config, full config, partial role config, unknown role, relative backend root, and adapter backend-root usage.
- `cargo test -p ticket` and focused `cargo test -p pod ticket --lib` pass.
- `cargo check --workspace --all-targets`, `cargo fmt --check`, `git diff --check`, and `./tickets.sh doctor` pass.
## Follow-up tickets
- `ticket-role-pod-launcher`: construct role-specific `SpawnPod` requests from Ticket context, role config, selected Profile, launch prompt, workflow binding, and scope policy.
- `tui-ticket-role-actions`: expose fixed Ticket role actions in TUI using the launcher.
- Later workflow-state engine: persisted workflow phase/state, phase-specific allowed tools, and phase prompts committed to history before model use.
+53
View File
@@ -0,0 +1,53 @@
Ticket config role profile mapping is complete and merged.
Implementation:
- `767870a ticket: add workspace ticket config`
- `8fab67b ticket: reject nix profile selectors`
- merge commit: `9910df4 merge: add ticket config roles`
Summary:
- Added `.yoi/ticket.config.toml` support through `crates/ticket/src/config.rs`.
- Added fixed Ticket roles only:
- `intake`
- `orchestrator`
- `coder`
- `reviewer`
- `investigator`
- Added role config fields:
- `profile`
- optional `launch_prompt`
- optional `workflow`
- Did not add role-level `system_instruction`; durable role/system behavior remains owned by the selected Profile.
- Added backend config for local Ticket storage:
- `kind = "local"`
- `root = "work-items"`
- Missing config defaults to local `<workspace>/work-items`, all role profiles `inherit`, no launch prompts, and documented workflow defaults.
- Unknown roles/fields, unsupported backend kinds, malformed refs, path-like profile refs, `.lua`, and `.nix` profile refs are rejected.
- Wired the configured backend root into `crates/pod/src/feature/builtin/ticket.rs`.
- Preserved fail-closed/no-register behavior for malformed config or unusable backend roots.
- Kept dependency direction clean: `pod -> ticket`; `ticket` does not depend on `pod` or `manifest`.
Review:
- External sibling review initially requested one blocker fix: reject `*.nix` profile selectors to align with `SpawnPod.profile` validation.
- Coder fixed it in `8fab67b` and added a focused `legacy.nix` rejection test.
- Re-review approved with no blockers.
Non-blocker follow-ups:
- Normalize `HostAuthority::TicketBackend { root }` and canonical backend root consistently before relying on root strings for stricter security/audit comparison.
- Add an execution-level Pod adapter test that runs a Ticket tool against the configured backend root.
- Later role launcher should resolve Profile selectors/prompt refs/workflow refs at runtime and commit launch prompts as user/task messages.
Post-merge validation passed:
- `cargo test -p ticket`
- `cargo test -p pod ticket --lib`
- `cargo test -p pod feature --lib`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check`
- `./tickets.sh doctor`
- `nix build .#yoi --no-link`
+382
View File
@@ -0,0 +1,382 @@
<!-- event: create author: tickets.sh at: 2026-06-05T17:33:22Z -->
## Created
Created by tickets.sh create.
---
<!-- event: decision author: hare at: 2026-06-05T17:35:08Z -->
## Decision
Decision: implement `.yoi/ticket.config.toml` as Ticket orchestration configuration with fixed Ticket role slots.
Use fixed roles, not an arbitrary Role registry:
- intake
- orchestrator
- coder
- reviewer
- investigator
The config maps these fixed Ticket roles to Profile selector strings and optional role system instruction / launch prompt / workflow refs. This keeps Profile as the Pod runtime recipe while Ticket orchestration owns the role-to-profile binding.
The first implementation should parse/validate config and wire the configured backend root into Ticket tools. It should not spawn Pods, add TUI actions, or implement a stateful workflow engine yet.
Detailed investigation and implementation plan: `artifacts/investigation-plan.md`.
---
<!-- event: plan author: hare at: 2026-06-05T17:35:08Z -->
## Plan
Plan:
1. Add Ticket config model/parser, probably in `crates/ticket/src/config.rs`, using lightweight string wrapper types for Profile/prompt/workflow refs so `ticket` does not depend on `pod`.
2. Parse `.yoi/ticket.config.toml` from a workspace root, with defaults when missing.
3. Support `[backend]` local root and fixed `[roles.*]` sections.
4. Wire the configured backend root into `crates/pod/src/feature/builtin/ticket.rs` so Ticket tools no longer hard-code `<workspace>/work-items`.
5. Add focused tests in `ticket` and `pod`.
6. Defer Pod role launching, TUI actions, and workflow-state/prompt sequencing to follow-up tickets.
---
<!-- event: decision author: hare at: 2026-06-05T18:15:02Z -->
## Decision
Decision update: remove `system_instruction` from the MVP `.yoi/ticket.config.toml` role schema.
Rationale:
- The selected Profile already owns durable role behavior and system/instruction configuration.
- Adding `system_instruction` to Ticket role config would create an unclear override/merge boundary with Profile.
- Ticket role config should bind fixed Ticket roles to Profile selectors, workflow refs, and launch prompts only.
- Launch prompts remain useful here because they are per-action first committed task/user messages, unlike role-stable system behavior.
Updated MVP role fields:
- `profile`
- optional `launch_prompt`
- optional `workflow`
If system-level role overlays are needed later, design them explicitly as a separate prompt/context ticket, likely with an overlay name and clear history/cache semantics. Do not add an implicit system instruction override in this ticket.
---
<!-- event: review author: hare at: 2026-06-05T18:45:41Z status: approve -->
## Review: approve
# External review: ticket-config-role-profile-mapping
## 1. Result: request changes
Request changes. The implementation is otherwise close to the ticket, but one validation gap fails the requested alignment with existing `SpawnPod.profile` selector rules and should be fixed before merge.
## 2. Summary of implementation
The coder commit `767870a4fbf12f942a8b270e1cc316d7f35d3ef6` adds `crates/ticket/src/config.rs` and exports it from the `ticket` crate. The new parser reads `.yoi/ticket.config.toml`, defaults missing config to `<workspace>/work-items` plus fixed role defaults, models the fixed roles `intake`, `orchestrator`, `coder`, `reviewer`, and `investigator`, and stores profile / launch prompt / workflow references as lightweight strings without introducing `pod` or `manifest` dependencies.
The Pod built-in Ticket feature now loads `TicketConfig` from the Pod working directory, uses the configured backend root for `LocalTicketBackend`, and refuses to register Ticket tools when the config is malformed or the backend root is unusable. The implementation does not add Pod spawning, TUI actions, workflow state, system-instruction overlays, role registries, external trackers, or scheduler behavior.
## 3. Requirement-by-requirement assessment
- `.yoi/ticket.config.toml` path and schema: mostly satisfied. The parser uses the fixed path `.yoi/ticket.config.toml`, supports `[backend] kind/root`, and uses fixed `[roles.<role>]` sections with `profile`, optional `launch_prompt`, and optional `workflow`.
- Fixed roles only: satisfied. Unknown role names are rejected during config resolution.
- No `system_instruction` role field: satisfied. `deny_unknown_fields` rejects it and a test checks this.
- Missing config defaults: satisfied. Missing file returns local backend `<workspace>/work-items`, all role profiles `inherit`, no launch prompts, and the documented workflow defaults.
- Relative backend roots: satisfied. Relative roots are joined to the workspace root.
- Backend directories not auto-created: satisfied in the Pod adapter path. The adapter canonicalizes/checks the root and required `open/`, `pending/`, and `closed/` directories before registering tools.
- Unknown roles/fields and malformed refs: mostly satisfied, but see blocker below for an accepted path-like profile selector that `SpawnPod.profile` rejects.
- Crate dependency boundary: satisfied. `ticket` adds `toml` but does not depend on `pod` or `manifest`; profile/prompt/workflow refs remain string wrappers.
- Pod adapter configured root / fail-closed behavior: satisfied. Config parse errors and unusable roots produce diagnostics and no Ticket tools are registered.
- HostAuthority root consistency: acceptable but imperfect. The backend uses the canonicalized usable root, while `HostAuthority::TicketBackend { root }` is built from the pre-canonicalized configured path; see follow-up.
- Explicit non-goals: satisfied. I found no added Pod spawning, TUI action, workflow engine, prompt injection, Profile semantic change, `system_instruction` overlay, arbitrary role registry, storage rename, external tracker, or scheduler work.
- `Cargo.lock` / `package.nix`: changes are limited to adding the existing workspace `toml` dependency to `ticket` and updating the Nix cargo hash. That is necessary and looks safe.
- Tests: broadly cover missing/full/partial config, unknown role/field, relative root, unsupported backend kind, malformed profile path, and Pod adapter root/no-register behavior. They do not cover the blocker case below.
## 4. Blockers
1. `ProfileSelectorRef` accepts `legacy.nix`/`*.nix` as a valid role profile selector, but `SpawnPod.profile` explicitly rejects `*.nix` as path-like.
The ticket requires role profile selector syntax to stay aligned with existing `SpawnPod/profile` selectors where possible, and the review checklist asks that malformed refs be rejected or clearly reported. `crates/pod/src/spawn/tool.rs` rejects path-like profile values including `legacy.nix`, while `crates/ticket/src/config.rs` currently rejects `path:`, dot-prefixed values, values containing `/`, and `*.lua`, but not `*.nix`. Because role config values are meant to be later usable by role launch code, accepting a selector that the existing launch boundary rejects is a config-validation failure.
Expected fix: reject `*.nix` in `ProfileSelectorRef::new` and add a focused test alongside the existing malformed ref test.
## 5. Non-blockers / follow-ups
- `HostAuthority::TicketBackend { root }` is derived from `self.backend_root.display()` before canonicalization, while the actual `LocalTicketBackend` is built from `usable_root` after `canonicalize()`. This can make the granted/audited authority root differ from the root used by tools when the configured path includes `..` components or symlinks. The current implementation still requires matching host authority on the contributed tools and fail-closes on unusable roots, so I am not blocking on it, but the adapter should prefer a validated/canonical authority root where practical.
- The Pod adapter test for configured backend root checks feature root selection and tool registration count. It does not execute a tool against the configured root. The code path is straightforward (`LocalTicketBackend::new(usable_root)`), so this is acceptable, but an execution-level regression test would be stronger.
## 6. Validation assessed or rerun
Rerun/read-only checks:
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --stat develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --name-status develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git diff --check develop...HEAD`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline --decorate 767870a4fbf12f942a8b270e1cc316d7f35d3ef6`
Assessed by inspection:
- Ticket requirements, investigation/plan, and thread.
- `crates/ticket/src/config.rs`
- `crates/ticket/src/lib.rs`
- `crates/ticket/Cargo.toml`
- `crates/pod/src/feature/builtin/ticket.rs`
- `Cargo.lock`
- `package.nix`
- Relevant existing `SpawnPod.profile` selector validation in `crates/pod/src/spawn/tool.rs`.
Not rerun: `cargo test`, `cargo check`, `cargo fmt --check`, `./tickets.sh doctor`, or `nix build`. The review request allowed focused read-only validation, and rerunning these would write build/test artifacts outside the review artifact path in this scoped sibling review.
## 7. Residual risk
After the `*.nix` selector rejection is fixed, residual risk is mainly around future launch integration: prompt/workflow refs are intentionally lightweight strings and will need runtime validation when the role launcher resolves them. The configured backend root is wired into the current Ticket tools, but authority-root canonicalization should be tightened before relying on HostAuthority root strings for security/audit semantics beyond this feature gate.
---
## Re-review of blocker fix: 8fab67b
### Result: approve
The blocker is resolved, and I found no new blocker in the focused fix commit.
### Assessment
- `ProfileSelectorRef::new` now rejects values ending in `.nix` alongside other path-like selectors (`path:`, dot-prefixed selectors, slash-containing selectors, and `.lua`). This aligns the Ticket role profile config validation with the existing `SpawnPod.profile` path-selector rejection boundary for the reported case.
- A focused test, `nix_profile_selector_refs_are_rejected`, was added for `profile = "legacy.nix"` and asserts that the config load fails with the path-selector rejection message.
- The fix is limited to `crates/ticket/src/config.rs` and does not introduce source-boundary, runtime behavior, dependency, or scope expansion changes.
### Validation assessed
Rerun/read-only checks:
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline HEAD && git diff develop...HEAD -- crates/ticket/src/config.rs`
- `cd /home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping && git show --stat --oneline HEAD && git show --unified=8 -- crates/ticket/src/config.rs`
### Blockers
None.
---
<!-- event: implementation_report author: hare at: 2026-06-05T18:45:41Z -->
## Implementation report
# Implementation report: ticket-config-role-profile-mapping
## Worktree / branch
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-config-role-profile-mapping`
- Branch: `work/ticket-config-role-profile-mapping`
## Commits
- `767870a ticket: add workspace ticket config`
- `8fab67b ticket: reject nix profile selectors`
## Summary
Implemented `.yoi/ticket.config.toml` as workspace-local Ticket orchestration configuration with fixed Ticket role slots and wired the configured backend root into the existing Ticket built-in feature adapter.
The implementation keeps Ticket role configuration narrow:
- fixed roles only: `intake`, `orchestrator`, `coder`, `reviewer`, `investigator`;
- role fields: `profile`, optional `launch_prompt`, optional `workflow`;
- no `system_instruction` role field;
- durable role/system behavior remains owned by the selected Profile.
## Final module/API layout
Added `crates/ticket/src/config.rs`, exported as `ticket::config`.
Main public API:
- `TicketConfig`
- `load_workspace(workspace_root)`
- `default_for_workspace(workspace_root)`
- `backend_root()`
- `role(role)`
- `profile_for(role)`
- `launch_prompt_for(role)`
- `workflow_for(role)`
- `TicketBackendConfig`
- `TicketBackendKind`
- `TicketRole`
- `ProfileSelectorRef`
- `PromptRef`
- `WorkflowRef`
- `TicketConfigError`
- `TICKET_CONFIG_RELATIVE_PATH = ".yoi/ticket.config.toml"`
The `ticket` crate keeps lightweight string refs and does not depend on `pod` or `manifest`.
## Schema/defaults implemented
Config path:
```toml
.yoi/ticket.config.toml
```
Backend:
```toml
[backend]
kind = "local"
root = "work-items"
```
Role example:
```toml
[roles.coder]
profile = "project:coder"
launch_prompt = "$workspace/prompts/ticket-coder"
workflow = "multi-agent-workflow"
```
Defaults when the config file is missing:
- backend: local `<workspace>/work-items`;
- all role profiles: `inherit`;
- launch prompts: none;
- workflows:
- intake: `ticket-intake-workflow`;
- orchestrator: `ticket-orchestrator-routing`;
- coder: `multi-agent-workflow`;
- reviewer: `multi-agent-workflow`;
- investigator: `ticket-orchestrator-routing`.
Validation rejects unknown top-level fields, unknown backend fields, unknown role fields, unknown roles, unsupported backend kinds, malformed/empty refs, path-like profile selector values, `.lua`, and `.nix` profile selector values.
## Pod Ticket feature adapter wiring
Updated `crates/pod/src/feature/builtin/ticket.rs` so `TicketFeature::for_workspace(...)` loads `ticket::config::TicketConfig`.
Behavior:
- missing config uses documented defaults, preserving previous `<workspace>/work-items` behavior;
- valid config uses configured `[backend].root`;
- malformed config fails closed: Ticket tools are not registered and a feature diagnostic is emitted;
- missing/unusable backend root preserves existing no-register behavior;
- tool authority continues to use `HostAuthority::TicketBackend { root }` for the configured backend root.
## Changed files
- `Cargo.lock`
- `crates/pod/src/feature/builtin/ticket.rs`
- `crates/ticket/Cargo.toml`
- `crates/ticket/src/config.rs`
- `crates/ticket/src/lib.rs`
- `package.nix`
## Review status
External sibling review initially requested one blocker fix:
- `ProfileSelectorRef` accepted `*.nix` profile selectors while existing `SpawnPod.profile` validation rejects them.
The blocker was fixed by commit `8fab67b` and re-review approved with no blockers.
Remaining non-blocker follow-ups:
- `HostAuthority::TicketBackend { root }` is derived from the configured path while the actual backend uses a canonicalized usable root; future explicit grant/audit comparisons should normalize consistently.
- Pod adapter root usage could be strengthened with an execution-level tool test against the configured root.
## Validation
Coder-reported validation for the main implementation passed:
- `cargo test -p ticket`
- `cargo test -p pod ticket --lib`
- `cargo test -p pod feature --lib`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check`
- `./tickets.sh doctor`
- `nix build .#yoi --no-link`
Coder-reported validation for the blocker fix passed:
- `cargo test -p ticket config`
- `cargo test -p ticket`
- `cargo fmt --check`
- `git diff --check`
## Ready for merge
Yes.
---
<!-- event: close author: hare at: 2026-06-05T18:48:15Z status: closed -->
## Closed
Ticket config role profile mapping is complete and merged.
Implementation:
- `767870a ticket: add workspace ticket config`
- `8fab67b ticket: reject nix profile selectors`
- merge commit: `9910df4 merge: add ticket config roles`
Summary:
- Added `.yoi/ticket.config.toml` support through `crates/ticket/src/config.rs`.
- Added fixed Ticket roles only:
- `intake`
- `orchestrator`
- `coder`
- `reviewer`
- `investigator`
- Added role config fields:
- `profile`
- optional `launch_prompt`
- optional `workflow`
- Did not add role-level `system_instruction`; durable role/system behavior remains owned by the selected Profile.
- Added backend config for local Ticket storage:
- `kind = "local"`
- `root = "work-items"`
- Missing config defaults to local `<workspace>/work-items`, all role profiles `inherit`, no launch prompts, and documented workflow defaults.
- Unknown roles/fields, unsupported backend kinds, malformed refs, path-like profile refs, `.lua`, and `.nix` profile refs are rejected.
- Wired the configured backend root into `crates/pod/src/feature/builtin/ticket.rs`.
- Preserved fail-closed/no-register behavior for malformed config or unusable backend roots.
- Kept dependency direction clean: `pod -> ticket`; `ticket` does not depend on `pod` or `manifest`.
Review:
- External sibling review initially requested one blocker fix: reject `*.nix` profile selectors to align with `SpawnPod.profile` validation.
- Coder fixed it in `8fab67b` and added a focused `legacy.nix` rejection test.
- Re-review approved with no blockers.
Non-blocker follow-ups:
- Normalize `HostAuthority::TicketBackend { root }` and canonical backend root consistently before relying on root strings for stricter security/audit comparison.
- Add an execution-level Pod adapter test that runs a Ticket tool against the configured backend root.
- Later role launcher should resolve Profile selectors/prompt refs/workflow refs at runtime and commit launch prompts as user/task messages.
Post-merge validation passed:
- `cargo test -p ticket`
- `cargo test -p pod ticket --lib`
- `cargo test -p pod feature --lib`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check`
- `./tickets.sh doctor`
- `nix build .#yoi --no-link`
---