@codyswann/lisa 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +50 -23
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/bdd/baseline.mjs +211 -121
- package/expo/copy-overwrite/scripts/bdd/contract.mjs +10 -2
- package/expo/copy-overwrite/scripts/bdd/envelope.mjs +3 -2
- package/expo/copy-overwrite/scripts/bdd/render.mjs +2 -2
- package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +45 -8
- package/expo/copy-overwrite/scripts/classify-maestro-failures.mjs +775 -0
- package/expo/create-only/.github/workflows/nightly-e2e-report.yml +71 -0
- package/expo/create-only/.maestro/flake-classification.json +21 -0
- package/expo/create-only/bdd/coverage-map.json +1 -2
- package/expo/package-lisa/package.lisa.json +1 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +75 -64
- package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +13 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +30 -10
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +36 -23
- package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +16 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +16 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/lisa/rules/eager/integration-access-layer.md +7 -3
- package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +19 -8
- package/plugins/lisa/rules/reference/credential-substrate-precedence.md +166 -0
- package/plugins/lisa/rules/reference/integration-access-layer.md +27 -15
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +76 -65
- package/plugins/lisa/skills/lisa-jam-access/SKILL.md +14 -6
- package/plugins/lisa/skills/lisa-linear-access/SKILL.md +31 -11
- package/plugins/lisa/skills/lisa-notion-access/SKILL.md +37 -24
- package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +17 -7
- package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +17 -7
- package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +76 -65
- package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +14 -6
- package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +31 -11
- package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +37 -24
- package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +17 -7
- package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +17 -7
- package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/lisa-copilot/rules/eager/integration-access-layer.md +7 -3
- package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +19 -8
- package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +166 -0
- package/plugins/lisa-copilot/rules/reference/integration-access-layer.md +27 -15
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +76 -65
- package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +14 -6
- package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +31 -11
- package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +37 -24
- package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +17 -7
- package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +17 -7
- package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +19 -8
- package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +171 -0
- package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +57 -0
- package/plugins/lisa-cursor/rules/integration-access-layer-reference.mdc +27 -15
- package/plugins/lisa-cursor/rules/integration-access-layer.mdc +7 -3
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +76 -65
- package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +14 -6
- package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +31 -11
- package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +37 -24
- package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +17 -7
- package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +17 -7
- package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/src/base/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/src/base/rules/eager/integration-access-layer.md +7 -3
- package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +19 -8
- package/plugins/src/base/rules/reference/credential-substrate-precedence.md +166 -0
- package/plugins/src/base/rules/reference/integration-access-layer.md +27 -15
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +76 -65
- package/plugins/src/base/skills/lisa-jam-access/SKILL.md +14 -6
- package/plugins/src/base/skills/lisa-linear-access/SKILL.md +31 -11
- package/plugins/src/base/skills/lisa-notion-access/SKILL.md +37 -24
- package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +17 -7
- package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +8 -0
- package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +17 -7
- package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +7 -1
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
- package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +631 -5
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
|
@@ -10,6 +10,30 @@
|
|
|
10
10
|
* File families the ratchet watches. `kind` selects the extractor;
|
|
11
11
|
* `direction` applies to numeric-leaf kinds ("min" values may only rise,
|
|
12
12
|
* "max" values may only fall).
|
|
13
|
+
*
|
|
14
|
+
* PER-FAMILY POLICY, reviewed 2026-08-12. A family keeps its ratchet unless a
|
|
15
|
+
* NAMED deterministic non-regression invariant replaces the property the
|
|
16
|
+
* ratchet was providing — deleting a ratchet deletes that property with it, so
|
|
17
|
+
* "this generates churn" is a reason to find a replacement, never a reason to
|
|
18
|
+
* delete on its own:
|
|
19
|
+
*
|
|
20
|
+
* coverage / simplecov / e2e KEPT. Here the ratchet IS the non-regression
|
|
21
|
+
* invariant, and no per-item equivalent exists short of committing a
|
|
22
|
+
* per-file coverage baseline — an artifact that only ever accumulates.
|
|
23
|
+
* Revisit if one appears.
|
|
24
|
+
* stryker KEPT. `thresholds.break` is an absolute floor,
|
|
25
|
+
* kept outright; the mutate-list comparison detects EXEMPTION ADDITIONS
|
|
26
|
+
* rather than creep, and has no number for a pull request to nudge.
|
|
27
|
+
* eslint / rubocop / k6 / lisa-config KEPT, unchanged.
|
|
28
|
+
*
|
|
29
|
+
* Exactly one family was replaced, and it is not watched here and never was:
|
|
30
|
+
* the BDD traceability floor, whose numeric ratchet gave way to per-obligation
|
|
31
|
+
* checks. See `expo/copy-overwrite/scripts/bdd/baseline.mjs`.
|
|
32
|
+
*
|
|
33
|
+
* Retiring a family is SEQUENCED: the replacement invariant lands, then a
|
|
34
|
+
* `thresholdRatchet.allow` entry merges from the base side, then the mechanism
|
|
35
|
+
* changes. Never the reverse — this checker is precisely what stops a change
|
|
36
|
+
* granting itself the exception that permits it.
|
|
13
37
|
*/
|
|
14
38
|
export const FAMILIES = [
|
|
15
39
|
{
|
|
@@ -53,7 +53,7 @@ platforms it requires and that each named platform has a configured runner.
|
|
|
53
53
|
"schemaVersion": 1,
|
|
54
54
|
"asOf": "<ISO date>",
|
|
55
55
|
"runnerPlatforms": { "<runner>": ["<platform>", "..."] },
|
|
56
|
-
"coverageFloor": { "<platform>": "<0-100,
|
|
56
|
+
"coverageFloor": { "<platform>": "<0-100, an absolute bar checked in enforced mode — not a ratchet, set once and left alone>" },
|
|
57
57
|
"platformWaivers": [
|
|
58
58
|
{
|
|
59
59
|
"scenario": "BDD-DOMAIN-NNN",
|
|
@@ -128,7 +128,9 @@ Two commands, wired into the project's script surface and into CI:
|
|
|
128
128
|
existing mapping);
|
|
129
129
|
- a discovered test named by no mapping and no exclusion, or an exclusion that no longer excuses
|
|
130
130
|
anything;
|
|
131
|
-
- a
|
|
131
|
+
- a platform sitting below its committed `coverageFloor`;
|
|
132
|
+
- coverage given back: an obligation mapped at the base revision that nothing maps here;
|
|
133
|
+
- new behavior nobody mapped or waived.
|
|
132
134
|
|
|
133
135
|
Regeneration is never blocked by the check: `--write` rewrites the report and burndown whenever a
|
|
134
136
|
report can be built at all, so a stale evidence string can never hold hostage the paperwork that
|
|
@@ -139,12 +141,20 @@ that currently fails is a red CI check, a separate signal; the map only asserts
|
|
|
139
141
|
exists and still says what it claimed. Both facts are required — a green gate over a red suite is
|
|
140
142
|
not coverage.
|
|
141
143
|
|
|
142
|
-
### Coverage floor, not coverage target
|
|
144
|
+
### Coverage floor, not coverage target — and not a ratchet
|
|
143
145
|
|
|
144
|
-
Projects adopting this contract mid-life start below 100% and must not be blocked by that. The
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
Projects adopting this contract mid-life start below 100% and must not be blocked by that. The
|
|
147
|
+
committed `coverageFloor` per platform is an **absolute bar** answering "is this platform below it
|
|
148
|
+
right now". Set it once at adoption to the honest measured number (or `0`) and leave it: nothing
|
|
149
|
+
forces it upward, and lowering it needs no ceremony.
|
|
150
|
+
|
|
151
|
+
What stops coverage sliding backwards is checked directly, per obligation, against the base
|
|
152
|
+
revision — **an obligation that was mapped may not stop being mapped, and new behavior arrives
|
|
153
|
+
mapped or waived**. Giving coverage back is legitimate but takes two artifacts one author cannot
|
|
154
|
+
produce alone: a recorded route (a `retirements` record or a `platformWaivers` entry) plus the
|
|
155
|
+
maintainer-applied `bdd-floor-baseline` label. Gaps that predate the change are burndown, never a
|
|
156
|
+
gate failure — which is what lets a brownfield project adopt `enforced` without first backfilling
|
|
157
|
+
its whole history.
|
|
148
158
|
|
|
149
159
|
## Waivers versus `@blocked`
|
|
150
160
|
|
|
@@ -203,7 +213,8 @@ A repo with no contract yet, taking its first frontend work item:
|
|
|
203
213
|
during this step, never left undisclosed.
|
|
204
214
|
3. **Write only this item's scenarios.** The first item is not a backfill project. Pre-existing
|
|
205
215
|
uncovered behavior becomes burndown in `docs/e2e-bdd-coverage.md`, and the floor starts where the
|
|
206
|
-
repo actually is.
|
|
216
|
+
repo actually is. That is a one-time act, not a recurring one: from here on the floor stays put
|
|
217
|
+
and what protects each new obligation is the per-obligation check, not the number.
|
|
207
218
|
4. **Seal this item's obligations** and commit the regenerated matrix and burndown with the change.
|
|
208
219
|
|
|
209
220
|
If a required platform has **no** e2e runner at all, that obligation is never left as a bare `N/A` —
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Credential-Substrate Precedence"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Credential-Substrate Precedence
|
|
7
|
+
|
|
8
|
+
**When more than one substrate can reach an external system, the configured credentials
|
|
9
|
+
provider's token/CLI path goes first and the interactive MCP is the fallback — and
|
|
10
|
+
identity-match verification is mandatory on every substrate, at every tier.**
|
|
11
|
+
|
|
12
|
+
This is **one shared, vendor-neutral contract cited by every `*-access` skill** (the
|
|
13
|
+
`leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug, never divergent
|
|
14
|
+
per-skill prose). An access skill states its per-vendor mechanics — which token, which
|
|
15
|
+
CLI, which identity anchor — and cites this rule for the ordering. It never restates,
|
|
16
|
+
narrows, or locally overrides the ordering.
|
|
17
|
+
|
|
18
|
+
Settled by the decision record `2026-08-12-credential-substrate-precedence` (D6). It is
|
|
19
|
+
a **settled decision** in the `settled-decisions` sense: re-arguing MCP-first inside a
|
|
20
|
+
skill is out of scope for that skill's work.
|
|
21
|
+
|
|
22
|
+
## The ladder
|
|
23
|
+
|
|
24
|
+
### Tier 1 — configured-provider substrate
|
|
25
|
+
|
|
26
|
+
The token or CLI path fed by `lisa-secrets-access`. Chosen **whenever its bootstrap
|
|
27
|
+
credential is available AND the resolved substrate identity-matches the configured
|
|
28
|
+
tenant/workspace/site.**
|
|
29
|
+
|
|
30
|
+
`lisa-secrets-access` is the single chokepoint that makes this tier actionable rather
|
|
31
|
+
than aspirational: it owns the one-store rule and the surface ladder, and its `tool:`
|
|
32
|
+
note line already declares which CLI a given credential is expected to drive. An access
|
|
33
|
+
skill resolves its credential through that chokepoint — never by reading an OS keychain
|
|
34
|
+
a second time.
|
|
35
|
+
|
|
36
|
+
Both halves of the gate are load-bearing. A present credential that identity-matches
|
|
37
|
+
nothing is **not** tier 1; it is a failed tier, and the ladder moves on.
|
|
38
|
+
|
|
39
|
+
### Tier 2 — interactive MCP
|
|
40
|
+
|
|
41
|
+
Used when the tier 1 path is **genuinely unavailable**. The three genuine cases:
|
|
42
|
+
|
|
43
|
+
- **No bootstrap** — the provider credential is absent or the project has not adopted a
|
|
44
|
+
credentials provider at all.
|
|
45
|
+
- **No adapter for the operation** — the token/CLI substrate has no documented adapter
|
|
46
|
+
for the requested operation, and the MCP does (per-operation, not per-session: a skill
|
|
47
|
+
may resolve tier 1 for one operation and tier 2 for the next).
|
|
48
|
+
- **Provider outage** — the provider path is present but failing for reasons the caller
|
|
49
|
+
cannot fix in-session.
|
|
50
|
+
|
|
51
|
+
"The MCP happens to be authenticated" is not one of them. Neither is "tier 1 is slower."
|
|
52
|
+
|
|
53
|
+
### Tier 3 — loud, actionable failure
|
|
54
|
+
|
|
55
|
+
When no substrate is both available and identity-matched, fail with a message naming the
|
|
56
|
+
exact credential to set and the exact remediation path. Never silently no-op, never
|
|
57
|
+
blind-retry a failed or absent substrate, and never fall through to a substrate that
|
|
58
|
+
failed identity-match.
|
|
59
|
+
|
|
60
|
+
## Identity-match is mandatory on every substrate
|
|
61
|
+
|
|
62
|
+
Verification runs **in both directions** before any operation: the substrate must claim
|
|
63
|
+
the configured tenant, and the configured tenant must be one the substrate can actually
|
|
64
|
+
reach. A substrate authenticated as a **different** account is **skipped, never used**,
|
|
65
|
+
regardless of tier — including tier 1. A credential is not an identity claim; the
|
|
66
|
+
identity claim is what the provider says when asked.
|
|
67
|
+
|
|
68
|
+
| Vendor | Identity anchor | Probe |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Atlassian | `atlassian.cloudId` / `atlassian.site` | `/rest/api/3/myself` email, acli `auth status` site, MCP accessible-resources contains the cloudId |
|
|
71
|
+
| Notion | `notion.workspaceId` (+ `prdDatabaseId` reachability) | `GET /v1/users/me` → `bot.workspace_name`/`workspace_id` |
|
|
72
|
+
| Linear | `linear.workspace` / `linear.teamKey` | `viewer`/`organization` on GraphQL; team list through the MCP |
|
|
73
|
+
| Sentry / PostHog / Jam / Sonar | configured org + project | the substrate's own whoami/org listing |
|
|
74
|
+
|
|
75
|
+
Skipping the check because "the user obviously meant this workspace" is forbidden. Silent
|
|
76
|
+
cross-tenant operations are precisely the hazard this contract exists to prevent.
|
|
77
|
+
|
|
78
|
+
## Why provider-first (and why it overturns a working default)
|
|
79
|
+
|
|
80
|
+
MCP-first was defensible and is being overturned deliberately, not corrected as an
|
|
81
|
+
oversight. Three reasons outweigh it.
|
|
82
|
+
|
|
83
|
+
**Headless parity.** Cron runs, cloud sessions, CI, and subagent sessions have no
|
|
84
|
+
browser. Under MCP-first an interactive session and a headless session resolve through
|
|
85
|
+
*different* substrates and can therefore fail differently — and the failure surfaces only
|
|
86
|
+
in the environment nobody is watching. Provider-first makes the primary path the same one
|
|
87
|
+
everywhere, with MCP as the enhancement rather than the default.
|
|
88
|
+
|
|
89
|
+
**Tenant safety — the generalized Atlassian write rule.** Substrates differ in *where
|
|
90
|
+
their target comes from*:
|
|
91
|
+
|
|
92
|
+
- **Per-invocation-bound.** The target is part of the call. A cloudId-scoped REST URL
|
|
93
|
+
(`https://api.atlassian.com/ex/jira/<CLOUDID>/…`) or a workspace-scoped API token
|
|
94
|
+
carries its tenant in the request itself, so nothing outside the call can redirect it.
|
|
95
|
+
- **Ambient-bound.** The target comes from machine-global or session-global state a skill
|
|
96
|
+
does not own: acli's single active account, an MCP's browser OAuth session bound to
|
|
97
|
+
whatever account the human last used. Any other process — or the human — can change it
|
|
98
|
+
between the check and the call. That is a **TOCTOU** window, and a successful
|
|
99
|
+
pre-flight `auth status` does not close it.
|
|
100
|
+
|
|
101
|
+
Prefer the per-invocation-bound substrate. This is exactly why Atlassian JIRA *writes*
|
|
102
|
+
were already forced onto the cloudId-scoped curl adapter; the hazard is not specific to
|
|
103
|
+
Atlassian and not specific to writes. A misrouted write is loud and often reversible; a
|
|
104
|
+
**read through the wrong tenant silently returns wrong data**, which then propagates into
|
|
105
|
+
tickets, PRDs, and verification claims — harder to detect and harder to unwind. Reads get
|
|
106
|
+
the same ordering as writes.
|
|
107
|
+
|
|
108
|
+
**Determinism.** A token path either has its bootstrap or does not, and says so. An MCP's
|
|
109
|
+
readiness depends on session state a skill cannot inspect reliably — the same server
|
|
110
|
+
registers under different prefixes depending on install path, and its data tools register
|
|
111
|
+
only after OAuth completes.
|
|
112
|
+
|
|
113
|
+
## MCP stays a first-class fallback
|
|
114
|
+
|
|
115
|
+
This is a **re-ordering, not a removal.** The strongest argument for MCP-first — that an
|
|
116
|
+
already-authenticated MCP is zero-setup and identity-verified — is preserved by keeping
|
|
117
|
+
MCP as a genuine, fully supported tier rather than deleting it:
|
|
118
|
+
|
|
119
|
+
- Every access skill keeps its MCP adapters in the dispatch table.
|
|
120
|
+
- An operation with no tier 1 adapter routes to MCP **as the normal path**, not as an
|
|
121
|
+
error.
|
|
122
|
+
- A project with no credentials provider is fully functional on MCP alone.
|
|
123
|
+
- MCP failure messages stay actionable (how to enable and authenticate the plugin).
|
|
124
|
+
|
|
125
|
+
Removing an MCP adapter is a separate decision requiring its own justification. Do not
|
|
126
|
+
treat this contract as license to delete one.
|
|
127
|
+
|
|
128
|
+
## Guarded fallback for ambient-bound substrates
|
|
129
|
+
|
|
130
|
+
When the ladder does fall back to an ambient-bound substrate for a **mutating**
|
|
131
|
+
operation, the fallback is guarded — never the normal path:
|
|
132
|
+
|
|
133
|
+
1. Switch to the configured profile and assert the active identity matches config
|
|
134
|
+
immediately before the write.
|
|
135
|
+
2. Execute the write.
|
|
136
|
+
3. Re-read the affected object(s) immediately afterward.
|
|
137
|
+
4. Perform a **post-write tenant assertion** on the response — the tenant is proven from
|
|
138
|
+
the response (self URL host, cloudId in the path, or response metadata), not assumed
|
|
139
|
+
from the pre-flight check.
|
|
140
|
+
5. On mismatch: stop, report a cross-tenant hazard, and best-effort **roll back** the
|
|
141
|
+
write when a safe reversal exists (delete the created object, remove the created
|
|
142
|
+
comment/link, revert a reversible field edit). Never continue as if it succeeded.
|
|
143
|
+
|
|
144
|
+
A successful pre-flight switch is not sufficient for tenant safety — another process can
|
|
145
|
+
mutate global state between the check and the write.
|
|
146
|
+
|
|
147
|
+
## Consequences to expect
|
|
148
|
+
|
|
149
|
+
- **A stale or wrong token now fails identity-match instead of silently succeeding
|
|
150
|
+
through an authenticated MCP.** That is intended: it is the exact class of bug this
|
|
151
|
+
contract exists to surface. Fix the credential (`/lisa:setup:<vendor>`); do not
|
|
152
|
+
re-order the ladder to route around it.
|
|
153
|
+
- Headless and interactive sessions take the same primary path, so a credential problem
|
|
154
|
+
reproduces on a laptop instead of only at 3am in cron.
|
|
155
|
+
- The ordering is **not configurable per project**. A knob would let a project
|
|
156
|
+
reintroduce the headless divergence this contract removes. Revisit only with a concrete
|
|
157
|
+
need and a new decision record.
|
|
158
|
+
|
|
159
|
+
## Adding or editing an access skill
|
|
160
|
+
|
|
161
|
+
1. Cite this rule by name; do not restate the ordering.
|
|
162
|
+
2. Document the tier 1 credential and the `lisa-secrets-access` resolution path.
|
|
163
|
+
3. Document the identity anchor and its probe, and state that mismatch means skip.
|
|
164
|
+
4. Keep MCP adapters and name the operations for which MCP is the only substrate.
|
|
165
|
+
5. Make the terminal failure name the exact credential and remediation command.
|
|
166
|
+
|
|
167
|
+
Single-substrate access skills are conformant when their one substrate is
|
|
168
|
+
provider-credential-authenticated (`lisa-sonarcloud-access`: the official SonarQube MCP
|
|
169
|
+
authenticates headlessly from `SONARQUBE_CLI_TOKEN`, so it *is* the tier 1 substrate and
|
|
170
|
+
needs no separate REST tier). Reserve a multi-tier ladder for vendors whose MCP is
|
|
171
|
+
browser-OAuth or keychain-bound and therefore dead headless.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Credential-Substrate Precedence (load-bearing)"
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Credential-Substrate Precedence (load-bearing)
|
|
7
|
+
|
|
8
|
+
**When more than one substrate can reach an external system, the configured credentials
|
|
9
|
+
provider's token/CLI path goes first and the interactive MCP is the fallback — and
|
|
10
|
+
identity-match verification is mandatory on every substrate, at every tier.**
|
|
11
|
+
|
|
12
|
+
**One shared, vendor-neutral contract cited by every `*-access` skill** (the
|
|
13
|
+
`leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug, never divergent
|
|
14
|
+
per-skill prose). An access skill states its per-vendor mechanics — which token, which
|
|
15
|
+
CLI, which identity anchor — and cites this rule for the ordering. It never restates,
|
|
16
|
+
narrows, or locally overrides that ordering.
|
|
17
|
+
|
|
18
|
+
Settled by decision record `2026-08-12-credential-substrate-precedence` (D6), and
|
|
19
|
+
settled in the `settled-decisions` sense: re-arguing MCP-first inside a skill is out of
|
|
20
|
+
scope for that skill's work.
|
|
21
|
+
|
|
22
|
+
## The ladder
|
|
23
|
+
|
|
24
|
+
1. **Tier 1 — configured-provider substrate.** The token or CLI path fed by
|
|
25
|
+
`lisa-secrets-access`, chosen whenever its bootstrap credential is available **and**
|
|
26
|
+
the resolved substrate identity-matches the configured tenant/workspace/site.
|
|
27
|
+
`lisa-secrets-access` is the single chokepoint — never read an OS keychain a second
|
|
28
|
+
time.
|
|
29
|
+
2. **Tier 2 — interactive MCP**, used only when tier 1 is *genuinely* unavailable:
|
|
30
|
+
no bootstrap, no adapter for the operation (per-operation, not per-session), or a
|
|
31
|
+
provider outage. "The MCP happens to be authenticated" and "tier 1 is slower" are
|
|
32
|
+
**not** qualifying reasons.
|
|
33
|
+
3. **Tier 3 — loud, actionable failure** naming the exact credential to set and the exact
|
|
34
|
+
remediation. Never silently no-op, never blind-retry a failed or absent substrate,
|
|
35
|
+
never fall through to one that failed identity-match.
|
|
36
|
+
|
|
37
|
+
## Identity-match is mandatory on every substrate
|
|
38
|
+
|
|
39
|
+
Verified **in both directions** before any operation: the substrate must claim the
|
|
40
|
+
configured tenant, and the configured tenant must be one the substrate can reach. A
|
|
41
|
+
substrate authenticated as a different account is **skipped, never used — including at
|
|
42
|
+
tier 1**. A credential is not an identity claim; the identity claim is what the provider
|
|
43
|
+
says when asked. Skipping the check because "the user obviously meant this workspace" is
|
|
44
|
+
forbidden.
|
|
45
|
+
|
|
46
|
+
## Mutating operations: fallback is guarded, never routine
|
|
47
|
+
|
|
48
|
+
Falling back to an ambient-bound substrate for a **write** requires: switch profile and
|
|
49
|
+
assert identity immediately before the write → write → re-read the affected objects →
|
|
50
|
+
assert the tenant **from the response** (self URL host, cloudId in the path, response
|
|
51
|
+
metadata), not from the pre-flight check → on mismatch, stop, report a cross-tenant
|
|
52
|
+
hazard, and best-effort roll back. A successful pre-flight switch is not sufficient:
|
|
53
|
+
another process can mutate global state between the check and the write.
|
|
54
|
+
|
|
55
|
+
Full contract (per-vendor identity anchors and probes, the provider-first rationale, MCP's
|
|
56
|
+
first-class fallback role, consequences, and the checklist for adding or editing an access
|
|
57
|
+
skill): [reference/credential-substrate-precedence.md](credential-substrate-precedence-reference.mdc).
|
|
@@ -9,24 +9,35 @@ Every Lisa skill or rule that consumes an external integration MUST route throug
|
|
|
9
9
|
the integration's `*-access` skill instead of calling that vendor's MCP tools or
|
|
10
10
|
REST API directly.
|
|
11
11
|
|
|
12
|
-
The access skill owns substrate resolution
|
|
12
|
+
The access skill owns substrate resolution. **The ordering is not this rule's to
|
|
13
|
+
define** — it is the single shared contract in `credential-substrate-precedence`,
|
|
14
|
+
cited identically by every `*-access` skill:
|
|
13
15
|
|
|
14
|
-
1.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
1. **Configured-provider token/CLI substrate** — the path fed by
|
|
17
|
+
`lisa-secrets-access` — when its bootstrap credential is present AND the
|
|
18
|
+
resolved substrate identity-matches the configured tenant/workspace/account.
|
|
19
|
+
2. **Interactive MCP**, as a first-class fallback, when the provider path is
|
|
20
|
+
genuinely unavailable (no bootstrap, no adapter for the operation, provider
|
|
21
|
+
outage).
|
|
22
|
+
3. Loud failure naming the exact credential to set.
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
Identity-match verification is mandatory on **every** substrate, in both
|
|
25
|
+
directions; one authenticated as a different tenant is skipped, never used. See the
|
|
26
|
+
`credential-substrate-precedence` rule for the rationale (headless parity, tenant
|
|
27
|
+
safety, determinism), the guarded-fallback protocol, and what "genuinely
|
|
28
|
+
unavailable" means.
|
|
29
|
+
|
|
30
|
+
Do not blind-retry a failed or absent substrate, and never silently no-op when no
|
|
31
|
+
tier is available.
|
|
22
32
|
|
|
23
33
|
Some MCPs authenticate headlessly from an env token and need no separate REST
|
|
24
|
-
tier —
|
|
25
|
-
routines
|
|
34
|
+
tier — such an MCP **is** the configured-provider substrate on both developer
|
|
35
|
+
machines and cloud routines, so a single-substrate access skill is conformant. The
|
|
36
|
+
official SonarQube MCP is one such case (`SONARQUBE_CLI_TOKEN`
|
|
26
37
|
[+ `SONARQUBE_CLI_ORG`/`SONARQUBE_CLI_SERVER`]): `lisa-sonarcloud-access` resolves it as a
|
|
27
|
-
single substrate with no hand-rolled REST fallback. Reserve the
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
single substrate with no hand-rolled REST fallback. Reserve the multi-tier ladder
|
|
39
|
+
for vendors whose MCP is browser-OAuth or keychain-bound and therefore dead
|
|
40
|
+
headless.
|
|
30
41
|
|
|
31
42
|
## Access Skills
|
|
32
43
|
|
|
@@ -65,7 +76,8 @@ When editing any skill listed in the matrix:
|
|
|
65
76
|
delegate to the matching access skill.
|
|
66
77
|
- Keep operation names coarse and vendor-native. Add new operation rows to the
|
|
67
78
|
access skill instead of embedding REST details in the consumer.
|
|
68
|
-
-
|
|
69
|
-
|
|
79
|
+
- Put the documented token/CLI substrate first and keep the MCP as an explicit,
|
|
80
|
+
fully supported fallback tier — `credential-substrate-precedence`. Preserving
|
|
81
|
+
the MCP adapters is required; re-ordering them is not the same as removing them.
|
|
70
82
|
- If a vendor has no documented token substrate, keep the MCP-only behavior and
|
|
71
83
|
fail with a clear "no documented headless substrate" message.
|
|
@@ -9,7 +9,11 @@ Skills and rules that use external integrations route through the matching
|
|
|
9
9
|
`*-access` skill. Do not call vendor MCP tools or REST APIs directly from a
|
|
10
10
|
consumer skill.
|
|
11
11
|
|
|
12
|
-
Resolution order is
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
Resolution order is the configured-provider token/CLI substrate first when its
|
|
13
|
+
bootstrap credential is present and identity-matched, then the interactive MCP as
|
|
14
|
+
fallback, then a loud error naming the exact credential to set. Identity-match is
|
|
15
|
+
mandatory on every substrate — one authenticated as a different tenant is skipped,
|
|
16
|
+
never used. The ordering itself is settled in
|
|
17
|
+
[reference/credential-substrate-precedence.md](credential-substrate-precedence-reference.mdc);
|
|
18
|
+
full matrix and migration rules:
|
|
15
19
|
[reference/integration-access-layer.md](integration-access-layer-reference.mdc).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lisa-atlassian-access
|
|
3
|
-
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly.
|
|
3
|
+
description: "Vendor-neutral access layer for Atlassian (JIRA + Confluence). Every jira-* and confluence-* skill MUST delegate through this skill rather than calling Atlassian directly. Per the credential-substrate-precedence contract, resolves a substrate per operation with the ATLASSIAN_API_TOKEN curl path first for reads and writes alike whenever the token is present and identity-matched — binding JIRA writes to the configured cloudId — then acli, then the Atlassian MCP as fallbacks. acli is used when installed and switchable to a profile matching the configured site; mismatched active profiles are skipped only after switch plus re-verification fails, and acli writes are a guarded fallback with post-write tenant assertions."
|
|
4
4
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,46 +35,17 @@ EMAIL=$(jq -r '.atlassian.email // empty' .lisa.config.local.json 2>/dev/null)
|
|
|
35
35
|
[ -z "$CLOUDID" ] && { echo "Error: atlassian.cloudId not set. Run /lisa:setup:atlassian." >&2; exit 1; }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are switched to the configured profile when one exists, then skipped only if the switch fails or re-verification still mismatches.
|
|
38
|
+
Probe each tier in order; the first that's ready AND identity-matches is the substrate for this operation. The ordering is the shared `credential-substrate-precedence` contract — the configured-provider token substrate leads for **reads and writes alike**, with acli and the MCP as identity-matched fallbacks — not an Atlassian-local choice. Identity-match is verified before any operation; substrates authenticated as a different Atlassian account are switched to the configured profile when one exists, then skipped only if the switch fails or re-verification still mismatches.
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
substrate=""
|
|
42
42
|
|
|
43
|
-
# Tier 1:
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
if [ "$OP_KIND" != "jira-write" ] && command -v acli >/dev/null 2>&1 && acli auth status >/dev/null 2>&1; then
|
|
50
|
-
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
51
|
-
if [ "$current_site" != "$SITE" ]; then
|
|
52
|
-
# acli installed but pointing at a different site. Try switching profiles.
|
|
53
|
-
acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1 || true
|
|
54
|
-
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
55
|
-
fi
|
|
56
|
-
if [ "$current_site" = "$SITE" ]; then
|
|
57
|
-
substrate="acli"
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# Tier 2: Atlassian MCP (if acli not ready OR the operation isn't acli-covered)
|
|
62
|
-
# $OP_REQUIRES is a conceptual variable set by the dispatch table to "non-acli" for
|
|
63
|
-
# operations that have no acli adapter (e.g. read-page-descendants). It is not a real
|
|
64
|
-
# shell variable initialized here — the condition is illustrative pseudo-code.
|
|
65
|
-
if [ -z "$substrate" ] || [ "$OP_REQUIRES" = "non-acli" ]; then
|
|
66
|
-
# Probe via mcp__plugin_atlassian_atlassian__getAccessibleAtlassianResources.
|
|
67
|
-
# (Pseudo-code; actual call is the MCP tool invocation, not a bash command.)
|
|
68
|
-
# If the MCP returns a list and $CLOUDID is in it, MCP is identity-matched.
|
|
69
|
-
# If the MCP is unauthenticated or $CLOUDID is NOT in the list, MCP is skipped.
|
|
70
|
-
if mcp_atlassian_authenticated_and_matches_cloudid "$CLOUDID"; then
|
|
71
|
-
: ${substrate:=mcp}
|
|
72
|
-
# Mark MCP as available even if acli already won tier 1 — used for ops acli can't do.
|
|
73
|
-
mcp_available=true
|
|
74
|
-
fi
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# Tier 3: curl + API token (headless / multi-account / scoped-token path)
|
|
43
|
+
# Tier 1: curl + API token — the configured-provider substrate, resolved through
|
|
44
|
+
# lisa-secrets-access. Leads for every operation because it is per-invocation-bound:
|
|
45
|
+
# the cloudId-scoped gateway URL and the token's own account carry the tenant inside
|
|
46
|
+
# the request, so no ambient machine-global state can redirect it. acli (one global
|
|
47
|
+
# active account) and the MCP (browser OAuth session) are ambient-bound and therefore
|
|
48
|
+
# TOCTOU-exposed — see credential-substrate-precedence, "tenant safety".
|
|
78
49
|
read_atlassian_token() {
|
|
79
50
|
local email="$1"
|
|
80
51
|
[ -n "$ATLASSIAN_API_TOKEN" ] && { echo "$ATLASSIAN_API_TOKEN"; return; }
|
|
@@ -136,13 +107,50 @@ public static class LisaCred {
|
|
|
136
107
|
esac
|
|
137
108
|
}
|
|
138
109
|
TOKEN=$(read_atlassian_token "$EMAIL")
|
|
139
|
-
[ -n "$TOKEN" ]
|
|
140
|
-
|
|
110
|
+
if [ -n "$TOKEN" ]; then
|
|
111
|
+
# Identity-match before use: /rest/api/3/myself must report the configured account
|
|
112
|
+
# (Step 2). A present-but-wrong token fails the gate loudly instead of quietly
|
|
113
|
+
# deferring to an acli profile or MCP session authenticated somewhere else — that
|
|
114
|
+
# silent success is the bug class the precedence contract exists to surface.
|
|
115
|
+
if atlassian_token_matches_config "$TOKEN" "$EMAIL" "$CLOUDID"; then
|
|
116
|
+
curl_available=true
|
|
141
117
|
substrate="curl"
|
|
142
118
|
else
|
|
143
|
-
:
|
|
119
|
+
echo "Warning: ATLASSIAN_API_TOKEN does not match the configured account/site. Skipping curl tier." >&2
|
|
144
120
|
fi
|
|
145
|
-
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Tier 2: acli — identity-matched fallback. Used when no token is available, or for
|
|
124
|
+
# operations with no curl adapter. Never the primary path for JIRA writes when token
|
|
125
|
+
# auth is available: acli stores one machine-global active account and workitem writes
|
|
126
|
+
# cannot pin a cloudId per invocation, so switch-then-write is a TOCTOU risk in
|
|
127
|
+
# multi-account or concurrent sessions. When a write does land here it is the *guarded*
|
|
128
|
+
# fallback documented in the dispatch table (assert, write, re-read, assert, roll back).
|
|
129
|
+
if command -v acli >/dev/null 2>&1 && acli auth status >/dev/null 2>&1; then
|
|
130
|
+
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
131
|
+
if [ "$current_site" != "$SITE" ]; then
|
|
132
|
+
# acli installed but pointing at a different site. Try switching profiles.
|
|
133
|
+
acli auth switch --site "$SITE" ${EMAIL:+--email "$EMAIL"} >/dev/null 2>&1 || true
|
|
134
|
+
current_site=$(acli auth status 2>/dev/null | awk '/^ Site:/{print $2}')
|
|
135
|
+
fi
|
|
136
|
+
if [ "$current_site" = "$SITE" ]; then
|
|
137
|
+
acli_available=true
|
|
138
|
+
# Mark acli available even if curl already won tier 1 — used for ops curl can't do.
|
|
139
|
+
: ${substrate:=acli}
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Tier 3: Atlassian MCP — first-class interactive fallback, for when neither tier above
|
|
144
|
+
# is available or covers the operation (e.g. an op with no curl and no acli adapter).
|
|
145
|
+
# Probe via mcp__plugin_atlassian_atlassian__getAccessibleAtlassianResources.
|
|
146
|
+
# (Pseudo-code; actual call is the MCP tool invocation, not a bash command.)
|
|
147
|
+
# If the MCP returns a list and $CLOUDID is in it, MCP is identity-matched.
|
|
148
|
+
# If the MCP is unauthenticated or $CLOUDID is NOT in the list, MCP is skipped.
|
|
149
|
+
if mcp_atlassian_authenticated_and_matches_cloudid "$CLOUDID"; then
|
|
150
|
+
: ${substrate:=mcp}
|
|
151
|
+
# Mark MCP as available even if an earlier tier won — used for ops they can't do.
|
|
152
|
+
mcp_available=true
|
|
153
|
+
fi
|
|
146
154
|
|
|
147
155
|
# Fail loudly with actionable remediation if nothing works.
|
|
148
156
|
if [ -z "$substrate" ]; then
|
|
@@ -154,15 +162,25 @@ if [ -z "$substrate" ]; then
|
|
|
154
162
|
cat >&2 <<EOF
|
|
155
163
|
Error: no Atlassian access substrate available for site $SITE.
|
|
156
164
|
|
|
157
|
-
Attempted:
|
|
165
|
+
Attempted (in credential-substrate-precedence order):
|
|
166
|
+
curl — no ATLASSIAN_API_TOKEN found for $EMAIL (env, slug-suffixed env, or keychain) OR the token does not match the configured account/site
|
|
158
167
|
acli — $(command -v acli >/dev/null && echo "installed but identity mismatch or unauthenticated" || echo "not installed")
|
|
159
168
|
MCP — $([ "$plugin_enabled_global" = "true" ] || [ "$plugin_enabled_project" = "true" ] || [ "$plugin_enabled_local" = "true" ] && echo "plugin enabled but not authenticated or cloudId $CLOUDID not in accessible resources" || echo "plugin not enabled in any settings.json scope")
|
|
160
|
-
curl — no ATLASSIAN_API_TOKEN found for $EMAIL (env, slug-suffixed env, or keychain)
|
|
161
169
|
|
|
162
|
-
Remediation paths (
|
|
170
|
+
Remediation paths (the first is the contract's primary path):
|
|
171
|
+
|
|
172
|
+
1. Provision an API token — works headless, in CI, in subagents, and in
|
|
173
|
+
multi-account setups, and is the substrate this project resolves first.
|
|
174
|
+
|
|
175
|
+
Run /lisa:setup:atlassian — guided flow with clipboard-piped keychain store.
|
|
176
|
+
|
|
177
|
+
2. Install acli and authenticate (identity-matched fallback for multi-account developers).
|
|
163
178
|
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
brew tap atlassian/homebrew-acli && brew install acli
|
|
180
|
+
acli auth login # OAuth as the account matching $EMAIL
|
|
181
|
+
|
|
182
|
+
3. Install the Atlassian MCP plugin (local scope — per-developer, gitignored).
|
|
183
|
+
The supported fallback when no credentials provider is configured.
|
|
166
184
|
|
|
167
185
|
Run in your terminal:
|
|
168
186
|
|
|
@@ -174,25 +192,16 @@ Remediation paths (pick one):
|
|
|
174
192
|
Then restart Claude Code (or run /restart-mcp) to load the plugin, and
|
|
175
193
|
invoke 'mcp__plugin_atlassian_atlassian__authenticate' to complete OAuth.
|
|
176
194
|
|
|
177
|
-
2. Install acli and authenticate (best for multi-account developers).
|
|
178
|
-
|
|
179
|
-
brew tap atlassian/homebrew-acli && brew install acli
|
|
180
|
-
acli auth login # OAuth as the account matching $EMAIL
|
|
181
|
-
|
|
182
|
-
3. Provision an API token (headless / CI / scoped-token environments).
|
|
183
|
-
|
|
184
|
-
Run /lisa:setup:atlassian — guided flow with clipboard-piped keychain store.
|
|
185
|
-
|
|
186
195
|
EOF
|
|
187
196
|
exit 1
|
|
188
197
|
fi
|
|
189
198
|
```
|
|
190
199
|
|
|
191
|
-
Operation dispatch then uses `$substrate` for the primary route. If the operation has no
|
|
200
|
+
Operation dispatch then uses `$substrate` for the primary route. If the operation has no adapter for the selected substrate, fall through in contract order — `$curl_available`, then `$acli_available`, then `$mcp_available` — skipping the tier already tried. The fall-through stops at the first available tier that can perform the operation. A tier that failed identity-match is never in the fall-through set.
|
|
192
201
|
|
|
193
202
|
### Step 2 — Connection-match check
|
|
194
203
|
|
|
195
|
-
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Step 1's substrate selection already tries to switch mismatched acli profiles
|
|
204
|
+
The active connection MUST point at the cloudId/site declared in `.lisa.config.json`. Identity-match is mandatory on **every** substrate, tier 1 included (`credential-substrate-precedence`); the "curl mode check" below *is* the tier-1 gate referenced as `atlassian_token_matches_config` in Step 1. Step 1's substrate selection already validates the token account and tries to switch mismatched acli profiles before selection. This step repeats the assertion before any operation runs — defensive in case the substrate state changed since selection.
|
|
196
205
|
|
|
197
206
|
Read configured site:
|
|
198
207
|
|
|
@@ -291,12 +300,12 @@ Rules:
|
|
|
291
300
|
|
|
292
301
|
### Step 3 — Operation dispatch
|
|
293
302
|
|
|
294
|
-
Substrate column meanings:
|
|
303
|
+
Substrate column meanings (ordering per `credential-substrate-precedence`):
|
|
295
304
|
|
|
296
|
-
- **`
|
|
297
|
-
- **`
|
|
298
|
-
- **`
|
|
299
|
-
- Multiple cells filled means tier ordering applies — try
|
|
305
|
+
- **`curl`**: routes through curl + Basic auth + `ATLASSIAN_API_TOKEN` — the configured-provider substrate. Preferred for every operation, read or write, whenever the token is present and identity-matched.
|
|
306
|
+
- **`acli`**: routes through `acli`. Identity-matched fallback — used when no token is available or the op has no curl adapter. For JIRA writes it is the *guarded* fallback (see the tenant-safety rule below).
|
|
307
|
+
- **`MCP`**: routes through the Atlassian MCP. First-class fallback for ops neither tier above covers, when identity-matched (cloudId in `getAccessibleAtlassianResources`).
|
|
308
|
+
- Multiple cells filled means tier ordering applies — try curl, then acli, then MCP, taking the first that has an adapter for the op AND is identity-matched.
|
|
300
309
|
- One cell means only that substrate can perform the op.
|
|
301
310
|
|
|
302
311
|
`<SITE>` = `.atlassian.site` (e.g. `acme.atlassian.net`). `<CLOUDID>` = `.atlassian.cloudId`. `<AUTH>` = `Basic $(printf '%s:%s' "$email" "$ATLASSIAN_API_TOKEN" | base64)`. JIRA curl writes use the cloudId-bound Atlassian gateway `https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/...`; JIRA curl reads may use either that gateway or `https://<SITE>/rest/api/3/...` after the token account check. Confluence uses `/wiki/rest/api/...` (v1) or `/api/v2/...` (v2).
|
|
@@ -334,7 +343,7 @@ Substrate column meanings:
|
|
|
334
343
|
|
|
335
344
|
**acli flag note:** acli's `--output` flag does not exist; the correct flag is `--json`. List commands require `--paginate` or `--limit` (no implicit fetch-all). `acli jira workitem view` defaults to a restricted field set (`key,issuetype,summary,status,assignee,description`), so `read-ticket` MUST pass `--fields '*all'` or an explicit equivalent that includes every downstream dependency: parent, subtasks, issue links, components, labels, priority, status, issue type, summary, description, fix versions, affected versions, attachments, comments, estimates, sprint/story-point fields, and project-required custom fields. Never rely on the default view fields; they hide parent/components/labels and corrupt leaf-only, relationship-search, build-ready, and required-custom-field gates. Several documented adapters are nominal — verify against `acli <subcmd> --help` before relying on them. When acli's adapter is broken or missing for a specific op, fall through to MCP (if identity-matched) then curl per the tier ordering.
|
|
336
345
|
|
|
337
|
-
**JIRA write tenant-safety rule
|
|
346
|
+
**JIRA write tenant-safety rule** — the Atlassian instance of the shared guarded-fallback protocol in `credential-substrate-precedence` (which states the general rule: prefer the per-invocation-bound substrate over the ambient-bound one, for reads and writes alike; the rationale is not restated here). Create, edit, transition, comment, and link are write operations. They MUST use the curl adapter whenever token auth is available because the URL includes `<CLOUDID>` and cannot be redirected by the user-global acli active account. If the flow must fall back to acli for a write, it is a guarded fallback, not the normal path:
|
|
338
347
|
|
|
339
348
|
1. Switch and assert the active `acli auth status` site/email matches config immediately before the write.
|
|
340
349
|
2. Execute the write.
|
|
@@ -378,15 +387,17 @@ Do not paraphrase substrate output beyond JSON normalization.
|
|
|
378
387
|
## Invariants
|
|
379
388
|
|
|
380
389
|
- Caller skills never invoke `acli` or `curl` against Atlassian directly. They only invoke this skill.
|
|
390
|
+
- Tier order is the shared `credential-substrate-precedence` contract — token curl first (reads **and** writes), then acli, then the Atlassian MCP. Do not restate or locally override the ordering here.
|
|
391
|
+
- acli and the Atlassian MCP remain first-class **fallbacks**, not removed tiers: every adapter stays in the dispatch table, and a project with no credentials provider is fully functional on them.
|
|
381
392
|
- Substrate is decided once per skill invocation and never switches mid-operation.
|
|
382
|
-
- Connection match is mandatory. Operations that bypass it (because "the user obviously meant the configured site") are forbidden.
|
|
393
|
+
- Connection match is mandatory on every tier, including the token tier. Operations that bypass it (because "the user obviously meant the configured site") are forbidden. A present-but-wrong token fails the gate rather than deferring to another substrate.
|
|
383
394
|
- Profile mutations (`acli auth switch`) are allowed when acli is the active substrate. The curl substrate never mutates the token — if `ATLASSIAN_API_TOKEN` doesn't match the configured account, fail loud rather than silently substituting.
|
|
384
395
|
- JIRA writes are cloudId-bound by default. `acli` write adapters are fallback-only and must perform post-write tenant assertions plus safe rollback on mismatch.
|
|
385
396
|
- `.lisa.config.local.json` overrides `.lisa.config.json` per-key — the same precedence rule as every other consumer of project config.
|
|
386
397
|
|
|
387
398
|
## Headless behavior
|
|
388
399
|
|
|
389
|
-
In a headless / non-interactive context, the MCP tier is unavailable (its OAuth flow needs a browser). The
|
|
400
|
+
In a headless / non-interactive context, the MCP tier is unavailable (its OAuth flow needs a browser). The ladder collapses to: curl + `ATLASSIAN_API_TOKEN` → acli (if pre-authenticated, e.g., a CI image baked with a service-account token). Because curl is already tier 1 interactively, headless and interactive sessions take the **same primary path** — that is the "headless parity" arm of `credential-substrate-precedence`, and it is why a credential problem reproduces on a laptop instead of only in cron. Never block on interactive prompts. If both fail readiness checks, exit non-zero with a deterministic error.
|
|
390
401
|
|
|
391
402
|
Treat all four of these as headless:
|
|
392
403
|
|