@takagaki/cortex-decisions-viewer 0.12.68 → 0.12.69

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.
Files changed (2) hide show
  1. package/dist/render.js +320 -16
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -17,6 +17,9 @@ exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
17
17
  exports.sanitizeIngestCounts = sanitizeIngestCounts;
18
18
  exports.sanitizeIngestFailures = sanitizeIngestFailures;
19
19
  exports.sanitizeStateNow = sanitizeStateNow;
20
+ exports.devExcludedSkip = devExcludedSkip;
21
+ exports.devExcludedRefs = devExcludedRefs;
22
+ exports.devExcludedLabel = devExcludedLabel;
20
23
  exports.overlayStateOnFleet = overlayStateOnFleet;
21
24
  exports.chatRowsToAdd = chatRowsToAdd;
22
25
  exports.gchatRowsToAdd = gchatRowsToAdd;
@@ -554,8 +557,9 @@ const TOOL_KIND_ICONS = {
554
557
  "google-meet": "🎥",
555
558
  };
556
559
  /**
557
- * 状態の表示語と色。**4 語+「登録状況を確認中です」しか出さない**(読み取り中/承認待ち/
558
- * 停止中/未確認、そして登録簿にしか無くまだ測っていない行の「確認中」・#581)。
560
+ * 状態の表示語と色。**5 語+「登録状況を確認中です」しか出さない**(読み取り中/
561
+ * 接続OK(読み取りの対象外)/承認待ち/停止中/未確認、そして登録簿にしか無く
562
+ * まだ測っていない行の「確認中」・#581 / #640)。
559
563
  *
560
564
  * 色は既存の `ds-gate-*` を流用する——同じ画面で「緑=読めている・橙=人の操作待ち・
561
565
  * 灰=分からない」の意味を 2 通りに増やさない。
@@ -568,6 +572,13 @@ const TOOL_KIND_ICONS = {
568
572
  function toolStateLabel(state) {
569
573
  if (state === "reading")
570
574
  return { text: "読み取り中", cls: "ds-gate-ok" };
575
+ // 届いてはいるが、Gold の材料にしていない(#640・engine #399)。**「読み取り中」と
576
+ // 言わない**——読みに行かないと決めた接続先なので、言えば画面が嘘をつく。
577
+ // **警告色にもしない**(`ds-gate-ok` のまま)——直すものが無い中立の事実で、橙にすると
578
+ // 直しに行かせる。状態名を `"excluded"` にしないのは、`liveToolItems` の中で
579
+ // `match.excluded`(登録簿の devExcluded =「停止中」)が別の意味で使われているため
580
+ if (state === "ok_not_ingested")
581
+ return { text: "接続OK(読み取りの対象外)", cls: "ds-gate-ok" };
571
582
  if (state === "waiting")
572
583
  return { text: "承認待ち", cls: "ds-gate-warn" };
573
584
  // 止めているものは警告色にしない(直す必要が無いので、橙にすると直しに行かせてしまう)
@@ -656,11 +667,13 @@ function regRowOfToolItem(item) {
656
667
  * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
657
668
  */
658
669
  function toolsFoldSummary(states) {
659
- // 表示の順は状態の語と同じ(読み取り中→承認待ち→停止中→未確認→登録状況を確認中です)
660
- var order = ["reading", "waiting", "stopped", "unknown", "pending"];
661
- var count = { reading: 0, waiting: 0, stopped: 0, unknown: 0, pending: 0 };
670
+ // 表示の順は状態の語と同じ(読み取り中→接続OK(読み取りの対象外)→承認待ち→停止中→
671
+ // 未確認→登録状況を確認中です)。🔴 **新しい語をここへ足し忘れると、下の `order.indexOf`
672
+ // `unknown` へ倒すので、畳んだ 1 行だけが「未確認」と言う**(開くと違う語が出る・#640)
673
+ var order = ["reading", "ok_not_ingested", "waiting", "stopped", "unknown", "pending"];
674
+ var count = { reading: 0, ok_not_ingested: 0, waiting: 0, stopped: 0, unknown: 0, pending: 0 };
662
675
  (states || []).forEach(function (s) {
663
- // ⚠ 4 語のどれかに当たるかは **order で見る**(素のオブジェクトの添字だと
676
+ // ⚠ 5 語のどれかに当たるかは **order で見る**(素のオブジェクトの添字だと
664
677
  // "constructor" のような語が継承プロパティに当たって、知らない語が素通りする)
665
678
  var k = String(s == null ? "" : s);
666
679
  count[order.indexOf(k) < 0 ? "unknown" : k] += 1;
@@ -1172,6 +1185,145 @@ function sanitizeStateNow(v) {
1172
1185
  }
1173
1186
  return out.length ? out : null;
1174
1187
  }
1188
+ /**
1189
+ * その行が「接続はできるが、Gold の材料にしていない」**中立**の事実か(#640・engine #399)。
1190
+ *
1191
+ * 夜間の Gold 昇格は、性質が「作業物」で取得ごと省いたソースにも到達性を 1 回書く
1192
+ * (engine #399・PR #405)。同じ ref に 2 行が並ぶ:
1193
+ *
1194
+ * ```
1195
+ * SK: dev##<owner>/<repo>#reachable status: ok by: gold
1196
+ * SK: dev##<owner>/<repo>#ingested status: skipped by: gold
1197
+ * detail: 性質が作業物のため Gold の材料にしません
1198
+ * ```
1199
+ *
1200
+ * 🔴 **「鍵が無くて取り込みを始めていない」`skipped` と混ぜない(#225)。** どちらも同じ語だが、
1201
+ * 利用者が取るべき行動が正反対で、こちらは**何もしなくてよい**——警告(橙)に落とすと、
1202
+ * GitHub を登録した全案件が「直すものがある」と言われる(engine #399 実装レビュー🟡3)。
1203
+ * 見分ける材料は書き手(`by: "gold"`)で、理由文は engine が `by` の綴りを変えた日の保険。
1204
+ *
1205
+ * 🔴 **cap=`dev` で門を掛けるのが、「開発以外は 1 バイトも変わらない」の実体。**
1206
+ * 課題管理・デザインの `skipped`(=鍵未登録)はこの関数に一度も当たらない。他の能力にも
1207
+ * 「読まないと決めた」が生まれたら、そのときに cap を足す(先回りして広げない——
1208
+ * 広げた先の `skipped` が鍵未登録かどうかを、ここでは確かめられない)。
1209
+ *
1210
+ * ⚠ **チャットには同じ穴が残っている(この便では直さない・実装レビュー🟡8)。** engine の
1211
+ * 門(`external-sources.sh`)は GitHub 専用ではなく、`nature: 作業物` を宣言した Slack
1212
+ * チャンネル / Google Chat スペースにも `chat##…#ingested: skipped` を書く。ここは
1213
+ * `cap !== "dev"` で弾くので、その接続先の Home は #640 以前と**まったく同じ嘘**
1214
+ * (「読み取り中」)を言う。広げるときは、チャットの `skipped` に鍵未登録の書き手が
1215
+ * いないことを先に確かめる。
1216
+ *
1217
+ * ⚠ **`goldState: "excluded"`(行の「読み取り停止中」)とも別物。** あちらは人が
1218
+ * `dev-sources.json` の exclude に書いて止めたもので、こちらはソースの性質で門が省いたもの。
1219
+ *
1220
+ * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す。
1221
+ * 正規表現も持たない——文字列の一致だけで足りる)。
1222
+ */
1223
+ function devExcludedSkip(row) {
1224
+ if (!row || row.cap !== "dev" || row.question !== "ingested")
1225
+ return false;
1226
+ if (row.status !== "skipped")
1227
+ return false;
1228
+ // 🔴 **書き手と理由の AND(片方だけで中立と決めない)。** engine の `neutral` の印は
1229
+ // ストアの属性にならない(`connection-state-writer.mjs` の `historyArgs` は履歴を飛ばす
1230
+ // だけ)ので、画面が中立と言える材料はこの 2 つしかない。`by: "gold"` だけで倒すと、
1231
+ // 夜間が**本物の** `skipped` を dev に書き始めた日に、⚠️ にしたい行が黙って
1232
+ // 「接続OK」に化ける(倒れる向きが問題を隠す側になる・設計レビュー🔴3)。
1233
+ // 理由文の印は engine の 2 文(`性質が作業物のため…` / `ソースの性質により…`)に共通で、
1234
+ // 正本は `infra/lambda/shared/adapters.mjs` の `INGEST_NEUTRAL_DETAIL_MARK`
1235
+ // (写しのずれは `state-vocab-parity.test.mjs` が見張る)
1236
+ if (row.by !== "gold")
1237
+ return false;
1238
+ var d = typeof row.detail === "string" ? row.detail : "";
1239
+ return d.indexOf("材料にしません") >= 0;
1240
+ }
1241
+ /**
1242
+ * 「いま材料にしていない」開発の接続先(#640)。**鍵は ref の生値**(`overlayStateOnFleet` の
1243
+ * `dev` の鍵と同じ畳み方——揃えないと、行に札が出ないのに Home だけ出る食い違いになる)。
1244
+ *
1245
+ * 🔴 **同じ接続先の「届くか」のほうが新しければ、その中立は古い事実として捨てる。**
1246
+ * 接続状態の行に TTL は無く、engine が中立の行を書くのは**門で省いた晩だけ**
1247
+ * (`connection-state-write-gold.mjs` の `gatedIngestedRow`)。登録簿で性質を「会話」に
1248
+ * 直すと、engine は翌晩から**行を書かないだけ**で、`ingested: ok` に上書きはしない。
1249
+ * 鮮度を見ないと、**言われたとおりに直した人の画面が永久に変わらない**——
1250
+ * 「登録簿で性質を『会話』にしてください」と出し続ける(設計レビュー🔴2)。
1251
+ *
1252
+ * 🔴 **比べる相手は「その晩の Gold 昇格が回った時刻」(`by: "gold"` の行の最新の `at`)。**
1253
+ * 同じ ref の「届くか」と比べてはいけない——接続状態の SK は `cap#workspace#ref#question`
1254
+ * で **`reachable` は 1 本しかなく**、「接続を確認」(`by: "button"`)がその 1 本を
1255
+ * 上書きする。比べると次の 2 つが**どちらも**起きる(実装レビュー🔴1):
1256
+ *
1257
+ * - ボタンの行も比べる … 押した瞬間に `at` が今になり、**押しただけで札が消える**
1258
+ * (押した人には「確認したら読まれるようになった」と読める)
1259
+ * - 夜間の行だけ比べる … 押すと夜間の行が**消える**ので比べる相手を失い、
1260
+ * 性質を直して消えたはずの札が**押しただけで復活する**
1261
+ *
1262
+ * 夜間の Gold 昇格は 1 回の run の全部の行に**同じ `at`** を書く(engine の
1263
+ * `connection-state-write-gold.mjs` は `at` を run で 1 つだけ作る)。だから
1264
+ * **書き手が `gold` の行の最新の `at`**=その案件で夜間が最後に回った時刻で、これは
1265
+ * ボタンが 1 本上書きしても残る(他の接続先・他の問いの行が持っている)。
1266
+ * 中立の行自身も `by: "gold"` なので、**厳密に新しいときだけ**捨てれば同じ晩の組は残る。
1267
+ *
1268
+ * ⚠ **残る穴**: その案件の `by: "gold"` の行が**この 1 本だけ**(接続先が 1 つで、
1269
+ * その `reachable` をボタンが上書きした)ときは比べる相手が無く、古い札が次の夜まで残る。
1270
+ * 塞ぐのは engine 側(門を通らなくなった先の `ingested` の行を消す/`ok` を書く)で、
1271
+ * 決定ログ `20260916-640.md` の「覆し方」に書いてある。
1272
+ *
1273
+ * 時刻はミリ秒ありの ISO に正規化済み(サーバの `normalizeAt`)なので辞書順で比べられる。
1274
+ *
1275
+ * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
1276
+ */
1277
+ function devExcludedRefs(rows) {
1278
+ var out = {};
1279
+ // その案件で夜間の Gold 昇格が最後に回った時刻。**能力も問いも問わず集める**
1280
+ // ——1 回の run は全部の行に同じ `at` を書くので、ボタンが 1 本上書きしても
1281
+ // 他の行が同じ時刻を持っている(上の項)
1282
+ var lastGoldAt = "";
1283
+ var list = Array.isArray(rows) ? rows : [];
1284
+ var i, r, k;
1285
+ for (i = 0; i < list.length; i++) {
1286
+ r = list[i];
1287
+ if (!r)
1288
+ continue;
1289
+ if (r.by === "gold" && String(r.at) > lastGoldAt)
1290
+ lastGoldAt = String(r.at);
1291
+ if (r.cap !== "dev")
1292
+ continue;
1293
+ k = String(r.ref == null ? "" : r.ref);
1294
+ if (!k)
1295
+ continue;
1296
+ if (devExcludedSkip(r) && (!out[k] || String(r.at) > String(out[k].at)))
1297
+ out[k] = r;
1298
+ }
1299
+ for (k in out) {
1300
+ if (!Object.prototype.hasOwnProperty.call(out, k))
1301
+ continue;
1302
+ // **厳密に新しいときだけ捨てる。** 同じ晩の組(`at` が同値)を落とさないため
1303
+ if (lastGoldAt > String(out[k].at))
1304
+ delete out[k];
1305
+ }
1306
+ return out;
1307
+ }
1308
+ /**
1309
+ * 中立の行の理由文から、画面に出す短語を作る(#640)。
1310
+ *
1311
+ * 🔴 **engine が名指しを避けた性質を、画面が断定しない。** engine は性質が語彙
1312
+ * (会話/作業物/参照資料)に載っているときだけ名指しし、載っていなければ
1313
+ * 「ソースの性質により Gold の材料にしません」と書く(`gatedIngestedRow`)。
1314
+ * viewer に性質の語彙の写しは持たない——持つと 4 枚目の写しになり、engine が
1315
+ * 門に掛ける性質を増やした日に黙ってずれる(engine 設計レビュー🟡6 と同じ罠)。
1316
+ * **理由文が名指ししている語をそのまま括弧に入れる**のが、写しを増やさない唯一の形。
1317
+ *
1318
+ * **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
1319
+ */
1320
+ function devExcludedLabel(detail) {
1321
+ var d = typeof detail === "string" ? detail : "";
1322
+ var at = d.indexOf("性質が");
1323
+ var to = d.indexOf("のため");
1324
+ var nature = at === 0 && to > 3 ? d.slice(3, to) : "";
1325
+ return nature ? "読み取りの対象外(" + nature + ")" : "読み取りの対象外";
1326
+ }
1175
1327
  /**
1176
1328
  * ストアの「届くか」を行に重ねる。**まとめ表の時刻とは比べない。**
1177
1329
  * fleet-status.json は chat / dev の gate・needed を焼き込まない(観測の一本化・工程1。
@@ -1212,12 +1364,23 @@ function sanitizeStateNow(v) {
1212
1364
  * 画面に届かない(fleet-status.json には焼き込まれないので、ライブが届かない案件では出ない
1213
1365
  * ——それは意図。古いスナップショットの理由を出すより、出さないほうが嘘にならない)。
1214
1366
  * needed と同じく、上書きするときは空でも必ず載せる
1367
+ *
1368
+ * **開発の行だけ、`ingested` の中立の行も載せる(#640)。** 「接続できる」と「Gold の
1369
+ * 材料にしている」は別の状態で、`reachable: ok` だけを載せると、読みに行かないと決めた
1370
+ * リポジトリが画面で「読み取り中」になる(engine #399 の申し送り)。載せる欄は
1371
+ * `ingestExcluded`(併記の札を出すか)と `ingestExcludedDetail`(語の括弧に入れる性質)。
1372
+ * 🔴 **`goldState: "excluded"`(人が `dev-sources.json` の exclude で止めた)と別物。**
1215
1373
  */
1216
1374
  function overlayStateOnFleet(view, rows) {
1217
1375
  const v = view || {};
1218
1376
  if (!rows || !rows.length || !Array.isArray(v.externalSources))
1219
1377
  return v;
1220
1378
  const byKey = {};
1379
+ // 「接続できる」と「Gold の材料にしている」は別の状態(#640)。**鮮度の門は
1380
+ // `devExcludedRefs` 1 本**(タイル・Home と同じ規則を 2 枚にしない)。拾う対象は
1381
+ // この下が捨てている行(`question !== "reachable"`)だけなので、チャット・
1382
+ // Google Chat の経路は 1 バイトも変わらない
1383
+ const excludedByRef = devExcludedRefs(rows);
1221
1384
  for (const r of rows) {
1222
1385
  if (r.question !== "reachable")
1223
1386
  continue;
@@ -1233,16 +1396,28 @@ function overlayStateOnFleet(view, rows) {
1233
1396
  if (!s || !s.ref)
1234
1397
  return s;
1235
1398
  let hit = null;
1399
+ let ex = null;
1236
1400
  if (s.type === "slack")
1237
1401
  hit = byKey["chat\t" + String(s.workspace || "") + "\t" + String(s.ref)];
1238
1402
  else if (s.type === "google-chat")
1239
1403
  hit = byKey["google-chat\t" + String(s.ref)];
1240
- else if (String(s.type || "").indexOf("github") === 0)
1404
+ else if (String(s.type || "").indexOf("github") === 0) {
1241
1405
  hit = byKey["dev\t" + String(s.ref)];
1242
- if (!hit)
1243
- return s;
1406
+ // ⚠ 鍵は ref の生値(1 つ上の `dev` の鍵と同じ畳み方)。**`liveRowVerdict` だけは
1407
+ // 両側を小文字に畳む**ので、大小が揺れた repo では Home にだけ札が出る食い違いが
1408
+ // 作れる(#566 の宿題と同根・この便では直さず申し送り)
1409
+ ex = excludedByRef[String(s.ref)] || null;
1410
+ }
1411
+ // **到達性の行が無い夜でも、併記だけは出す。** engine は 2 行を同じ晩に書くが、
1412
+ // 片方しか届かない窓を「何も言わない」に倒すと、画面が知っている事実を捨てる
1413
+ if (!hit) {
1414
+ if (!ex)
1415
+ return s;
1416
+ changed = true;
1417
+ return Object.assign({}, s, { ingestExcluded: true, ingestExcludedDetail: String(ex.detail || "") });
1418
+ }
1244
1419
  changed = true;
1245
- return Object.assign({}, s, {
1420
+ return Object.assign({}, s, ex ? { ingestExcluded: true, ingestExcludedDetail: String(ex.detail || "") } : {}, {
1246
1421
  gate: hit.status,
1247
1422
  gateAt: hit.at,
1248
1423
  gateBy: hit.by,
@@ -2507,7 +2682,12 @@ const TILE_CAPS = {
2507
2682
  // `cap: "google-chat"` の行は夜間の Gold 昇格が書く(#153 PR-E で結線済み)ので、
2508
2683
  // 夜間が一度走れば規則2/3(⚠️ / 接続OK)まで進む。走る前は規則4(実績なし→中立)
2509
2684
  "チャット-google-chat": { id: "google-chat", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", refKeyed: true },
2510
- "開発": { id: "dev", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", refKeyed: true },
2685
+ // 🔴 **`alsoBad: ["ingested"]` `devExcludedSkip` の篩と一対で足した(#640)。** 夜間が
2686
+ // 「接続はできるが Gold の材料にしていない」を `ingested: skipped` で書くようになった
2687
+ // (engine #399)ので、`reachable` だけを見ていると画面が「読み取り中」と嘘をつく。
2688
+ // ただし `BAD_STATE_STATUSES.ingested` は `skipped` を**悪い**と数えるので、篩を外すと
2689
+ // **GitHub を登録した全案件のタイルが ⚠️「取り込みを飛ばしました」**になる
2690
+ "開発": { id: "dev", question: "reachable", noFact: "登録済み(接続は未確認です)", keyLabel: "", alsoBad: ["ingested"], refKeyed: true },
2511
2691
  };
2512
2692
  /**
2513
2693
  * 登録したばかりで、まだ実測の行が1件も届いていない接続先があるときの語(#523 決定6)。
@@ -2901,6 +3081,11 @@ function deriveCapTile(cap, ctx) {
2901
3081
  var skipped = 0;
2902
3082
  var noToken = 0;
2903
3083
  var unreachable = 0;
3084
+ // 「接続はできるが Gold の材料にしていない」接続先(#640)。**警告には数えない。**
3085
+ // 鮮度の門(古い中立を捨てる)は `devExcludedRefs` が持つ——ここに書き写すと、
3086
+ // 行・Home・タイルで「いつ古いと見なすか」が割れる
3087
+ var excludedRefs = meta.id === "dev" ? devExcludedRefs(rows) : {};
3088
+ var excluded = 0;
2904
3089
  var okSeen = false;
2905
3090
  // 部分失敗の内訳の合計(#634)。**能力の全接続先を足す**——タイルは能力ぜんたいを 1 語で言う
2906
3091
  var sumOk = 0;
@@ -2917,6 +3102,15 @@ function deriveCapTile(cap, ctx) {
2917
3102
  // 出続ける(engine #314 の症状の候補3)。**篩い方の正本は stateRefKey 1箇所**
2918
3103
  if (refs && meta.refKeyed && !refs[stateRefKey(meta.id, r.ref, r.workspace)])
2919
3104
  continue;
3105
+ // 🔴 **中立の事実を、悪い語の表に掛ける前に抜く(#640)。** `BAD_STATE_STATUSES.ingested`
3106
+ // は `skipped` を悪いと数える(鍵が無くて始めていない、の意味)ので、ここを外すと
3107
+ // **GitHub を登録した全案件のタイルが ⚠️ になる**(engine #399 実装レビュー🟡3)。
3108
+ // 数えるのは**鮮度の門を通った分だけ**——性質を「会話」に直した接続先の古い行は
3109
+ // ここで黙って落とす(⚠️ にもしない。直すものが無い過去の事実なので)。
3110
+ // 件数は規則3 の見出しへ回す。「読まないと決めた」を黙って消すと、画面が
3111
+ // 「全部読んでいます」と読める(#523「常に真実を表示する」)
3112
+ if (devExcludedSkip(r))
3113
+ continue;
2920
3114
  // **一度でも成功していれば実績あり。** `lastOkAt` を見るのは、いま失敗している行でも
2921
3115
  // 「繋がったことがある」のは事実だから(規則2が先に立つので表示は⚠️のまま)。
2922
3116
  // **実績は `question`(その能力が「実際に動いた」と言える問い)の側だけで数える**
@@ -2958,6 +3152,16 @@ function deriveCapTile(cap, ctx) {
2958
3152
  else
2959
3153
  unreachable++;
2960
3154
  }
3155
+ // 「材料にしていない」は**接続先ごとに 1 件**(#640)。行を数えると、同じ ref の古い行が
3156
+ // 残っている案件で件数が水増しされる。孤児の篩(#523 決定2)はここにも掛ける
3157
+ // ——外した repo の中立の行が「読み取りの対象外が 3 件」に数え続けられないように
3158
+ for (var ek in excludedRefs) {
3159
+ if (!Object.prototype.hasOwnProperty.call(excludedRefs, ek))
3160
+ continue;
3161
+ if (refs && meta.refKeyed && !refs[stateRefKey(meta.id, ek)])
3162
+ continue;
3163
+ excluded++;
3164
+ }
2961
3165
  // ---- 規則2: 実測が悪い → 警告 ----
2962
3166
  //
2963
3167
  // **失敗を先に言う。** 「鍵が無くて飛ばした」より「鍵はあるのに取れなかった」ほうが
@@ -2986,6 +3190,12 @@ function deriveCapTile(cap, ctx) {
2986
3190
  if (okSeen && sumFailed > 0) {
2987
3191
  return { rule: 3, state: "ok", text: "接続OK(" + sumOk + " 件取り込み・" + sumFailed + " 件失敗)", keyMissing: false };
2988
3192
  }
3193
+ // **読みに行かないと決めた接続先は、件数で言う(#640)。** 「接続OK」の一言で丸めると
3194
+ // 「決定・用語の材料になっている」と読めるし、⚠️ に落とすと直すものが無いのに直しに
3195
+ // 行かせる。**帯は症状だけ**——性質を変える手順は行の補足が 1 行だけ持つ
3196
+ if (okSeen && excluded > 0) {
3197
+ return { rule: 3, state: "ok", text: "接続OK(読み取りの対象外が " + excluded + " 件)", keyMissing: false };
3198
+ }
2989
3199
  if (okSeen)
2990
3200
  return { rule: 3, state: "ok", text: "接続OK", keyMissing: false };
2991
3201
  // ---- 規則4: 実績なし → 中立 ----
@@ -3333,12 +3543,28 @@ function liveToolItems(tools, registered, rows) {
3333
3543
  }
3334
3544
  return "reachable";
3335
3545
  };
3546
+ // 「いま材料にしていない」接続先(#640)。**ループの外で 1 回だけ作る**
3547
+ //(行ごとに作り直すと、行数 × 項目数の走査になる)
3548
+ var excludedRefs = devExcludedRefs(rows);
3336
3549
  var stateOf = function (regKey, regRow, fallback) {
3337
3550
  var v = liveRowVerdict(regKey, regRow, rows, questionOf(regKey));
3338
3551
  if (!v)
3339
3552
  return fallback;
3340
- if (v.status === "ok")
3553
+ if (v.status === "ok") {
3554
+ // 「接続できる」と「Gold の材料にしている」を別に出す(#640・engine #399)。
3555
+ // **届いていない行には出さない**(`v.status === "ok"` の中だけ)——「アクセス不可」の
3556
+ // ほうが直す一手を持っているので、そちらを勝たせる(帯は症状)。
3557
+ // 判定は `devExcludedSkip` 1 本(タイル・操作タブの行と共有)、行の突き合わせは
3558
+ // `liveRowVerdict` 1 本(鍵の畳み方・複数行の勝ち方を 2 枚にしない・R10)
3559
+ // 鮮度の門も 1 本(`devExcludedRefs`)を通す——性質を「会話」に直した接続先の
3560
+ // 古い行で、Home だけが「読み取りの対象外」と言い続けないように
3561
+ if (regKey === "dev") {
3562
+ var iv = liveRowVerdict(regKey, regRow, rows, "ingested");
3563
+ if (iv && devExcludedSkip(iv.row) && excludedRefs[String(iv.row.ref)])
3564
+ return "ok_not_ingested";
3565
+ }
3341
3566
  return "reading";
3567
+ }
3342
3568
  if (v.status === "bad")
3343
3569
  return "waiting";
3344
3570
  return "unknown";
@@ -4837,7 +5063,7 @@ a.ds-tile:hover { text-decoration: none; }
4837
5063
  .ds-check-label, .ds-check-action, .ds-check-detail,
4838
5064
  .ds-pipe-label, .ds-pipe-todo,
4839
5065
  .ds-empty, .sec-how, .modal-desc, .ds-toggle-msg,
4840
- .ds-drive-note, .ds-inactive-note, .ds-gate-help,
5066
+ .ds-drive-note, .ds-inactive-note, .ds-gate-help, .ds-not-ingested-note,
4841
5067
  /* 案件カレンダーの行(#428)。**カレンダー ID は空白の無い長い連なり**で、
4842
5068
  失敗の理由(detail)も長さが読めない。ここを外すと会議のモーダルが横に溢れる */
4843
5069
  .mt-cal-banner, .mt-cal-note, .mt-cal-warn, .mt-cal-primary, .mt-cal-sub, .mt-cal-id .rt-copy-text,
@@ -4886,6 +5112,13 @@ a.ds-tile:hover { text-decoration: none; }
4886
5112
  トグルを撤去したいま、読み取られていないことを示すのはこのバッジだけ。
4887
5113
  **判断のバッジなので、由来や組織のバッジより強く出す** */
4888
5114
  .badge.ds-excluded { background: #f3f4f6; color: #4b5563; border: 1px solid #d1d5db; }
5115
+ /* 「接続はできるが Gold の材料にしていない」印(#640)。**「読み取り停止中」と同じ灰色**
5116
+ ——どちらも「いまこの接続先がどう扱われているか」の印で、直すものが無い中立の事実。
5117
+ 橙(警告)にすると、直しようのないものを直しに行かせる */
5118
+ .badge.ds-not-ingested { background: #f3f4f6; color: #4b5563; border: 1px solid #d1d5db; }
5119
+ /* その理由と、変えたいときの一手(1 行だけ)。**行の補足(.ds-gate-help)と同じ見た目**だが、
5120
+ 「接続を確認」で隠される棚には置かない(押しても変わらない事実のため) */
5121
+ .ds-not-ingested-note { font-size: 12px; color: #6b7280; line-height: 1.6; margin: 4px 0 0; flex: 1 1 100%; }
4889
5122
  /* 登録の由来(#118)。**薄く出す**——読み取りの可否や Gold 対象かより弱い情報で、
4890
5123
  ここが目立つと「除外」「未宣言」の判断のバッジと同じ強さに見えてしまう */
4891
5124
  /* どの Slack ワークスペースのチャンネルか(#104)。**由来のバッジと同じ強さにする**——
@@ -5494,6 +5727,12 @@ var INGEST_TILE_ROWS = ${JSON.stringify(INGEST_TILE_ROWS)};
5494
5727
  ${sanitizeIngestCounts.toString()}
5495
5728
  ${sanitizeIngestFailures.toString()}
5496
5729
  ${sanitizeStateNow.toString()}
5730
+ // 「接続はできるが Gold の材料にしていない」中立の行(#640)。🔴 **overlayStateOnFleet /
5731
+ // deriveCapTile / liveToolItems / 行の併記が全部この 1 本を引く。** 落とすと 4 か所が
5732
+ // 未定義を参照して、ライブ取得が丸ごと落ちる(画面が静的表示に戻る)
5733
+ ${devExcludedSkip.toString()}
5734
+ ${devExcludedRefs.toString()}
5735
+ ${devExcludedLabel.toString()}
5497
5736
  ${overlayStateOnFleet.toString()}
5498
5737
  // Home の「使用ツール」(#275)。**判定と表示語の実体はビルド時の定義**——
5499
5738
  // 画面側に書き写すと、状態の言い換えが 2 枚になって片方だけ直る。
@@ -8684,10 +8923,20 @@ ${calendarAddUrl.toString()}
8684
8923
  // Issues と Discussions は同じ接続先(同じ dev 行)なので、理由・補足は1本目だけが
8685
8924
  // 持つ(#346)。同じ長文が2行に並ぶと一覧が案内で埋まる
8686
8925
  var seenRef = {};
8926
+ // 「材料にしていない」の 1 行は、**この組織で実際に出した行があってから**畳む(#640)。
8927
+ // 🔴 「seenRef」 を写経して「1 本目かどうか」でフラグを立てると、1 本目が
8928
+ // 併記を持たない行だったときにフラグだけ消費され、**その組織のどの行にも
8929
+ // 補足が出ない**(設計レビュー🟡5)。**出した行だけを数える**
8930
+ var shownExcluded = false;
8687
8931
  var cards = g.items.map(function (x) {
8688
8932
  var again = !!(x.ref && seenRef[x.ref]);
8689
8933
  if (x.ref) seenRef[x.ref] = 1;
8690
- return extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again });
8934
+ // 🔴 **フラグを立てるのは、その行が実際に補足を出すときだけ。** 出さない行
8935
+ //(同じ ref の 2 本目・「読み取り停止中」)でフラグを消費すると、**その組織の
8936
+ // どの行にも補足が出ない**(設計レビュー🟡5 と同じ罠。条件は 「extSourceCard」 と揃える)
8937
+ var quietExcluded = shownExcluded;
8938
+ if (!again && x && x.ingestExcluded === true && x.goldState !== "excluded") shownExcluded = true;
8939
+ return extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again, quietExcluded: quietExcluded });
8691
8940
  });
8692
8941
  if (g.state === "warn") {
8693
8942
  out.push(orgNoteBox(githubNotConnectedNodes(g), cards));
@@ -9663,15 +9912,23 @@ ${calendarAddUrl.toString()}
9663
9912
  /**
9664
9913
  * 登録したばかりで、まだ実測の行が1件も届いていない接続先があるか(#523 決定6)。
9665
9914
  *
9666
- * **対象は取り込む4能力だけ**(「alsoBadを持つ能力=規則4の文言が
9915
+ * **対象は取り込む4能力だけ**(実績の問いが 「ingestedの能力=規則4の文言が
9667
9916
  * 「まだ取り込みがありません」の系)。チャット・開発の規則4は既に
9668
9917
  * 「接続は未確認です」と言っており、待っていることが読める。
9669
9918
  *
9919
+ * 🔴 **判定に 「alsoBad」 の有無を使わない(#640)。** かつては「取り込む4能力は
9920
+ * alsoBad を持つ」という**偶然の一致**を代用していたが、あちらは
9921
+ * 「実績の問いとは別に、悪ければ⚠️にする問い」を並べる欄で、意味が違う。
9922
+ * 開発に 「alsoBad: ["ingested"]」 を足した瞬間、**GitHub を登録した直後15分の
9923
+ * 開発タイルが「登録済み(接続は未確認です)」から「確認しています…」に黙って変わる**
9924
+ * ——今日の 7 能力では 「question === "ingested"」 と同値なので、表示は 1 バイトも
9925
+ * 変わらないまま、意図(規則4の文言の系)をそのまま述語にする。
9926
+ *
9670
9927
  * 上限は 「UNSETTLED_TTL_MS」(15分)。過ぎたら中立文に戻る——**待てば出ると言い続けない**。
9671
9928
  */
9672
9929
  function capConfirming(derCap) {
9673
9930
  var meta = TILE_CAPS[derCap];
9674
- if (!meta || !meta.alsoBad) return false;
9931
+ if (!meta || meta.question !== "ingested") return false;
9675
9932
  var ops = pendingOpsNow();
9676
9933
  var now = Date.now();
9677
9934
  for (var i = 0; i < ops.length; i++) {
@@ -14283,6 +14540,31 @@ ${calendarAddUrl.toString()}
14283
14540
  title: "このリポジトリは読み取り対象から除外されています(開発の登録ファイル dev-sources.json の exclude)。この画面からは戻せません。",
14284
14541
  }));
14285
14542
  }
14543
+ // 「接続できる」と「Gold の材料にしている」を別の状態として併記する(#640・engine #399)。
14544
+ //
14545
+ // **常に出す(gate の値で隠さない)。** 届かないことと材料にしていないことは別の事実で、
14546
+ // 片方を隠すと「アクセス不可を直せば読まれる」と誤読させる(#523・オーナー決定
14547
+ // 「操作タブは常に真実を表示する」)。
14548
+ //
14549
+ // 🔴 **「ds-gate」 ではなく 「badge」 で出す。** 行の「判定のバッジは 1 つ」を
14550
+ // 「connection-probe.test.mjs」 が見張っており、「probeSection」 も 「gateEl」 の参照 1 本で
14551
+ // 置き換える設計なので、「.ds-gate」 を 2 つにすると「押した結果がどちらを差し替えたか」が
14552
+ // 構造的に決まらなくなる。**「読み取り停止中」(人が止めた)と同じ棚**に並べるのが正しい
14553
+ // ——どちらも「判定」ではなく「この接続先がいまどう扱われているか」の印。
14554
+ //
14555
+ // 🔴 **警告色にしない。** 直すものが無い中立の事実で、橙にすると直しに行かせる。
14556
+ // 語は短く(長文を入れると行が折り返す)——性質の変え方は下の補足 1 行が持つ
14557
+ // 🔴 **「読み取り停止中」の行には出さない(実装レビュー🟡4)。** 人が exclude で止めた
14558
+ // 行は engine が観測しなくなる= 2 行とも書き直されないので、古い中立の行が永久に残る。
14559
+ // しかも止めてある以上、性質を「会話」にしても読まれない——**効かない一手**を案内する
14560
+ // ことになる。灰色の札が 2 つ並ぶのも「繰り返さない」に反する
14561
+ if (s.ingestExcluded === true && s.goldState !== "excluded") {
14562
+ badges.appendChild(el("span", {
14563
+ class: "badge ds-not-ingested",
14564
+ text: devExcludedLabel(s.ingestExcludedDetail),
14565
+ title: "接続はできています。この登録は Gold(決定・用語・メンバー・ルール)の材料にしていません。",
14566
+ }));
14567
+ }
14286
14568
  if (s.notify === true) badges.appendChild(notifyBadge());
14287
14569
  // **どのワークスペースのチャンネルかを、必ず行に出す(#104・2026-08-20 に社内も)。**
14288
14570
  //
@@ -14480,6 +14762,28 @@ ${calendarAddUrl.toString()}
14480
14762
  if (reason) card.appendChild(neededNode(reason, "ds-gate-help ds-gate-reason"));
14481
14763
  }
14482
14764
  }
14765
+ // 「材料にしていない」の理由と、変えたいときの一手(#640)。**1 行だけ。**
14766
+ //
14767
+ // 🔴 **「ds-gate-help」 を付けない。** あちらは「接続を確認」を押した瞬間に
14768
+ // 「probeSection」 がまとめて隠す札で、押した人がいちばん誤読しやすい瞬間
14769
+ //(「確認したから読まれるようになった」)に、この 1 行だけが消える。
14770
+ // 押しても変わらない事実なので、隠される棚に置かない(幅の扱いは CSS で揃える)。
14771
+ //
14772
+ // 🔴 **その組織のグループで最初の 1 行にだけ出す(「opts.quietExcluded」)。** GitHub の
14773
+ // 既定は「作業物」なので、全行に出すと一覧が案内で埋まる(#356・オーナー却下の原則)。
14774
+ // 同じリポジトリの 2 本目(Discussions)にも出さない(「opts.quietHelp」 と同じ理由)
14775
+ if (s.ingestExcluded === true && s.goldState !== "excluded" && !(opts && (opts.quietHelp || opts.quietExcluded))) {
14776
+ // 🔴 **性質を直書きしない(実装レビュー🟡2)。** すぐ上の札は理由文が名指しした語だけを
14777
+ // 括弧に入れているのに、ここで「作業物」と書くと、engine が名指しを避けた回に
14778
+ // **同じ行が 2 つのことを言う**(札は「読み取りの対象外」・補足は「作業物」)。
14779
+ // 名指しできた回だけ性質を言い、できなかった回は事実だけを言う
14780
+ var exNature = devExcludedLabel(s.ingestExcludedDetail).replace(/^読み取りの対象外(?/, "").replace(/)$/, "");
14781
+ card.appendChild(el("p", {
14782
+ class: "ds-not-ingested-note",
14783
+ text: (exNature ? "この登録は「" + exNature + "」なので、" : "この登録は、")
14784
+ + "決定・用語・メンバー・ルールの材料にしていません。議論の場として読ませるなら、登録簿で性質を「会話」にしてください",
14785
+ }));
14786
+ }
14483
14787
  // 「接続を確認」(2026-08-19)。**押せる人を絞らない**——読むだけで設定を変えないので、
14484
14788
  // 気づいた人がその場で確かめられるほうがよい(オーナー判断)。書き込みになる
14485
14789
  // 「表示名を更新する」は、押した結果の中から別のボタンとして出る(probeSection)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.68",
3
+ "version": "0.12.69",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,