Linear ships as a manifest now (#1281,
crates/stella-cli/src/issue_provider/linear.toml). Stella can read Linear's
words. It still cannot talk to Linear.
The problem
This build has one issue adapter and it runs gh
(crates/stella-cli/src/issue_provider.rs, GhIssueProvider). So a workspace
that sets [issues] provider = "linear" in stella.toml gets Linear's
vocabulary and the GitHub adapter. The loader prints a line saying so, which is
honest but is not a working Linear setup.
Half of doc:agent-native-delivery §4.1 is therefore not defined anywhere,
because nothing would read it: [connection], [states.write],
[capabilities], [branch] and [fields.write]. Writing that schema without
the transport would be scaffolding, which is why #1281 left it out.
What to build
- A Linear transport behind the
IssueProvider trait
(crates/stella-protocol/src/issue.rs), sitting beside GhIssueProvider
rather than branching inside it. The manifest's kind picks which one runs;
that choice is one table lookup today.
[connection]: api_url, api_key_env, team_key. The manifest holds the
name of an env var, never a secret. A stella connect linear OAuth token
stays in ~/.stella/integrations.json and keeps winning over the env var.
[states.write]: which state Stella sets when it drives a transition.
[capabilities]: sub_issues, blocked_state, close_reason, comments,
search, declared rather than probed (§4.2).
[branch] source = "provider": ask Linear for its canonical branch name,
fall back to a derived one. Nothing in the tree derives a branch name from an
issue yet, so that consumer has to be built too.
- Load-time validation (§4.3): an enabled provider whose
api_key_env is
unset, whose [classes] names a label that does not exist, or whose
[states] leaves a reachable state type unmapped is a startup error naming
the file and the key. ProviderManifest::resolve is infallible today and
every caller treats it that way, so this changes its signature.
- The trust boundary (§4.3), and it is why
[connection] cannot land
alone. A provider manifest with a real transport behind it is an egress
destination: a project-scope .stella/issues/*.toml could send diffs, file
paths and error text to a host the user never approved. It sits on the same
boundary as context_providers and [integrations], so an untrusted project
scope must contribute no provider — project_code_execution_trusted()
(crates/stella-cli/src/plugin_cmd.rs) is the gate that already does this
for project_custom_tools. This does not bite today because the only
transport is gh, which reaches the repository the user is already in and
holds no token Stella can leak. It bites the moment a manifest can name an
arbitrary api_url. Invariant 3 makes a leak here a privacy incident rather
than a bug, so land the trust gate in the same change as [connection],
never after it.
Blocked on a kernel change first
IssueState (crates/stella-protocol/src/issue.rs) is Open / InProgress /
Closed. It has no Blocked. So [states] blocked and
[states.blocked_from] — the label-or-relation degradation the spec describes
— cannot be represented at all until that variant exists and something branches
on it. Decide that before writing the manifest half.
Why this could not ride #1281
It needs a credential and a rig. A LINEAR_API_KEY and a real Linear
workspace are required to write the GraphQL client, and required again to
witness it — a wiremock test can pin the shapes but cannot establish that they
are Linear's actual shapes. AGENTS.md § "Fix over file" names this case.
Files
crates/stella-cli/src/issue_provider.rs — the trait impl and the gh
adapter to sit beside
crates/stella-cli/src/issue_provider/manifest.rs — BUILT_IN, resolve,
RUNNING_TRANSPORT, and announce_transport, which is the line that would
stop being printed
crates/stella-cli/src/issue_provider/linear.toml — the shipped manifest to
extend
docs/spec/agent-native-delivery/provider.linear.toml — the full target,
every block rendered
docs/spec/agent-native-delivery.md §4.1, §4.2, §4.3
Which pillar
Innovation, and vendor lock. A customer on Linear cannot use the self-driving
loop at all today. It is also the second half of the proof #1281 started: the
format carries Linear's vocabulary, and this is whether it carries Linear's
behaviour.
Done looks like
stella.toml with [issues] provider = "linear" reads, ranks and closes real
Linear issues; announce_transport prints nothing for a Linear binding; and
a manifest with an unset api_key_env fails at startup naming the file and
the key, with a test for each.
Definition of done (triage addendum)
Parent epic: #1280.
Linear ships as a manifest now (#1281,
crates/stella-cli/src/issue_provider/linear.toml). Stella can read Linear'swords. It still cannot talk to Linear.
The problem
This build has one issue adapter and it runs
gh(
crates/stella-cli/src/issue_provider.rs,GhIssueProvider). So a workspacethat sets
[issues] provider = "linear"instella.tomlgets Linear'svocabulary and the GitHub adapter. The loader prints a line saying so, which is
honest but is not a working Linear setup.
Half of
doc:agent-native-delivery§4.1 is therefore not defined anywhere,because nothing would read it:
[connection],[states.write],[capabilities],[branch]and[fields.write]. Writing that schema withoutthe transport would be scaffolding, which is why #1281 left it out.
What to build
IssueProvidertrait(
crates/stella-protocol/src/issue.rs), sitting besideGhIssueProviderrather than branching inside it. The manifest's
kindpicks which one runs;that choice is one table lookup today.
[connection]:api_url,api_key_env,team_key. The manifest holds thename of an env var, never a secret. A
stella connect linearOAuth tokenstays in
~/.stella/integrations.jsonand keeps winning over the env var.[states.write]: which state Stella sets when it drives a transition.[capabilities]:sub_issues,blocked_state,close_reason,comments,search, declared rather than probed (§4.2).[branch] source = "provider": ask Linear for its canonical branch name,fall back to a derived one. Nothing in the tree derives a branch name from an
issue yet, so that consumer has to be built too.
api_key_envisunset, whose
[classes]names a label that does not exist, or whose[states]leaves a reachable state type unmapped is a startup error namingthe file and the key.
ProviderManifest::resolveis infallible today andevery caller treats it that way, so this changes its signature.
[connection]cannot landalone. A provider manifest with a real transport behind it is an egress
destination: a project-scope
.stella/issues/*.tomlcould send diffs, filepaths and error text to a host the user never approved. It sits on the same
boundary as
context_providersand[integrations], so an untrusted projectscope must contribute no provider —
project_code_execution_trusted()(
crates/stella-cli/src/plugin_cmd.rs) is the gate that already does thisfor
project_custom_tools. This does not bite today because the onlytransport is
gh, which reaches the repository the user is already in andholds no token Stella can leak. It bites the moment a manifest can name an
arbitrary
api_url. Invariant 3 makes a leak here a privacy incident ratherthan a bug, so land the trust gate in the same change as
[connection],never after it.
Blocked on a kernel change first
IssueState(crates/stella-protocol/src/issue.rs) isOpen/InProgress/Closed. It has noBlocked. So[states] blockedand[states.blocked_from]— the label-or-relation degradation the spec describes— cannot be represented at all until that variant exists and something branches
on it. Decide that before writing the manifest half.
Why this could not ride #1281
It needs a credential and a rig. A
LINEAR_API_KEYand a real Linearworkspace are required to write the GraphQL client, and required again to
witness it — a wiremock test can pin the shapes but cannot establish that they
are Linear's actual shapes. AGENTS.md § "Fix over file" names this case.
Files
crates/stella-cli/src/issue_provider.rs— the trait impl and theghadapter to sit beside
crates/stella-cli/src/issue_provider/manifest.rs—BUILT_IN,resolve,RUNNING_TRANSPORT, andannounce_transport, which is the line that wouldstop being printed
crates/stella-cli/src/issue_provider/linear.toml— the shipped manifest toextend
docs/spec/agent-native-delivery/provider.linear.toml— the full target,every block rendered
docs/spec/agent-native-delivery.md§4.1, §4.2, §4.3Which pillar
Innovation, and vendor lock. A customer on Linear cannot use the self-driving
loop at all today. It is also the second half of the proof #1281 started: the
format carries Linear's vocabulary, and this is whether it carries Linear's
behaviour.
Done looks like
stella.tomlwith[issues] provider = "linear"reads, ranks and closes realLinear issues;
announce_transportprints nothing for a Linear binding; anda manifest with an unset
api_key_envfails at startup naming the file andthe key, with a test for each.
Definition of done (triage addendum)
IssueState::Blockedkernel change lands instella-protocolfirst, as its own reviewable step, per this issue's own "Blocked on a kernel change first" section.stella.tomlwithprovider = "linear"reading/ranking/closing a real Linear issue, (b)announce_transportstaying silent for a bound Linear provider, and (c) an unsetapi_key_envfailing at startup naming the file and key.project_code_execution_trusted()) lands in the same change as[connection], never after — per this issue's own explicit ordering requirement.crates/stella-cli/src/issue_provider.rsand itsissue_provider/submodule besideGhIssueProvider— no new crate, and no growth to a grandfathered god file (command_deck.rsetc.).docs/spec/agent-native-delivery.md§4.1–4.3 anddocs/spec/agent-native-delivery/provider.linear.tomlupdated to match what actually ships;make proseandmake line-citationsstay green on the touched docs.make gate(or the SCR-001-narrowedstella-cli/stella-protocolslice while iterating) passes, includingdoc-warningsand clippy on the new module.Parent epic: #1280.