|
| 1 | +# Idea-intake bridge schema |
| 2 | + |
| 3 | +Product spec for **#4779**. Defines the interface that turns a freeform human idea into something the loop |
| 4 | +mechanics can execute against, so a person renting a loop does not have to hand-translate their intent into a |
| 5 | +well-formed, claimable issue. It is the input contract the feasibility scoring adapted in **#5671** reads, and |
| 6 | +the upstream boundary for the Rent-a-Loop execution path. |
| 7 | + |
| 8 | +This is a written spec only — no code is implemented here. It defines (1) the **idea submission schema**, (2) the |
| 9 | +**translation rules** from an idea to a structured, claimable task-graph, (3) the **scoring rubric** the execution |
| 10 | +loop evaluates its own output against, and (4) two worked examples traced end-to-end. |
| 11 | + |
| 12 | +## Design constraints (why the shape below) |
| 13 | + |
| 14 | +- **Reuse the existing feasibility gate, don't reinvent it.** `packages/loopover-engine/src/feasibility.ts` |
| 15 | + already reduces a candidate to a `go` / `raise` / `avoid` verdict over three discriminants |
| 16 | + (`claimStatus`, `duplicateClusterRisk`, `issueStatus`) with `avoid > raise > go` precedence. The idea bridge |
| 17 | + must produce, per constituent issue, exactly those discriminants so the adapted freeform scoring (#5671) can |
| 18 | + call the same `buildFeasibilityVerdict` without a parallel decision path. |
| 19 | +- **Emit the shape the loop already runs on.** Each constituent issue must translate into the fields the coding |
| 20 | + loop already consumes — a title, a body, `labels`, `linkedIssues`, and an acceptance-criteria artifact — so the |
| 21 | + bridge output drops straight into the existing claim → analyze → execute flow. |
| 22 | +- **Freeform in, structured out, human-auditable at the seam.** The idea is natural language; the task-graph is |
| 23 | + strict. The translation is the only fuzzy step, so it is explicit, bounded, and always reviewable before any |
| 24 | + loop claims work. |
| 25 | + |
| 26 | +## 1. Idea submission schema |
| 27 | + |
| 28 | +An `IdeaSubmission` is the raw input a renter provides. |
| 29 | + |
| 30 | +| Field | Type | Required | Notes | |
| 31 | +|---|---|---|---| |
| 32 | +| `id` | string | yes | Stable idea identifier (bridge-assigned). | |
| 33 | +| `title` | string | yes | One-line intent. Bounded (≤ 120 chars). | |
| 34 | +| `body` | string | yes | Freeform description of the desired outcome. Bounded + public-safe (no secrets). | |
| 35 | +| `targetRepo` | string | yes | `owner/name` the loop will act on. Must be an installed, registered repo. | |
| 36 | +| `constraints` | string[] | no | Renter-stated musts/must-nots (e.g. "no new dependencies", "keep the public API stable"). | |
| 37 | +| `acceptanceHints` | string[] | no | Renter's own success signals, folded into per-issue acceptance criteria. | |
| 38 | +| `priority` | `"normal" \| "high"` | no | Advisory only. Never maps to `gittensor:priority` (that label is maintainer-propagated, never renter-set). | |
| 39 | + |
| 40 | +Rules: |
| 41 | +- `title`/`body`/each `constraints[]` entry are length-bounded and stripped of anything non-public-safe at intake, |
| 42 | + mirroring the manifest text-slot handling in `focus-manifest.ts`. |
| 43 | +- `targetRepo` that is not installed+registered is rejected at intake, not scored — an uninstallable repo can |
| 44 | + never produce a `go`. |
| 45 | + |
| 46 | +## 2. Translation rules — idea → task-graph |
| 47 | + |
| 48 | +The bridge deterministically expands one `IdeaSubmission` into a `TaskGraph`. |
| 49 | + |
| 50 | +``` |
| 51 | +TaskGraph { |
| 52 | + ideaId: string |
| 53 | + issues: ConstituentIssue[] // ≥ 1, topologically ordered by dependsOn |
| 54 | + rubric: ScoringRubric // see §3 |
| 55 | +} |
| 56 | +
|
| 57 | +ConstituentIssue { |
| 58 | + key: string // stable within the graph, e.g. "issue-1" |
| 59 | + title: string // becomes the issue/PR title |
| 60 | + body: string // becomes the issue/PR body |
| 61 | + labels: string[] // gittensor:bug | gittensor:feature (type), never gittensor:priority |
| 62 | + dependsOn: string[] // keys of issues that must land first |
| 63 | + acceptanceCriteria: AcceptanceCriterion[] // ≥ 1 |
| 64 | + feasibility: FeasibilityGateInput // the discriminants §3 scores — { claimStatus, duplicateClusterRisk, issueStatus, found } |
| 65 | +} |
| 66 | +
|
| 67 | +AcceptanceCriterion { |
| 68 | + id: string |
| 69 | + statement: string // testable, behavior-level ("uploads retry on 5xx"), not implementation-level |
| 70 | + kind: "behavior" | "artifact" | "constraint" |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +Translation rules: |
| 75 | +1. **Decompose by independently-shippable outcome.** Each `ConstituentIssue` is a unit that can be claimed, |
| 76 | + executed, and merged on its own. A multi-step idea becomes several issues linked by `dependsOn`; a simple idea |
| 77 | + becomes exactly one. |
| 78 | +2. **Every issue carries testable acceptance criteria.** Criteria are behavior-level and implementation-agnostic — |
| 79 | + they describe *what* is true when done, never *how*. Renter `acceptanceHints` and `constraints` fold in as |
| 80 | + `artifact`/`constraint` criteria. |
| 81 | +3. **Type labels are inferred, priority is never.** An issue gets `gittensor:bug` or `gittensor:feature` from its |
| 82 | + outcome; `gittensor:priority` is never emitted by the bridge (it is maintainer-propagated only). |
| 83 | +4. **Each issue is pre-scored for feasibility** by populating `feasibility` (§3), so the loop can gate before |
| 84 | + claiming rather than after wasting an attempt. |
| 85 | +5. **Ordering respects `dependsOn`.** An issue whose dependency has not landed is held (`raise`), never claimed |
| 86 | + ahead of its prerequisite. |
| 87 | + |
| 88 | +## 3. Scoring rubric |
| 89 | + |
| 90 | +The rubric is the existing feasibility gate applied per constituent issue. The freeform-scoring work in #5671 |
| 91 | +maps idea/issue text onto the three discriminants; this spec fixes what those discriminants mean for an idea so |
| 92 | +the mapping is stable: |
| 93 | + |
| 94 | +| Discriminant | Idea-bridge meaning | Verdict effect (per `feasibility.ts`) | |
| 95 | +|---|---|---| |
| 96 | +| `issueStatus = ready` | criteria are testable, scope is a single shippable outcome, no blocking prerequisite open | eligible for `go` | |
| 97 | +| `issueStatus = needs_proof` / `hold` | outcome under-specified, or a `dependsOn` prerequisite not yet landed | `raise` (`issue_quality_uncertain`) | |
| 98 | +| `issueStatus = invalid` / `do_not_use` | not implementable as stated, or violates a hard constraint/guarded surface | `avoid` | |
| 99 | +| `duplicateClusterRisk = high` | outcome duplicates an existing open issue/PR cluster | `avoid` (`duplicate_cluster_high`) | |
| 100 | +| `duplicateClusterRisk = medium` | overlaps an existing effort but not identical | `raise` | |
| 101 | +| `claimStatus = claimed` | an equivalent issue is already claimed by another loop | `raise` (`claim_status_claimed`) | |
| 102 | +| `claimStatus = solved` | the outcome already exists on the default branch | `avoid` (`claim_status_solved`) | |
| 103 | +| `found = false` | the bridge could not resolve a concrete target for this issue | `raise` (`target_not_found`) | |
| 104 | + |
| 105 | +The graph-level disposition is the **least-favorable** verdict across its issues (`avoid` if any issue avoids, |
| 106 | +else `raise` if any raises, else `go`) — a renter should not be told "go" while any constituent is unshippable. |
| 107 | +Precedence (`avoid > raise > go`) is inherited unchanged from `buildFeasibilityVerdict`, so the bridge adds no |
| 108 | +second decision surface. |
| 109 | + |
| 110 | +## 4. Worked examples |
| 111 | + |
| 112 | +### Example A — simple idea (single issue) |
| 113 | + |
| 114 | +**Idea:** `{ title: "Retry flaky uploads", body: "Our upload client gives up on the first 5xx; it should retry a |
| 115 | +few times before failing.", targetRepo: "acme/widgets", constraints: ["no new dependencies"] }` |
| 116 | + |
| 117 | +**Task-graph:** |
| 118 | +- `issue-1` — title *"Uploads should retry on 5xx"*, labels `[gittensor:bug]`, `dependsOn: []` |
| 119 | + - AC1 (behavior): a 5xx upload response triggers a bounded retry before surfacing an error |
| 120 | + - AC2 (behavior): a non-5xx (e.g. 4xx) failure is **not** retried |
| 121 | + - AC3 (constraint): no new runtime dependency is added |
| 122 | + - `feasibility`: `{ claimStatus: "unclaimed", duplicateClusterRisk: "none", issueStatus: "ready", found: true }` |
| 123 | +- **rubric →** `buildFeasibilityVerdict(issue-1.feasibility)` = **`go`**. Graph verdict = `go`. One claimable issue |
| 124 | + drops straight into the loop. |
| 125 | + |
| 126 | +### Example B — multi-step idea (dependency chain) |
| 127 | + |
| 128 | +**Idea:** `{ title: "Add API key auth to the public endpoints", body: "Let callers authenticate the read API with |
| 129 | +an API key instead of leaving it open.", targetRepo: "acme/widgets", acceptanceHints: ["existing callers keep |
| 130 | +working during rollout"] }` |
| 131 | + |
| 132 | +**Task-graph (ordered by `dependsOn`):** |
| 133 | +- `issue-1` — *"Introduce API-key store + validation helper"*, labels `[gittensor:feature]`, `dependsOn: []` |
| 134 | + - AC1 (behavior): a valid key validates; an unknown/expired key is rejected |
| 135 | + - AC2 (artifact): keys are stored hashed, never in plaintext |
| 136 | + - `feasibility`: `{ claimStatus: "unclaimed", duplicateClusterRisk: "none", issueStatus: "ready", found: true }` → `go` |
| 137 | +- `issue-2` — *"Gate the read endpoints behind key validation"*, labels `[gittensor:feature]`, `dependsOn: ["issue-1"]` |
| 138 | + - AC1 (behavior): a request with a valid key succeeds; without one is rejected |
| 139 | + - AC2 (constraint, from `acceptanceHints`): a documented grace/rollout path keeps existing callers working |
| 140 | + - `feasibility`: `{ claimStatus: "unclaimed", duplicateClusterRisk: "none", issueStatus: "hold", found: true }` |
| 141 | + — `hold` because `issue-1` has not landed → **`raise`** (`issue_quality_uncertain`) |
| 142 | +- **rubric →** graph verdict = least-favorable = **`raise`**: `issue-1` is claimable now (`go`), `issue-2` is |
| 143 | + correctly held until its prerequisite lands, then re-scores to `go`. |
| 144 | + |
| 145 | +## Acceptance-criteria checklist (per #4779) |
| 146 | + |
| 147 | +- [x] Input schema for an idea submission — §1. |
| 148 | +- [x] Translation rules idea → structured, claimable task-graph (constituent issues, per-issue acceptance |
| 149 | + criteria, scoring rubric) — §2, §3. |
| 150 | +- [x] At least two fully worked examples (one simple, one multi-step) traced end-to-end — §4. |
| 151 | +- [x] Concrete enough for the freeform feasibility scoring (#5671) to implement against — the rubric maps idea |
| 152 | + text onto the exact `FeasibilityGateInput` discriminants `buildFeasibilityVerdict` already consumes. |
0 commit comments