forge-connector について
forge-connector は、外部データを Teamwork Graph に取り込み、Rovo Search と Rovo Chat で検索可能にする Atlassian Forge の graph:connector アプリを構築するスキルです。開発者向けに、オブジェクト種別やアクセス制御の検討から、ひな型作成、同期処理まで案内します。
主な機能
- 01コネクターアプリのひな型生成
- 02Teamwork Graph のオブジェクト種別の選定
- 03認証情報とフォーム設定の案内
- 04アクセス制御とデータ権限の設計
- 05定期的な再取り込みの設定
活用シーン
- 01外部システムのコンテンツを Rovo 検索に追加
- 02ソース側の権限を検索結果に反映
- 03接続データの定期更新を設定
MCP 対応
スキルでは、マニフェスト構文、モジュール設定、デプロイ手順の参照先として Forge MCP を挙げています。Custom UI を追加する場合は、Atlaskit コンポーネントの参照に ADS MCP を利用します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ atlassian/ forge-skills --skill forge-connectorインストール後、Claude Code で/forge-connectorを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdscripts/__init__.pyforge_env.pyscaffold_connector.pytests/__init__.pytest_forge_env.pytest_scaffold_connector.py詳細
Forge コネクター
外部データを Atlassian の Teamwork Graph に取り込み、Rovo Search と Rovo Chat に表示する graph:connector Forge アプリを構築します。
重要なルール
- Jira へのインストールが必須です — Teamwork Graph モジュールを使用するアプリは、Jira サイトにインストールする必要があります。Confluence のみにインストールしても動作しません。
- チャットで認証情報を尋ねないでください — ユーザー自身のターミナルで
forge loginを実行するよう案内してください。 - 必ず自分でスキャフォールドスクリプトを実行してください — 手動の手順を案内するだけでなく、
scripts/scaffold_connector.pyを実行してボイラープレートを生成してください。 - インストールが必要な場合は、必ずユーザーに Atlassian サイトの URL を尋ねてください — URL を自分で調べたり、推測したりしないでください。
- 切断すると Atlassian がデータを削除します —
action = 'DELETED'の場合、アプリはローカルの状態をクリーンアップするだけでかまいません。Teamwork Graph のデータは Atlassian が自動的に削除します。 - ハンドラーの引数は直接渡されます — Forge はリクエストオブジェクトをハンドラーの最初の引数として渡します。
event.payloadの下にはネストされません。設定値はevent.payload.configではなく、request.configPropertiesにあります。これはTypeError: Cannot destructure property of undefinedエラーの最も一般的な原因です。 - ストレージには
@forge/kvsを使用してください —@forge/kvsからkvsをインポートしてください。@forge/storageは使用しないでください。コネクター関数の実行時には、そのstorageエクスポートがundefinedになります。 @forge/teamwork-graphの名前付きエクスポートgraphを使用してください — 正しいインポートはconst { graph } = require('@forge/teamwork-graph')です。graph.setObjects({ objects, connectionId })を呼び出してください。setObjectsを名前付きエクスポートとして直接インポートしないでください。validateConnectionHandlerは{ success, message }を返す必要があります — Error をスローしないでください。拒否する場合は{ success: false, message: '...' }、受け入れる場合は{ success: true }を返してください。function宣言はmodulesの下に配置してください —manifest.ymlでは、function:はトップレベルのキーではなく、modules:の下に置くキーです。トップレベルに配置すると lint エラーが発生します。formConfigurationではtype: headerを含むform配列を使用します —fields:やbeforeYouBegin:は使用しないでください。正しい形式はform: [{ key, type: header, title, description, properties: [...] }]です。- スコープは
read/write/delete:object:jiraです —read:object:jira、write:object:jira、delete:object:jiraを使用してください。read:graph:teamworkとwrite:graph:teamworkは無効で、forge lintが失敗します。 - このスキルで実行する
forgeコマンドにはATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connectorを設定してください — シェルで実行するforgeコマンド(例:forge lint、forge logs、forge deploy)と、代替手段としてユーザーに提示する対話型のforge createコマンドには、この環境変数を付けてください。同梱のスクリプトでは自動的に設定されます。このスキル内で簡略化のために省略されているその他のコマンドも、実行時には追加してください。対象外はforge loginとforge tunnel(ユーザーが実行する認証/ライブ開発コマンド)のみです。 setObjects/setUsers/setGroupsのレスポンスを無視しないでください — データ取り込みの呼び出しを、結果を確認しないまま実行する処理として扱わないでください。必ずresponse.successとresponse.results?.rejectedの両方を確認してください。呼び出しが部分的に失敗する場合があります。一部のオブジェクトが拒否され、他のオブジェクトが受け入れられることがあり、rejected.length > 0でもresponse.successがtrueになることがあります。- アクセス制御 ID に一貫性を持たせてください —
permissionsでUSERまたはGROUPのプリンシパルを使用する場合、idの値は、対応するgraph.setUsers()/graph.setGroups()の呼び出しで使用するexternalIdと完全に一致させる必要があります。ID が一致しないと(例:user-42とaccountId:user-42)、本来アクセスできるユーザーからオブジェクトが見えなくなりますが、そのことは通知されません。 - 更新スケジュールにはオーケストレーションを使用してください — ユーザーが定期的なデータ再取り込みを希望する場合は、
scheduledTriggerではなく、graph.scheduleOrUpdateTaskとtaskRunnerハンドラー(プラットフォーム管理方式)を使用してください。オーケストレーションでは、実行頻度、再試行、ファンアウトが標準で処理されます。プラットフォームのタスク追跡を必要としない軽量な cron スタイルのトリガーをユーザーが明示的に求めている場合に限り、scheduledTriggerを代替として使用してください。 taskIdは有効な UUID でなければなりません —scheduleOrUpdateTaskとscheduleChildTaskのどちらも、task.taskIdに UUID 文字列を必要とします。ルートタスクにはuuid()(v4)を使用し、子タスクにはuuidv5('<scanId>:<parentTaskId>:<itemId>', APP_NAMESPACE)を使用してください。ルートタスク ID は KVS に保存し、onConnectionChangeを再実行したときに同じ ID を再利用できるようにしてください。これにより、競合するスケジュールが作成されず、既存タスクが更新されます。- スケジュール前に
TaskInfoを KVS に書き込み、完了時に削除しないでください —scheduleOrUpdateTaskまたはscheduleChildTaskを呼び出す前に、必ずtaskIdをキーとしてTaskInfoレコードを KVS に書き込んでください。成功したら、レコードのcompleted: trueを設定し、削除しないでください。削除すると、重複配信でENTITY_NOT_FOUNDが返され、先に記録された成功が上書きされます。 - オーケストレーションのレスポンスでは
response.errorで分岐してください —scheduleOrUpdateTaskとscheduleChildTaskは、成功時に{ success: true }ではなく{ status: 'ACCEPTED' }を返します。失敗を検出するにはif (response.error)を確認し、if (!response.success)は確認しないでください。
MCP の前提条件
| MCP サーバー | 目的 |
|---|---|
| Forge MCP | マニフェスト構文、モジュール設定、デプロイガイド |
| ADS MCP | Atlaskit コンポーネント(カスタム UI を追加する場合のみ) |
エージェントのワークフロー — ステップ 0~7 を順番に完了してください
ステップ 0:前提条件
Node.js(node -v、22 以上が必要)、Forge CLI(forge --version)、ログイン状態(forge whoami)を確認します。不足しているツールをインストールします:
npm install -g @forge/cli
認証されていない場合は、ターミナルで forge login を実行するようユーザーに伝えます。
ステップ 1:開発者スペースを見つける
注: Forge CLI 12.x には
forge developer-spaces listが存在しません。非対話形式では開発者スペースの一覧を取得できません。
forge create では、開発者スペースを選択するためにインタラクティブな TTY が必要です。ユーザー自身で実行するよう依頼します:
Tell the user:
cd <parent-directory>
ATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connector forge create --template blank <app-name>
When prompted, select a Developer Space and let it complete.
Come back when done.
scaffold スクリプトの --dev-space-id フラグは省略可能で、指定しなくても構いません。指定されていない場合にスクリプトがこのフラグをスキップするよう、更新されています。
ステップ 1.5:データを見つけてオブジェクトタイプにマッピングする
スキャフォールディングの前に実施してください。 適切な Teamwork Graph オブジェクトタイプを特定するため、ユーザーに次の質問をします。atlassian:document を前提としたり、デフォルトとして使用したりしないでください。
ユーザーへの質問
-
どの外部システムまたはツールに接続しますか? 例:Google Drive、ServiceNow、Salesforce、GitHub、Confluence、Slack、Figma、Zendesk
-
Rovo でどのようなコンテンツを検索可能にしたいですか? 特定に役立つ例を挙げて質問してください。
- ファイル、ページ、Wiki 記事、レポート、PDF →
atlassian:documentが候補です - タスク、チケット、課題、バグ、ストーリー →
atlassian:work-itemが候補です - チャットメッセージ、メール、コメント →
atlassian:messageまたはatlassian:commentが候補です - プロジェクト、ワークスペース、ボード →
atlassian:projectが候補です - コードリポジトリ →
atlassian:repositoryが候補です - プルリクエスト/マージリクエスト →
atlassian:pull-requestが候補です - Git コミット →
atlassian:commitが候補です - デザインファイル(Figma、Sketch)→
atlassian:designが候補です - 動画録画 →
atlassian:videoが候補です - カレンダーイベント、会議 →
atlassian:calendar-eventが候補です - スレッド、チャンネル →
atlassian:conversationが候補です - 顧客アカウントまたは組織 →
atlassian:customer-organizationが候補です - チームスペースまたは組織単位 →
atlassian:spaceが候補です
- ファイル、ページ、Wiki 記事、レポート、PDF →
-
コンテンツは単一の種類ですか、それとも複数の種類が混在していますか? 混在している場合(例:タスクとドキュメントの両方を扱うプロジェクト管理ツール)は、それぞれを独自のオブジェクトタイプとして取り込む計画を立ててください。スキャフォールドは主要なタイプを 1 つサポートしています。後から
manifest.ymlにobjectTypesエントリを追加できます。 -
接続時に管理者が認証情報(API キー、URL、OAuth トークン)を入力する必要がありますか? 必要な場合 → スキャフォールドコマンドで
--has-form-configを使用します。 不要な場合(データがすべて Atlassian 内から取得される場合)→ このフラグを省略します。 -
ソースデータはどのくらいの頻度で変更されますか? これにより同期の頻度が決まります。プラットフォームによるすべての更新には、オーケストレーション(
scheduleOrUpdateTask+taskRunner)を使用してください。これにより、頻度、再試行、ファンアウトがネイティブに処理されます。軽量な cron をタスク追跡なしで使用する必要が明確にある場合に限り、従来のscheduledTrigger方式を使用してください。頻度 オーケストレーションの間隔 1 時間ごと scheduleInterval: { value: 1, timeUnit: 'hour' }毎日 scheduleInterval: { value: 1, timeUnit: 'day' }N 分ごと(1~60) scheduleInterval: { value: N, timeUnit: 'minute' }静的/一度のみ スケジュールされた更新は不要です。 onConnectionChangeで一度取り込みます選択した間隔を記録してから、ステップ 2 に進んでください。
-
Rovo Search で取り込んだコンテンツを閲覧できるのは誰ですか? これにより、各オブジェクトの
permissions.accessControlsが決まります。次の質問をしてください。- 「このコンテンツはすべて公開されていますか、それともソースシステムで閲覧できるユーザーが制限されていますか?」
- 「Rovo Search の検索結果にも、ソースシステムの権限を反映させますか?」
回答を適切なプリンシパルモデルに対応させてください。
ソースシステムのアクセスモデル 使用する accessControls制限のない公開コンテンツ principals: [{ type: 'EVERYONE' }]特定の名前付きユーザーがアクセス可能 principals: [{ type: 'user', id: '<atlassian-account-id>' }]— ユーザーごとに 1 エントリチームまたはグループベース(例:Confluence スペース、Google Workspace グループ) principals: [{ type: 'group', id: '<group-id>' }]— グループごとに 1 エントリ非公開/所有者のみ 所有者の Atlassian アカウント ID を指定した userプリンシパルを 1 つ混在(ソースのオブジェクトごとの ACL) 取り込み時に各アイテムの ACL を取得し、それぞれを userまたはgroupプリンシパルに対応させるユーザーがコンテンツは公開されていると明示的に確認しない限り、
EVERYONEを既定にしないでください。制限されたコンテンツにEVERYONEを設定すると、Rovo Search で閲覧すべきでないユーザーにデータが漏洩します。選択した権限モデルを記録してから、ステップ 2 に進んでください。ステップ 3 で
setObjects呼び出しを記述する際に参照してください。
マッピングの決定
回答に基づいて、以下のオブジェクトタイプの表から最適なタイプを選択します。コンテンツにより適したタイプが本当にない場合(例:任意のファイル添付)に限り、atlassian:document を選択してください。「Rovo でインデックス化」の列で ❌ が付いているタイプ(atlassian:build、atlassian:deployment、atlassian:test)については、これらのオブジェクトは Rovo Search または Rovo Chat に表示されないことをユーザーに警告してください。
ステップ 2 に進む前に、選択したオブジェクトタイプと権限モデルを記録してください。
ステップ 2:コネクターアプリをスキャフォールドする
スキルのディレクトリ(この SKILL.md を含むディレクトリ)から実行します。<object-type> をステップ 1.5 で決定したタイプに置き換えてください。--dev-space-id は省略可能です。
python3 -m scripts.scaffold_connector \
--name <app-name> \
--connector-name "<Human Readable Name>" \
--object-type <object-type> \
--directory <parent-directory>
前のステップで ID を取得している場合に限り、--dev-space-id <id> を追加してください。
オブジェクトタイプ — ステップ 1.5 で選択したタイプを使用してください。上記の確認質問に回答する前に、既定で atlassian:document を選択しないでください。
フォーム設定フラグ — 管理者が API 認証情報または接続の詳細を入力する必要がある場合(ステップ 1.5 の質問 4 で決定)、--has-form-config を追加してください。Atlassian 内だけで動作するアプリ(外部の認証情報が不要)の場合は省略してください。
forge createに TTY が必要なためスキャフォールドに失敗する場合: スキャフォールドスクリプトは手動で実行する代替コマンドを出力します。ユーザーに対話形式でforge createを実行してもらい、ディレクトリが作成されたらステップ 3 に進んでください。スキャフォールドスクリプトがmanifest.ymlとsrc/index.jsを書き込むのは、ディレクトリの作成後だけです。
ステップ 3:生成されたコードをカスタマイズする
スキャフォールドの実行後(またはユーザーが対話形式で forge create を実行した後):
cd <app-name>
npm install
空のテンプレートは src/index.js(TypeScript ではなく JavaScript)を生成します。API 呼び出しを追加するために編集してください。スキャフォールドによって動作するハンドラーのひな形が生成されます。ビジネスロジックを追加してください。
編集する主なファイル
| ファイル | 変更内容 |
|---|---|
src/index.js | fetchExternalData() — API 呼び出しに置き換えます |
manifest.yml | 外部 API がある場合は、その URL を permissions.external.fetch.backend に追加します |
package.json | 依存関係として @forge/api、@forge/kvs、@forge/teamwork-graph を追加します |
setObjects — Teamwork Graph にデータを取り込む
graph の名前付きエクスポートを使用してください。setObjects を直接分割代入しないでください。
const { graph } = require('@forge/teamwork-graph');
const result = await graph.setObjects({
connectionId, // required — the connectionId from the handler request
objects: [
{
schemaVersion: '1.0',
id: 'unique-id-from-source', // unique per connectionId
updateSequenceNumber: 1,
displayName: 'My Document Title',
url: 'https://source-system.example.com/doc/123',
createdAt: '2024-01-15T10:00:00Z', // ISO 8601
lastUpdatedAt: '2024-01-20T14:30:00Z',
// Use the permission model chosen in Step 1.5 question 6.
// EVERYONE only if content is confirmed publicly accessible.
// For user-restricted content: { type: 'USER', id: '<externalId from setUsers>' }
// For group-restricted content: { type: 'GROUP', id: '<externalId from setGroups>' }
permissions: [{
accessControls: [{
principals: [{ type: 'EVERYONE' }],
}],
}],
'atlassian:document': {
type: {
category: 'DOCUMENT', // see Document Categories table below
mimeType: 'application/vnd.google-apps.document',
},
content: {
mimeType: 'application/vnd.google-apps.document',
text: 'document title or snippet for search indexing',
},
},
},
],
});
// Always check both success AND rejected — a call can partially fail.
const rejected = result.results?.rejected ?? [];
if (!result.success || rejected.length > 0) {
console.error('setObjects ingestion failed', {
connectionId,
acceptedCount: result.results?.accepted?.length ?? 0,
rejectedCount: rejected.length,
rejected,
error: result.error,
});
}
- 1 回の呼び出しにつき最大 100 個のオブジェクト — 大規模なデータセットはループでバッチ処理します
idはconnectionIdごとに一意である必要があります- すべての
graph.setObjects()呼び出しでconnectionIdが必要です
ドキュメントカテゴリー(atlassian:document.type.category 用)
| MIME タイプ | カテゴリー |
|---|---|
application/vnd.google-apps.document | DOCUMENT |
application/vnd.google-apps.spreadsheet | SPREADSHEET |
application/vnd.google-apps.presentation | PRESENTATION |
application/vnd.google-apps.folder | FOLDER |
application/pdf | PDF |
image/* | IMAGE |
video/* | VIDEO |
audio/* | AUDIO |
| その他 | OTHER |
getObjectByExternalId — 単一オブジェクトを検索する
const { graph } = require('@forge/teamwork-graph');
const data = await graph.getObjectByExternalId({
externalId: 'unique-id-from-source',
objectType: 'atlassian:document',
connectionId,
});
if (data.success) console.log(data.object);
ステップ 4:デプロイしてインストールする
必ずデプロイスクリプトを実行してください — ユーザーに手動で forge deploy コマンドを実行するよう案内するだけでは不十分です。
デプロイスクリプトはこのスキルではなく、forge-app-builder スキルにあります。この SKILL.md のパスからそのディレクトリを特定してください。2 階層上に移動し(skills/forge-connector/ → skills/)、次に forge-app-builder/ に移動します。以下のすべてのコマンドは、そのディレクトリから実行してください。
# Derive forge-app-builder skill dir from this SKILL.md's path:
# e.g. if this file is at /path/to/skills/forge-connector/SKILL.md
# then the deploy script dir is: /path/to/skills/forge-app-builder/
# If you have the site URL:
python3 -m scripts.deploy_forge_app \
--app-dir <app-directory> \
--site <site-url> \
--product jira
# If you don't have the site URL yet, deploy first then ask:
python3 -m scripts.deploy_forge_app \
--app-dir <app-directory> \
--product jira \
--deploy-only
# Ask: "What is your Atlassian site URL (e.g. yourcompany.atlassian.net)?"
python3 -m scripts.deploy_forge_app \
--app-dir <app-directory> \
--site <site-url> \
--product jira \
--skip-deps
ステップ 5:Atlassian Administration から接続する
デプロイ後、ユーザーに次の手順を案内してください。
- Atlassian Administration → Apps → [site] → Connected apps に移動します
- アプリを見つけて、View app details → Connections タブを選択します
- コネクターの下にある Connect をクリックします
- 設定フィールドがある場合(
formConfigurationが定義されている場合)は入力します - Connect をクリックします。これにより
action: CREATEDを指定してonConnectionChangeが呼び出され、データの取り込みが開始されます
ステップ 6:forge tunnel で監視する
開発中に forge tunnel を使うと、コネクター関数の実行時にライブログをターミナルへ直接ストリーミングできます。forge logs を待つ必要がないため、onConnectionChangeHandler、validateConnectionHandler、setObjects の呼び出しで発生したエラーを見つける最も速い方法です。
ユーザー自身のターミナルで実行するよう伝えてください(対話型セッションが必要です)。
cd <app-directory>
forge tunnel
トンネルを起動すると、コネクター関数が呼び出されるたびに(Atlassian Admin で「接続」をクリックする場合や、スケジュールされた再取り込みを実行する場合など)、出力がすぐにストリーミングされます。次の内容を確認してください。
[connector] Fetched N items—fetchExternalData()が実行されたことを確認できます[connector] Batch 1: N accepted, 0 rejected—setObjectsが成功したことを確認できますvalidateConnectionHandlerからの未捕捉エラーやスローされた例外
トンネルを実行していない場合は、代わりに forge logs を使って過去の呼び出しを確認してください。
# Most recent 50 log lines from development environment
forge logs -e development --limit 50
# Production logs for a specific site
forge logs -e production --site <your-site> --limit 50
トンネルとログ — 使い分け:
| 状況 | 使用するもの |
|---|---|
| 接続フローを積極的に開発/テストしている | forge tunnel — ライブストリーミング |
| 過去の呼び出しや本番環境の問題をデバッグする | forge logs |
| トンネルが捕捉する前にコネクター関数がタイムアウトした | --limit 100 を指定した forge logs |
注:
forge tunnelは、ユーザーが対話型ターミナルで実行する必要があります。エージェント経由で実行しようとしないでください。
ステップ 7:エンドツーエンド検証(任意)
チェックを実行する前に、ユーザーに次のように尋ねてください。
「本番環境へのデプロイ前に、エンドツーエンド検証チェックを実行しますか?接続、取り込み、Rovo Search での表示、権限の境界がすべて正しく機能していることを確認できます。」
ユーザーがいいえと答えた場合、またはスキップを希望した場合は、次に進み、チェックを実行したり説明したりしないでください。 ユーザーがはいと答えた場合は、以下のすべてのチェックを順番に実施してください。
チェック 1 — 接続の確立
Atlassian Administration → Apps → Connected apps で、コネクターのステータスが Connected と表示されていることを確認します。エラーまたは保留中の状態が表示されている場合は、ステップ 6 に戻り、forge tunnel または forge logs の出力を調べてください。
チェック 2 — validateConnection の成功(設定されている場合)
アプリに validateConnection 関数がある場合、Connect をクリックしたときに管理者へ成功メッセージが表示されたことを確認します。表示されていない場合は、ログで戻り値を確認してください。スローされたエラーではなく、{ success: true } が返されている必要があります。
チェック 3 — onConnectionChange の実行と取り込み
forge logs またはトンネルの出力で、次の内容を確認してください。
forge logs -e development --limit 50
次の 3 つの兆候をすべて確認してください。
- ハンドラーが呼び出された:
action: CREATEDを含むonConnectionChangeHandlerのログ行 - データが取得された:例:
[connector] Fetched N items setObjectsが成功した:例:[connector] Batch 1: N accepted, 0 rejected
setObjects が { success: false } を返した場合、エラーの詳細は result.error にあります。ユーザーに伝え、続行する前に修正してください。
チェック 4 — Rovo Search でのオブジェクトの表示
- Jira サイトで Rovo Search を開きます(取り込み後、インデックス作成に最大 5 分かかります)
- 取り込まれたオブジェクトの少なくとも 1 つの
displayNameまたはコンテンツテキストに含まれる単語を検索します - コネクターのニックネーム(接続時に管理者が設定)で絞り込みます
コネクターから少なくとも 1 件の結果が表示されるはずです。5 分経っても何も表示されない場合は、次の内容を確認してください。
setObjectsのログにN acceptedが記録され、N > 0 であること(チェック 3)- オブジェクトの
permissionsがログイン中のユーザーに一致すること(EVERYONEプリンシパル、またはテストユーザーを含むuser/groupプリンシパル) manifest.ymlにwrite:object:jiraスコープが含まれており、スコープを変更した場合はアプリが再デプロイされていること
チェック 5 — 権限境界(EVERYONE を使用した場合のみスキップ)
コネクターで user または group のプリンシパルを使用している場合:
- アクセス権があるはずのユーザーとしてログインし、オブジェクトが Rovo Search に表示されることを確認します
- アクセス権がないはずのユーザーとしてログインし、オブジェクトが表示されないことを確認します
制限されたオブジェクトが権限のないユーザーに表示される場合は、setObjects の accessControls プリンシパルを再確認して、再デプロイしてください。
チェック 6 — Rovo Chat がコネクターのデータを参照する
取り込んだコンテンツにのみ答えが含まれる質問を Rovo Chat にします。例:
「[取り込んだアイテムのタイトル] のステータスは何ですか?」
Rovo Chat はコネクターをソースとして引用するはずです。コンテンツが見つからない場合、チェック 3 と 4 に未解決の問題がある可能性が高いです。
チェック 7 — スケジュールされた再取り込みが実行される(設定している場合)
scheduledTrigger を追加した場合:
- 一時的に
manifest.ymlのinterval: fiveMinutesを設定して再デプロイし、1 サイクル待ちます forge logsにrefreshIngestionHandlerによる新しい取り込み実行が表示されることを確認します- 本番環境に移行する前に、元の間隔に戻して再デプロイします
本番環境への準備完了ゲート
ユーザーが検証の実行を選択した場合、該当するすべてのチェックに合格してから本番環境にデプロイ(forge deploy -e production)してください:
| チェック | 本番環境で必須 |
|---|---|
| 1 — 接続の確立 | 常に |
| 2 — validateConnection の合格 | validateConnection が設定されている場合のみ |
| 3 — エラーなしで取り込みが実行された | 常に |
| 4 — オブジェクトが Rovo Search に表示される | 常に |
| 5 — 権限境界 | user/group プリンシパルを使用する場合のみ |
| 6 — Rovo Chat がコネクターを引用する | 常に |
| 7 — スケジュールされた再取り込みが実行される | scheduledTrigger が設定されている場合のみ |
マニフェストリファレンス
重要なルール:
- スコープは
read:object:jira、write:object:jira、delete:object:jiraです。read:graph:teamwork/write:graph:teamworkではありません(これらはforge lintで失敗します)function:はトップレベルではなく、modules:の下に宣言します- エグレスでは単なる文字列ではなく
address:を使用します(自動修正するにはforge lint --fixを実行します)formConfigurationではform: [{ type: header, properties: [...] }]を使用します。fields:やbeforeYouBegin:ではありません
最小限のコネクター(管理者設定なし、OAuth なし)
アプリが Atlassian 内で完結し、外部の認証情報が不要な場合に使用します。
app:
id: <generated-by-forge-create>
runtime:
name: nodejs24.x
memoryMB: 256
architecture: arm64
permissions:
scopes:
- read:object:jira
- write:object:jira
- delete:object:jira
- storage:app
modules:
graph:connector:
- key: my-connector
name: My Service
capabilities:
replicatesPermissions: false # true if mirroring source-system ACLs into Teamwork Graph
syncFidelity: mirror # append | upsert | mirror (mirror is recommended)
supportsIncrementalSync: false
icons:
light: https://cdn.example.com/logo.png
dark: https://cdn.example.com/logo.png
objectTypes:
- atlassian:document
datasource:
onConnectionChange:
function: on-connection-change
function:
- key: on-connection-change
handler: index.onConnectionChangeHandler
オーケストレーションを備えたコネクター(プラットフォーム管理の更新)
プラットフォーム管理の定期的な再取り込みを有効にするには、任意のコネクター構成に orchestration.taskRunner ブロックを追加します。既存の onConnectionChange ハンドラーと併せて設定します。
modules:
graph:connector:
- key: my-connector
name: My Service
capabilities:
replicatesPermissions: false
syncFidelity: mirror
supportsIncrementalSync: false
icons:
light: https://cdn.example.com/logo.png
dark: https://cdn.example.com/logo.png
objectTypes:
- atlassian:document
orchestration:
taskRunner:
function: task-runner-fn # references the function key below
datasource:
onConnectionChange:
function: on-connection-change
function:
- key: on-connection-change
handler: index.onConnectionChangeHandler
- key: task-runner-fn
handler: index.taskRunner
timeoutSeconds: 900 # optional: extend to 15 min for large datasets
timeoutSecondsが有効なのは、orchestration.taskRunnerのようなコンシューマーモジュールから参照されている関数のみです。参照されていない関数に設定すると、マニフェストの検証に失敗します。
管理者用フォーム設定を備えたコネクター(API キー/URL)
外部システムへの接続に必要な認証情報を管理者が入力する場合に使用します。
app:
id: <generated-by-forge-create>
runtime:
name: nodejs24.x
memoryMB: 256
architecture: arm64
permissions:
scopes:
- read:object:jira
- write:object:jira
- delete:object:jira
- storage:app
external:
fetch:
backend:
- address: 'https://api.your-service.com' # note: address: not a bare string
modules:
graph:connector:
- key: my-connector
name: My Service
capabilities:
replicatesPermissions: false # true if mirroring source-system ACLs into Teamwork Graph
syncFidelity: mirror # append | upsert | mirror (mirror is recommended)
supportsIncrementalSync: false
icons:
light: https://cdn.example.com/logo.png
dark: https://cdn.example.com/logo.png
objectTypes:
- atlassian:document
datasource:
formConfiguration:
form: # use form:, NOT fields: or beforeYouBegin:
- key: connectionDetails
type: header
title: Connection Details
description: >
Provide your My Service API credentials.
Find them in My Service → Settings → API.
properties:
- key: apiKey # camelCase keys — accessed as request.configProperties.apiKey
label: API Key
type: string
isRequired: true
- key: apiUrl
label: API URL
type: string
isRequired: true
validateConnection:
function: validate-connection
onConnectionChange:
function: on-connection-change
function: # function: is under modules:, NOT top-level
- key: on-connection-change
handler: index.onConnectionChangeHandler
- key: validate-connection
handler: index.validateConnectionHandler
ハンドラーのシグネチャ
重要: Forge はリクエストをラップせずに最初の引数として直接渡します。
event.payloadの下にはラップされません。フォームの設定値はevent.payload.configではなくrequest.configPropertiesにあります。これを誤ると、TypeError: Cannot destructure property of undefinedが発生します。
onConnectionChange
const { kvs } = require('@forge/kvs');
const { graph } = require('@forge/teamwork-graph');
exports.onConnectionChangeHandler = async (request) => {
// request.action, request.connectionId, request.configProperties
const { action, connectionId, configProperties } = request;
if (action === 'DELETED') {
// Atlassian removes Teamwork Graph data automatically on disconnect.
// Only clean up locally stored credentials.
await kvs.deleteSecret(connectionId);
return { success: true };
}
// CREATED or UPDATED — persist credentials and ingest data
await kvs.setSecret(connectionId, configProperties);
await ingestAllData(connectionId, configProperties);
return { success: true };
};
validateConnection
const { fetch } = require('@forge/api');
exports.validateConnectionHandler = async (request) => {
// request.configProperties — NOT event.payload.config
const { configProperties } = request;
// Return { success: false, message } to reject — do NOT throw an Error.
// Return { success: true } to accept.
const response = await fetch(`${configProperties['apiUrl']}/health`);
if (!response.ok) {
return { success: false, message: 'Invalid API credentials. Please check your settings.' };
}
return { success: true, message: 'Connection validated successfully.' };
};
refreshIngestion(スケジュールされたトリガー)
exports.refreshIngestionHandler = async () => {
const activeConnections = await kvs.get('active-connections') ?? [];
for (const connectionId of activeConnections) {
const config = await kvs.getSecret(connectionId);
if (config) await ingestAllData(connectionId, config);
}
};
オブジェクトタイプ
太字のオブジェクトは、Rovo Search と Rovo Chat でインデックス化されます。
| オブジェクトタイプ | Rovo でインデックス化 | 最適な用途 |
|---|---|---|
atlassian:document | ✅ | ファイル、ページ、Wiki 記事、レポート |
atlassian:message | ✅ | チャットメッセージ、メール、コメント |
atlassian:work-item | ✅ | タスク、チケット、課題 |
atlassian:project | ✅ | プロジェクト、ワークスペース |
atlassian:space | ✅ | チームスペース、組織単位 |
atlassian:design | ✅ | デザインファイル(Figma など) |
atlassian:repository | ✅ | コードリポジトリ |
atlassian:pull-request | ✅ | PR、マージリクエスト |
atlassian:commit | ✅ | Git コミット |
atlassian:branch | ✅ | Git ブランチ |
atlassian:conversation | ✅ | スレッド、チャンネル |
atlassian:video | ✅ | ビデオ録画 |
atlassian:calendar-event | ✅ | ミーティング、イベント |
atlassian:comment | ✅ | レビューコメント |
atlassian:customer-organization | ✅ | 顧客アカウント、組織 |
atlassian:build | ❌ | CI/CD ビルド |
atlassian:deployment | ❌ | デプロイ |
atlassian:test | ❌ | テストケース |
Rovo Search / Rovo Chat での表示
取り込みが完了すると:
- オブジェクトは Rovo Search に、コネクターのニックネーム(接続時に管理者が設定)を名前とするサブフィルターの下に表示されます
- 取り込んだコンテンツに関連するトピックについて質問すると、Rovo Chat は回答内でコネクターのオブジェクトを参照し、引用できます
- データはすぐには利用できません。
onConnectionChangeの実行後、インデックス化に数分かかる場合があります
取り込みが機能していることを確認するには:
- Jira サイトで Rovo Search を開きます
- 取り込んだオブジェクトの
nameまたはpropertiesに含まれるテキストを検索します - コネクターのニックネームでフィルターし、結果を絞り込みます
大規模データセット向けのバッチ処理パターン
const { graph } = require('@forge/teamwork-graph');
const BATCH_SIZE = 100;
async function ingestAllData(connectionId, config) {
const items = await fetchExternalData(config);
for (let i = 0; i < items.length; i += BATCH_SIZE) {
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
const batch = items.slice(i, i + BATCH_SIZE);
const result = await graph.setObjects({
connectionId, // required in every call
objects: batch.map(item => ({
schemaVersion: '1.0',
id: item.id, // unique per connectionId
updateSequenceNumber: 1,
displayName: item.title,
url: item.url,
createdAt: item.createdAt,
lastUpdatedAt: item.updatedAt,
// Replace with USER/GROUP principals if source system has access controls.
// The id must exactly match the externalId used in graph.setUsers() / graph.setGroups().
permissions: [{
accessControls: [{ principals: [{ type: 'EVERYONE' }] }],
}],
'atlassian:document': {
type: { category: 'DOCUMENT', mimeType: item.mimeType },
content: { mimeType: item.mimeType, text: item.title },
},
})),
});
// Always check both success AND rejected — a call can partially fail.
const rejected = result.results?.rejected ?? [];
if (!result.success || rejected.length > 0) {
console.error(`[connector] setObjects batch ${batchNum} failed`, {
connectionId,
acceptedCount: result.results?.accepted?.length ?? 0,
rejectedCount: rejected.length,
rejected,
error: result.error,
});
} else {
console.log(`[connector] Batch ${batchNum}: ${result.results?.accepted?.length ?? batch.length} accepted, 0 rejected`);
}
}
}
オーケストレーションによる再取り込み(推奨)
ユーザーが更新スケジュールを選択した場合は、オーケストレーションを使用します。プラットフォームは、接続ごとに設定された周期で taskRunner を呼び出すため、外部スケジューラーは不要です。詳しくは、オーケストレーションの概念を参照してください。
タスク ID の生成用に uuid パッケージをインストールします。
npm install uuid
各 taskRunner の呼び出しで受け取る 3 つの ID
| ID | 有効期間 | 用途 |
|---|---|---|
taskId | ルートタスクの呼び出し間で一定 | TaskInfo の KVS キー |
scanId | ルートタスクのスケジュール実行ごとに 1 つ | 変更せずに updateTaskStatus に渡します。すべての子タスクにも引き継がれます |
taskExecutionId | 呼び出しの試行ごとに固有 | 変更せずに updateTaskStatus に渡します |
onConnectionChange — ルートタスクをスケジュールする
CREATED と UPDATED の両方で scheduleOrUpdateTask を呼び出します。同じ taskId を再利用すると、既存のスケジュールがその場で更新されます。
const { graph, types } = require('@forge/teamwork-graph');
const { kvs } = require('@forge/kvs');
const { v4: uuid } = require('uuid');
exports.onConnectionChangeHandler = async (request) => {
const { action, connectionId, configProperties } = request;
if (action === 'DELETED') {
await kvs.deleteSecret(connectionId);
await kvs.delete(`rootTaskId:${connectionId}`);
// TaskInfo rows for in-flight tasks are cleaned up here or in a maintenance pass.
return { success: true };
}
await kvs.setSecret(connectionId, configProperties);
// Mint once, reuse on UPDATED — same taskId makes scheduleOrUpdateTask an update.
let rootTaskId = await kvs.get(`rootTaskId:${connectionId}`);
if (!rootTaskId) {
rootTaskId = uuid();
await kvs.set(`rootTaskId:${connectionId}`, rootTaskId);
}
// Write TaskInfo BEFORE scheduling (taskRunner reads this to dispatch).
await kvs.set(`taskInfo:${rootTaskId}`, {
taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
connectionId,
createdAt: new Date().toISOString(),
});
const response = await graph.scheduleOrUpdateTask({
connectionId,
task: {
taskId: rootTaskId,
taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
scheduleInterval: { value: 1, timeUnit: 'day' }, // value: 1–60; timeUnit: minute|hour|day
},
});
// Branch on response.error — NOT response.success (orchestration responses don't set a boolean success field).
if (response.error) {
console.error('[connector] scheduleOrUpdateTask failed:', response.error);
return { success: false, message: response.error };
}
return { success: true };
};
taskRunner — 冪等性を保って取り込みを実行する
exports.taskRunner = async (request) => {
const { taskId, scanId, taskExecutionId, connectionId } = request;
// Read TaskInfo — dispatch key and idempotency state.
const taskInfo = await kvs.get(`taskInfo:${taskId}`);
if (!taskInfo) {
await graph.updateTaskStatus({
scanId, taskExecutionId, connectionId,
status: 'failure',
failureReason: 'ENTITY_NOT_FOUND',
task: { taskId },
});
return { success: false, message: 'Task not found' };
}
// Short-circuit duplicate deliveries — the platform delivers at-least-once.
if (taskInfo.completed) {
await graph.updateTaskStatus({ scanId, taskExecutionId, connectionId, status: 'success', task: { taskId } });
return { success: true, message: 'Already completed (duplicate delivery)' };
}
try {
const config = await kvs.getSecret(connectionId);
await ingestAllData(connectionId, config);
// Mark completed BEFORE updateTaskStatus so a crash between the two
// doesn't cause a duplicate invocation to re-run the full pipeline.
await kvs.set(`taskInfo:${taskId}`, { ...taskInfo, completed: true });
await graph.updateTaskStatus({ scanId, taskExecutionId, connectionId, status: 'success', task: { taskId } });
return { success: true };
} catch (err) {
console.error('[connector] taskRunner failed:', err);
await graph.updateTaskStatus({
scanId, taskExecutionId, connectionId,
status: 'failure',
failureReason: 'INTERNAL_ERROR',
task: { taskId },
});
return { success: false, message: err.message };
}
};
タスクタイプ(types.FORGE_TASK_TYPES)
taskType は値が限定された列挙型です。生の文字列ではなく、SDK 定数を使用してください。
const { types } = require('@forge/teamwork-graph');
types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL // full re-sync of objects
types.FORGE_TASK_TYPES.ENTITY_INGESTION_INCREMENTAL // delta sync (changes only)
types.FORGE_TASK_TYPES.USER_INGESTION_FULL
types.FORGE_TASK_TYPES.USER_INGESTION_INCREMENTAL
types.FORGE_TASK_TYPES.GROUP_INGESTION_FULL
types.FORGE_TASK_TYPES.GROUP_INGESTION_INCREMENTAL
types.FORGE_TASK_TYPES.RELATIONSHIP_INGESTION_FULL
types.FORGE_TASK_TYPES.RELATIONSHIP_INGESTION_INCREMENTAL
子タスクを使ったファンアウト(大規模データセット)
大規模なデータセットでは、ルートのtaskRunnerから各項目の子タスクにファンアウトします。子タスクには決定論的なUUID(uuidv5)を使用し、重複して配信された場合も同じTaskInfo行に解決されるようにします。
const { v5: uuidv5 } = require('uuid');
const APP_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // any stable UUID
// Inside taskRunner, after fetching item list:
for (const item of items) {
const childTaskId = uuidv5(`${scanId}:${taskId}:${item.id}`, APP_NAMESPACE);
await kvs.set(`taskInfo:${childTaskId}`, {
taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
connectionId,
createdAt: new Date().toISOString(),
metadata: { itemId: item.id },
});
const response = await graph.scheduleChildTask({
connectionId,
scanId, // must be the current scan's scanId
task: {
taskId: childTaskId,
taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
},
});
if (response.error) {
console.error('[connector] scheduleChildTask failed:', response.error);
}
}
スケジュールされた再取り込み(レガシー — オーケストレーションを推奨)
オーケストレーションを推奨(上のセクション)。ユーザーがプラットフォーム管理の定期同期を希望する場合に使用してください。
scheduledTriggerは、プラットフォームによるタスク追跡が不要なシンプルなcron形式のトリガーにのみ使用します。
基本的なスケジュールトリガーでデータを最新の状態に保つには、次のようにします。
# In manifest.yml — under modules:
scheduledTrigger:
- key: refresh-trigger
function: refresh-ingestion
interval: day # prefer 'day' or 'hour'; avoid 'fiveMinutes'
# Under function:
- key: refresh-ingestion
handler: index.refreshIngestionHandler
const { kvs } = require('@forge/kvs');
// Track active connections in onConnectionChangeHandler:
// await kvs.set('active-connections', [...activeConnections, connectionId]);
// await kvs.setSecret(connectionId, configProperties); // store credentials securely
exports.refreshIngestionHandler = async () => {
const activeConnections = await kvs.get('active-connections') ?? [];
for (const connectionId of activeConnections) {
const config = await kvs.getSecret(connectionId); // retrieve stored credentials
if (config) await ingestAllData(connectionId, config);
}
};
スクリプト
| スクリプト | スキルディレクトリ | 用途 |
|---|---|---|
scripts/scaffold_connector.py | skills/forge-connector/(このスキル) | 新しいコネクターアプリをひな形から作成します — manifest.yml、src/index.tsを生成し、SDKをインストールします。実行方法:python3 -m scripts.scaffold_connector |
scripts/deploy_forge_app.py | skills/forge-app-builder/(別のスキル) | Jiraにデプロイしてインストールします。forge-app-builderディレクトリから実行してください:python3 -m scripts.deploy_forge_app |
ひな形作成スクリプトはこのスキルのディレクトリにあります。デプロイスクリプトはforge-app-builderスキルのディレクトリにあります。実行前に必ずそこへcdするか、このSKILL.mdの場所からパスを導出してください。
開発のベストプラクティス
よくある問題を避けるため、次のプラクティスに従ってください。詳しくは、公式のコネクター要件をご覧ください。
取り込みエラーを無視しない
setObjects、setUsers、setGroupsからのレスポンスを必ず確認してください。呼び出し全体が失敗する場合だけでなく、一部の項目だけが拒否される場合もあります。一部のオブジェクトが拒否されていても、response.successがtrueになることがあります。
const response = await graph.setObjects({ connectionId, objects });
const rejected = response.results?.rejected ?? [];
if (!response.success || rejected.length > 0) {
console.error('Teamwork Graph object ingestion failed', {
connectionId,
acceptedCount: response.results?.accepted?.length ?? 0,
rejectedCount: rejected.length,
rejected,
error: response.error,
});
}
アクセス制御IDの整合性を保つ
オブジェクトのpermissionsでUSERまたはGROUPプリンシパルを使用する場合は、まずgraph.setUsers()/graph.setGroups()で対応するユーザーまたはグループを取り込んでください。accessControlsのidは、それらの呼び出しに渡すexternalIdと完全に同じ文字列でなければなりません。IDが一致しないと、本来アクセス権を持つユーザーからオブジェクトが気付かないうちに見えなくなります。
// Ingest user with externalId 'user-42'
await graph.setUsers({ connectionId, users: [{ externalId: 'user-42', displayName: 'Alex Chen', emails: [{ value: '[email protected]', primary: true }] }] });
// Use the same ID 'user-42' in accessControls — NOT 'accountId:user-42' or any other variant
await graph.setObjects({
connectionId,
objects: [{
// ...
permissions: [{ accessControls: [{ principals: [{ type: 'USER', id: 'user-42' }] }] }],
}],
});
ケイパビリティを宣言する
graph:connectorモジュールにcapabilitiesブロックを追加してください。これがないと、管理者には設定画面に「開発者によってケイパビリティが宣言されていません」と表示され、信頼性が損なわれます。コネクターの動作を正確に反映した値を設定してください。
| プロパティ | 型 | 値 | 説明 |
|---|---|---|---|
replicatesPermissions | boolean | true / false | ソースシステムのACLをミラーリングするかどうかです。falseの場合、取り込まれたデータはワークスペース内のすべてのユーザーに表示されます。 |
syncFidelity | enum | append / upsert / mirror | mirrorを推奨します。新しいオブジェクト、更新、削除を同期します。 |
supportsIncrementalSync | boolean | true / false | 前回の実行以降の変更のみをコネクターが同期するかどうかです。 |
データ削除の責任
| アプローチ | 動作 | 要件 |
|---|---|---|
| ミラー(推奨) | ソースでの削除が同期されます — Teamwork Graph は常に最新の状態を反映します | 削除の伝播を実装します |
| 切断のみ | 削除は同期されません — 管理者が切断するまでデータは保持されます | この制限をコネクターのドキュメントと Marketplace の掲載ページで目立つように説明します |
管理者が切断すると、Atlassian は取り込まれたすべてのデータを自動的に削除します。action: DELETED を指定した onConnectionChange ハンドラーでは、アプリ側のリソース(同期ジョブの停止、Webhook の削除、保存済み認証情報の削除)のクリーンアップのみが必要です。
トラブルシューティング
| 問題 | 対処方法 |
|---|---|
マニフェストで graph:connector が認識されない | forge lint を実行してください。エラーの原因となっているフィールドが正確に特定されます |
TypeError: Cannot destructure property 'config' of 'event.payload' | ハンドラーが event.payload.config を使用しています。request.configProperties に変更してください。Forge はリクエストを直接渡し、event.payload の下にはネストしません |
TypeError: Cannot read properties of undefined (reading 'set') | @forge/storage の storage を使用しています。@forge/kvs の kvs に切り替えてください |
graph.setObjects is not a function | インポートが誤っています。const { graph } = require('@forge/teamwork-graph') を使用し、graph.setObjects({ objects, connectionId }) を呼び出してください |
forge lint:スコープ read/write:graph:teamwork が無効 | read:object:jira、write:object:jira、delete:object:jira に置き換えてください |
forge lint:document should NOT have additional property 'function' | function: がトップレベルにあります。modules: の中に移動してください |
forge lint:formConfiguration must have required property 'form' | fields:/beforeYouBegin: を form: [{ type: header, properties: [...] }] に置き換えてください |
forge lint の警告:非推奨の egress エントリー | forge lint --fix を実行して、URL 文字列のみの形式を { address: 'url' } に自動変換してください |
forge developer-spaces list コマンドが見つからない | Forge CLI 12.x には存在しません。ユーザーに forge create を対話形式で実行し、developer space を選択してもらってください |
forge create が非 TTY エラーで失敗する | forge create には対話型ターミナルが必要です。ユーザーに実行を依頼し、その後、作成されたディレクトリにマニフェストとソースファイルを書き込んでください |
onConnectionChange がトリガーされない | 管理者が Atlassian Administration → Connected apps で「Connect」をクリックしたことを確認してください。forge tunnel を実行して関数が呼び出されることを確認してください |
| Rovo Search にオブジェクトが表示されない | インデックス作成に約 5 分かかるため、しばらくお待ちください。forge logs -e development --since 15m を実行して setObjects のエラーを確認してください |
setObjects が success: true を返すのに、Rovo にオブジェクトが表示されない | response.results?.rejected を確認してください。呼び出し全体が成功していても、個々のアイテムが拒否されることがあります。アイテムごとのエラー詳細を確認するには、rejected 配列をログに記録してください |
| アクセス権を持つユーザーに Rovo Search のオブジェクトが表示されない | accessControls の principal の id が、graph.setUsers()/graph.setGroups() で使用した externalId と完全に一致することを確認してください。ID が一致しない場合(例:user-42 と accountId:user-42)、アクセスに関する問題が通知されないまま発生します |
@forge/teamwork-graph の呼び出しで 403 が発生する | read:object:jira、write:object:jira、delete:object:jira がマニフェストのスコープに含まれていることを確認し、その後、再デプロイして forge install --upgrade を実行してください |
forge login が必要 | https://id.atlassian.com/manage/api-tokens で API トークンを作成し、forge login を実行してください |
scheduleOrUpdateTask が { status: 'ACCEPTED' } を返すのに、コードでは失敗と判定される | response.success ではなく response.error に基づいて分岐してください。オーケストレーションの成功レスポンスには、ブール値の success フィールドが設定されません |
SDK から task.taskType must be one of: ... が返される | taskType は選択肢が限定された列挙型です。@forge/teamwork-graph の types.FORGE_TASK_TYPES.* 定数を使用してください。任意の文字列はクライアント側で拒否されます |
task.taskId must be a valid UUID format | ルートタスクには uuid()(v4)、子タスクには uuidv5(...) を使用してください。"my-app:sync" のような自由形式の文字列は拒否されます |
Failed to schedule child tasks: Not Found - Scan not found | scheduleChildTask に渡した scanId がアクティブなスキャンと一致していません。アクティブな taskRunner の実行中に呼び出し、request.scanId を変更せずに渡していることを確認してください |
実行が成功した後に taskRunner が ENTITY_NOT_FOUND を報告する | TaskInfo を早く削除しすぎています。レコードを削除する代わりに、completed: true フラグを使用して重複配信を早期に処理してください |
taskRunner の taskMetadata が空 | scheduleChildTask の temporalTaskMetadata/persistentTaskMetadata は、呼び出し元に確実に引き継がれるわけではありません。ハンドラーに必要な情報はすべて、TaskInfo.metadata を介して KVS に保存してください |
scheduleInterval.value が範囲外というエラー | 値は 1 から 60 の間である必要があります。値を調整して再デプロイしてください |
名前とロゴに関するガイドライン
- コネクター名には公式サービス名を使用します(例:
Drive Connector by AcmeではなくGoogle Drive) - アイコンには公式サービスのロゴを使用します。独自のブランド要素を追加したり、組み合わせたりしないでください
- このガイドラインは
graph:connectorモジュールにのみ適用されます。Forgeアプリ自体には独自のブランド要素を使用できます