@takagaki/cortex-decisions-viewer 0.12.119 → 0.12.121

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/build.js CHANGED
@@ -45,6 +45,7 @@ exports.humanTouchedOf = humanTouchedOf;
45
45
  exports.attachHumanTouched = attachHumanTouched;
46
46
  exports.rawFrontmatterDate = rawFrontmatterDate;
47
47
  exports.attachSupersedes = attachSupersedes;
48
+ exports.attachRetractedBy = attachRetractedBy;
48
49
  exports.readGoldProposals = readGoldProposals;
49
50
  exports.buildHomeGrounding = buildHomeGrounding;
50
51
  exports.collectMeetings = collectMeetings;
@@ -68,10 +69,12 @@ const marked_1 = require("marked");
68
69
  const sanitize_html_1 = __importDefault(require("sanitize-html"));
69
70
  const canonical_evidence_1 = require("./canonical-evidence");
70
71
  const evidence_names_1 = require("./evidence-names");
72
+ const change_log_1 = require("./change-log");
71
73
  const repo_1 = require("./repo");
72
74
  const engagement_1 = require("./engagement");
73
75
  const story_1 = require("./story");
74
76
  const ranking_1 = require("./ranking");
77
+ const hands_1 = require("./hands");
75
78
  const rederivation_1 = require("./rederivation");
76
79
  // 直近のGold昇格で追加されたレコードのパス集合。レコード生成の各所から参照するのでモジュールスコープに置く
77
80
  // (引数で引き回すと呼び出し側の改修が広範囲になる)。build() の冒頭で1回だけ解決する。
@@ -826,6 +829,35 @@ function attachSupersedes(decisions, rawDateOf = (d) => d.fm.date, pathOf = () =
826
829
  d.supersedesIds = replaced;
827
830
  }
828
831
  }
832
+ /**
833
+ * ルールに取り下げ(`relations[retracted_by]`・cortex-engine #1094 便 4-2d)を載せる。
834
+ *
835
+ * 取り下げ=この線が 1 本以上(決定の「置き換えられた」と同じ導出・engine の目次と同じ)。
836
+ * target は取り下げを決めた決定の ID。実在する決定なら記録の綴りと決定日を添える(無ければ ID のまま・日付は空)。
837
+ * **取り下げの無いルール・ルール以外の区画にはキーごと付けない**(今までと同じ SiteData)
838
+ */
839
+ function attachRetractedBy(records, decisions, rawDateOf = (d) => d.date) {
840
+ const byKey = new Map();
841
+ for (const d of decisions) {
842
+ const k = (0, supersedes_1.decisionKeyOf)(d.id);
843
+ if (!byKey.has(k))
844
+ byKey.set(k, d);
845
+ }
846
+ for (const r of records) {
847
+ const out = [];
848
+ for (const rel of r.relations) {
849
+ if (rel.rel !== "retracted_by")
850
+ continue;
851
+ const k = (0, supersedes_1.decisionKeyOf)(rel.target);
852
+ if (!k || out.some((x) => (0, supersedes_1.decisionKeyOf)(x.id) === k))
853
+ continue;
854
+ const d = byKey.get(k);
855
+ out.push({ id: d ? d.id : k, date: d ? (0, supersedes_1.decisionDateOf)(rawDateOf(d), d.id) : "" });
856
+ }
857
+ if (out.length)
858
+ r.retractedBy = out;
859
+ }
860
+ }
829
861
  /**
830
862
  * AIからの更新提案(`.cortex/gold-proposals.json`)を読む。
831
863
  *
@@ -2366,7 +2398,7 @@ async function build(opts) {
2366
2398
  // 解決するので、あとから作ると空の表・空の実在判定が渡り、**実在するパスまで
2367
2399
  // 「リンクにしない」に倒れて全滅する**(以前は順序が逆だった)。
2368
2400
  const repoRoot = knowledgeRoot ? path.dirname(knowledgeRoot) : path.dirname(dirAbs);
2369
- // 帳簿 9 本の置き場(既定は `<repoRoot>/.cortex`)。読み口はこの 1 つから読む
2401
+ // 帳簿 10 本の置き場(既定は `<repoRoot>/.cortex`)。読み口はこの 1 つから読む
2370
2402
  const ledgerDir = (0, ledger_dir_1.ledgerDirOf)(repoRoot, opts.ledgerDir);
2371
2403
  const targetUrls = knowledgeRoot
2372
2404
  ? await buildTargetUrlMap(repoRoot, repo.baseUrl, repo.branch)
@@ -2394,6 +2426,8 @@ async function build(opts) {
2394
2426
  const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot, ledgerDir);
2395
2427
  // 目次と同じ順位(engine #551)。**読めないなら `null`** で、つながり画面の点は今までどおり一律
2396
2428
  const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot, ledgerDir);
2429
+ // 人の手の表の写し(engine #1094 q-10)。あれば記憶 v2 の案件で、確認の印とボタンを出さない。**無い案件は今と 1 バイトも同じ**(キーごと無い)
2430
+ const memoryV2 = await (0, hands_1.readMemoryV2)(repoRoot, ledgerDir);
2397
2431
  // 記録の id → リポジトリルート相対のパス(順位のファイルの鍵と突き合わせる。`repoRoot` 起点・cwd 起点にしない)
2398
2432
  const recordRelPaths = new Map();
2399
2433
  const relOf = (dir, fileName) => {
@@ -2457,6 +2491,9 @@ async function build(opts) {
2457
2491
  const records = await loadGenericRecords(recordsDir, exclude, repo.baseUrl, repo.branch);
2458
2492
  // 人の手(削除の確認の文面を替える材料・cortex-engine #1094 段 4)
2459
2493
  attachHumanTouched(records, recordsDir, repoRoot, ledgers.confirmation);
2494
+ // ルールの取り下げ(`retracted_by`・cortex-engine #1094 便 4-2d)。ルールの区画だけ・取り下げの無い案件では何も付かない
2495
+ if (e.name === "Rules")
2496
+ attachRetractedBy(records, decisions, (d) => rawDates.get(d) ?? d.fm.date);
2460
2497
  // 用語集の並び(活性度・強さ)。**帳簿を持たない案件では並びもそのまま**
2461
2498
  const rederived = orderRecordsByLedgers(records, recordsDir, rederive, opts.generatedAt);
2462
2499
  // 関係者だけ、関与の多い順(engine の目次と同じ `order`・便 14b)。用語集の並び
@@ -2518,11 +2555,9 @@ async function build(opts) {
2518
2555
  // すでにある会議(投入モーダルの「会議名」の選択肢)。intake の有無に関わらず載せる——
2519
2556
  // 出し分けは表示側の仕事で、ここで絞ると「投入の口を後から付けたら選択肢が空」になる
2520
2557
  const meetings = await collectMeetings(repoRoot);
2521
- // AIによるレコードの変更履歴を git から取る。**レコード数に関わらず git は2回だけ呼ぶ**。
2522
- // git が無い・浅い clone・該当0件なら空で、履歴の節が出ないだけ(他の表示は壊れない)。
2523
- // 走るのは latestGeneratedPaths と同じ **リポジトリルート**(process.cwd())——
2524
- // Decisions ディレクトリで実行すると pathspec に一致せず常に空になる
2525
- const aiHistory = (0, repo_1.collectAiChangeHistory)(process.cwd());
2558
+ // 記録ページの「更新履歴」。engine が毎晩書く帳簿(ledger-dir の gold-change-log.json)だけを読む(git は読まない)。
2559
+ // 無い・壊れ・版違いは null で、画面は「更新履歴はまだありません」の 1 つの形になる
2560
+ const changeLog = await (0, change_log_1.readChangeLog)(repoRoot, ledgerDir);
2526
2561
  // ストーリー(#901 V2-5)の日本時間の今日。日・週・月の3つで同じ日にそろえるため1回だけ計算する
2527
2562
  const storyToday = (0, story_1.todayJst)(new Date(opts.generatedAt));
2528
2563
  // ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
@@ -2545,6 +2580,7 @@ async function build(opts) {
2545
2580
  home,
2546
2581
  homeGrounding: homeGrounding,
2547
2582
  homeUpdatedAt: homeUpdatedAt,
2583
+ ...(memoryV2 ? { memoryV2: true } : {}),
2548
2584
  sections,
2549
2585
  graph,
2550
2586
  categories,
@@ -2587,7 +2623,7 @@ async function build(opts) {
2587
2623
  // null(無い・壊れ・version違い)は undefined へ落とす——SiteData の
2588
2624
  // 「知らないことはキーが無いことで表す」に合わせる(空の器を通さない)
2589
2625
  // 空の器は通さない(0件なら undefined = キーごと出さない)
2590
- aiHistory: Object.keys(aiHistory).length ? aiHistory : undefined,
2626
+ changeLog: changeLog ?? undefined,
2591
2627
  // 載せる日が無い案件(帳簿なし・壊れ・shadow の日だけ・ファイルなし)はキーごと出さない
2592
2628
  story: story ?? undefined,
2593
2629
  storyDigest: storyDigest ?? undefined,
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CHANGE_LOG_MAX_ROWS = exports.CHANGE_LOG_VERSION = exports.CHANGE_LOG_FILE = void 0;
7
+ exports.isCleanUrl = isCleanUrl;
8
+ exports.jstDay = jstDay;
9
+ exports.parseChangeLog = parseChangeLog;
10
+ exports.readChangeLog = readChangeLog;
11
+ /**
12
+ * 記録ページの「更新履歴」の材料(cortex-engine #1094 便 35b-1 U2・設計 D3)。
13
+ *
14
+ * 読むのは engine が毎晩書く帳簿 `gold-change-log.json`(ledger-dir の下)だけ。git は読まない。
15
+ * 形の契約は engine `test/fixtures/change-log-contract.json`(tools の `viewer/test/fixtures/` へ配られる)。
16
+ *
17
+ * ## 🔴 読めなければ空(縮退は 1 つの形)
18
+ *
19
+ * 帳簿が無い・壊れ・版違い・形違いは `null`(画面は「更新履歴はまだありません」。今の欄には戻さない)。
20
+ * 1 行でも契約に反すれば全体を捨てる(engine は書く前に同じ基準で検査するので、viewer を engine より厳しくしない)。
21
+ *
22
+ * ## 🔴 禁止語の表は写さない
23
+ *
24
+ * 機械の語(frontmatter・commit …)を `what` に出さない番人は engine の持ち物(`hasForbiddenTerm`)。
25
+ * 写すと表が 2 つになるので、ここは**構造・表示の安全・メールの形・Operated-By**だけを見る。
26
+ *
27
+ * ## 日付
28
+ *
29
+ * `at` は git の author date(作者のオフセット付き)。engine は同じ日のまとめを JST で行うので、画面の日付も
30
+ * **固定 +9 時間の UTC 算術**で JST に直す(閲覧者のタイムゾーンに依存しない・Date の地域表示は使わない)。
31
+ */
32
+ const promises_1 = __importDefault(require("node:fs/promises"));
33
+ const node_path_1 = __importDefault(require("node:path"));
34
+ const ledger_dir_1 = require("./ledger-dir");
35
+ /** 置き場(engine `scripts/gold/change-log.mjs` の `CHANGE_LOG_PATH`) */
36
+ exports.CHANGE_LOG_FILE = "gold-change-log.json";
37
+ /** 形の版(engine の `CHANGE_LOG_VERSION`。違ったら読まない) */
38
+ exports.CHANGE_LOG_VERSION = 1;
39
+ /** 1 記録で画面に渡す行数の上限(窓の `per_record` と同じ 50) */
40
+ exports.CHANGE_LOG_MAX_ROWS = 50;
41
+ /** `why` は 1 行 3 本まで(engine の `WHY_MAX`) */
42
+ const WHY_MAX = 3;
43
+ /** 載る記録のパス(4 区画の記録。README・雛形・入れ子は載らない) */
44
+ const RECORD_PATH = /^Cortex\/(Decisions|Glossary|Members|Rules)\/records\/[^/]+\.md$/;
45
+ const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
46
+ /** メールの形(URL の部分を除いて見る) */
47
+ // engine `change-log.mjs` の定義と同じ(緩めると、URL の後ろのメールや非 ASCII のローカル部が通る)
48
+ const URL_IN_TEXT_RE = /https?:\/\/[^\s"'<>)\]]+/g;
49
+ const EMAIL_RE = /[^\s@/:]+@[^\s@/:]+\.[^\s@/:]+/;
50
+ const hasEmailShape = (text) => EMAIL_RE.test(text.replace(URL_IN_TEXT_RE, " "));
51
+ /**
52
+ * `why.url` の検査(engine `cleanUrl` と同じ述語)。http(s) だけ・userinfo なし・クエリとフラグメントなし・
53
+ * 正規化しても同じ文字列。`javascript:` や `mailto:` は通らない
54
+ */
55
+ function isCleanUrl(raw) {
56
+ if (typeof raw !== "string")
57
+ return false;
58
+ const s = raw.trim().replace(/[.,;:!?)\]>"'、。)」』]+$/u, "");
59
+ if (!/^https?:\/\//i.test(s))
60
+ return false;
61
+ let u;
62
+ try {
63
+ u = new URL(s);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ if (!/^https?:$/.test(u.protocol))
69
+ return false;
70
+ if (u.username || u.password)
71
+ return false;
72
+ u.search = "";
73
+ u.hash = "";
74
+ return u.toString() === raw;
75
+ }
76
+ /** 日本時間の `YYYY/M/D`。読めない値は `null` */
77
+ function jstDay(at) {
78
+ const t = Date.parse(at);
79
+ if (!Number.isFinite(t))
80
+ return null;
81
+ const d = new Date(t + 9 * 3600 * 1000);
82
+ return `${d.getUTCFullYear()}/${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
83
+ }
84
+ const ROW_KEYS = ["at", "by", "what", "why", "commit"];
85
+ const BY_KINDS = ["member", "night", "unknown"];
86
+ const norm = (p) => p.normalize("NFC");
87
+ /** 中身を読む(投げない)。契約に 1 つでも反すれば `null` */
88
+ function parseChangeLog(text) {
89
+ let value;
90
+ try {
91
+ value = JSON.parse(text);
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ if (!isObj(value) || value.version !== exports.CHANGE_LOG_VERSION)
97
+ return null;
98
+ if (Object.keys(value).some((k) => !["version", "window", "records"].includes(k)))
99
+ return null;
100
+ const w = value.window;
101
+ if (!isObj(w) || !Number.isInteger(w.days) || w.days <= 0 || !Number.isInteger(w.per_record) || w.per_record <= 0)
102
+ return null;
103
+ if (!isObj(value.records))
104
+ return null;
105
+ const rows = Object.create(null);
106
+ let seq = 0;
107
+ for (const rec of Object.values(value.records)) {
108
+ if (!isObj(rec))
109
+ return null;
110
+ if (Object.keys(rec).some((k) => k !== "path" && k !== "rows"))
111
+ return null;
112
+ if (typeof rec.path !== "string" || !rec.path || !Array.isArray(rec.rows))
113
+ return null;
114
+ const recPath = norm(rec.path);
115
+ const out = [];
116
+ for (const r of rec.rows) {
117
+ if (!isObj(r))
118
+ return null;
119
+ if (Object.keys(r).some((k) => !ROW_KEYS.includes(k)))
120
+ return null;
121
+ if (typeof r.at !== "string")
122
+ return null;
123
+ const t = Date.parse(r.at);
124
+ const day = jstDay(r.at);
125
+ if (!Number.isFinite(t) || !day)
126
+ return null;
127
+ const by = r.by;
128
+ if (!isObj(by) || typeof by.kind !== "string" || !BY_KINDS.includes(by.kind))
129
+ return null;
130
+ if (Object.keys(by).some((k) => k !== "kind" && k !== "member"))
131
+ return null;
132
+ if (by.kind === "member" && (typeof by.member !== "string" || !by.member))
133
+ return null;
134
+ if (by.kind !== "member" && "member" in by)
135
+ return null;
136
+ if (typeof r.what !== "string" || !r.what)
137
+ return null;
138
+ if (!/^[0-9a-f]{7,64}$/.test(String(r.commit ?? "")))
139
+ return null;
140
+ if (!Array.isArray(r.why) || r.why.length > WHY_MAX)
141
+ return null;
142
+ const why = [];
143
+ for (const x of r.why) {
144
+ if (!isObj(x) || Object.keys(x).some((k) => k !== "label" && k !== "url"))
145
+ return null;
146
+ if (typeof x.label !== "string" || !x.label || !isCleanUrl(x.url))
147
+ return null;
148
+ why.push({ label: x.label, url: x.url });
149
+ }
150
+ const row = {
151
+ day, who: by.kind, what: r.what, why, commit: r.commit, _t: t, _i: seq++,
152
+ };
153
+ if (by.kind === "member")
154
+ row.member = by.member;
155
+ out.push(row);
156
+ }
157
+ // 記録の鍵(id)の綴りは走査しない。載る記録のパスの形でないものは捨てる(__proto__ などの鍵も入らない)
158
+ if (!RECORD_PATH.test(recPath))
159
+ continue;
160
+ (rows[recPath] ||= []).push(...out);
161
+ }
162
+ // メール・Operated-By は行の中のどこにも持たない(URL の部分を除いたメールの形も)
163
+ const rowsText = JSON.stringify([value.window, ...Object.values(value.records).map((r) => (isObj(r) ? r.rows : null))]);
164
+ if (hasEmailShape(rowsText) || /operated-by/i.test(rowsText))
165
+ return null;
166
+ const result = { windowDays: w.days, rows: {} };
167
+ for (const [p, list] of Object.entries(rows)) {
168
+ // 新しい順(時刻の数値で・同じ時刻は元の並びのまま)→ 50 行で切る
169
+ list.sort((a, b) => b._t - a._t || a._i - b._i);
170
+ result.rows[p] = list.slice(0, exports.CHANGE_LOG_MAX_ROWS).map(({ _t, _i, ...row }) => row);
171
+ }
172
+ return result;
173
+ }
174
+ /** 案件リポから読む。無い → `null`(黙る)・壊れている → `null`(build のログに 1 行) */
175
+ async function readChangeLog(repoRoot, ledgerDir) {
176
+ let text;
177
+ try {
178
+ text = await promises_1.default.readFile(node_path_1.default.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), exports.CHANGE_LOG_FILE), "utf8");
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ const parsed = parseChangeLog(text);
184
+ if (!parsed)
185
+ console.warn(`${exports.CHANGE_LOG_FILE}: 読めないので、記録ページの更新履歴は「まだありません」と出します`);
186
+ return parsed;
187
+ }
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ program
22
22
  .option("--secret-kinds <kinds>", "鍵の入力欄を出す連携(カンマ区切り。例: figma,backlog)")
23
23
  .option("--webhook-url <url>", "Backlog Webhook 受信のLambda URL(環境変数 WEBHOOK_URL でも可)")
24
24
  .option("--webhook-token <token>", "リアルタイム同期の**案件別**トークン(環境変数 WEBHOOK_TOKEN でも可。艦隊共通トークンを渡さないこと)")
25
- .option("--ledger-dir <dir>", "ビューアが読む帳簿(.cortex の 9 本)の置き場(未指定なら <リポジトリルート>/.cortex)")
25
+ .option("--ledger-dir <dir>", "ビューアが読む帳簿(.cortex の 10 本)の置き場(未指定なら <リポジトリルート>/.cortex)")
26
26
  .action(async (dir, opts) => {
27
27
  try {
28
28
  const result = await (0, build_1.build)({
package/dist/hands.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MEMORY_HANDS_VERSION = exports.MEMORY_HANDS_FILE = void 0;
7
+ exports.isMemoryHandsCopy = isMemoryHandsCopy;
8
+ exports.readMemoryV2 = readMemoryV2;
9
+ /**
10
+ * 人の手の表の写し(`.cortex/memory-hands.json`)を読む(cortex-engine #1094 q-10・仕様 1-6)。
11
+ *
12
+ * 仕様 1-6: **表があること自体が、その案件の記憶 v2 の印**。engine が栓 `human_touch_lock` が on の晩に、表が読めた晩だけ
13
+ * 目次と同じコミットで置く(engine `scripts/memory-hands-copy.mjs` の `HANDS_COPY_PATH`・`handsCopyText`)。ここは**印があるかを読むだけ**で、
14
+ * 記録ごとの欄(lock・touched …)はまだ画面の判定に使わない(残りは段 5・仕様の差 77)。
15
+ *
16
+ * v2 の案件では、画面から「AI生成・未確認」の札・警告帯・[確認済みにする]・「未確認のみ」を出さない(削除は残す)。
17
+ * 読めない・無い・版が違う案件は v2 でないものとして今のまま(`SiteData.memoryV2` はキーごと無い)。
18
+ */
19
+ const promises_1 = __importDefault(require("node:fs/promises"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const ledger_dir_1 = require("./ledger-dir");
22
+ /** 置き場(engine `scripts/memory-hands-copy.mjs` の `HANDS_COPY_PATH` の `.cortex/` の下) */
23
+ exports.MEMORY_HANDS_FILE = "memory-hands.json";
24
+ /** 形の版(engine の `HANDS_COPY_VERSION`。違ったら v2 の印と読まない) */
25
+ exports.MEMORY_HANDS_VERSION = 1;
26
+ /** 中身が人の手の表の写しの形か(投げない)。版が違う・形が違う・壊れている → `false` */
27
+ function isMemoryHandsCopy(text) {
28
+ try {
29
+ const parsed = JSON.parse(text);
30
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
31
+ return false;
32
+ const o = parsed;
33
+ return o.version === exports.MEMORY_HANDS_VERSION && !!o.records && typeof o.records === "object" && !Array.isArray(o.records);
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ /** 案件リポに写しがあり、読める形か。無い → `false`(黙る)・壊れている → `false`(build のログに 1 行) */
40
+ async function readMemoryV2(repoRoot, ledgerDir) {
41
+ let text;
42
+ try {
43
+ text = await promises_1.default.readFile(node_path_1.default.join((0, ledger_dir_1.ledgerDirOf)(repoRoot, ledgerDir), exports.MEMORY_HANDS_FILE), "utf8");
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ const ok = isMemoryHandsCopy(text);
49
+ if (!ok)
50
+ console.warn(`${exports.MEMORY_HANDS_FILE}: 読めないので、この案件は記憶 v2 でないものとして出します`);
51
+ return ok;
52
+ }
@@ -7,7 +7,7 @@ exports.DEFAULT_LEDGER_DIR_NAME = void 0;
7
7
  exports.defaultLedgerDir = defaultLedgerDir;
8
8
  exports.ledgerDirOf = ledgerDirOf;
9
9
  /**
10
- * ビューアが読む帳簿(9 本)の置き場(cortex-engine #1094 便 3-11 の tools(T1))。
10
+ * ビューアが読む帳簿(10 本)の置き場(cortex-engine #1094 便 3-11 の tools(T1))。
11
11
  *
12
12
  * 既定は `<リポジトリルート>/.cortex`(今までと同じ)。`--ledger-dir` で別のフォルダを指せる。
13
13
  * ビューアは保管庫(S3)も栓も知らない(AWS SDK を足さない)。どこから持ってくるかは呼び出し側(workflow)の仕事で、