@tyhld/conductor 0.12.100 → 0.12.101
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/autoupdate.js +64 -15
- package/dist/headless.js +10 -4
- package/dist/relay.js +35 -14
- package/package.json +1 -1
- package/scripts/hooks/skill_sync_notify.py +22 -10
- package/scripts/hooks/test_skill_sync_notify.py +42 -17
package/dist/autoupdate.js
CHANGED
|
@@ -252,33 +252,82 @@ function installConductor(d, version, offline) {
|
|
|
252
252
|
return { ok: false, detail: `npm install の終了 ${r.status}・入っている版 ${now ?? '不明'}(${(r.error ?? r.stderr).trim().slice(-200)})` };
|
|
253
253
|
}
|
|
254
254
|
/**
|
|
255
|
-
*
|
|
255
|
+
* 入れ替え後に揃える物(配布物の中の元 → 置き場 → 実行権を付けるか)。
|
|
256
|
+
* ★`dir` は ~/.tyhld の下の置き場の名前。番人が職人の書き込みを止める場所なので、
|
|
257
|
+
* ここを書けるのは更新係・設置台本(人が打つ物)だけ。
|
|
258
|
+
*/
|
|
259
|
+
const ENTRY_SYNC = [
|
|
260
|
+
{ from: 'scripts/tyhld-autoupdate.sh', dir: 'bin', to: 'tyhld-autoupdate', exe: true },
|
|
261
|
+
{ from: 'scripts/tyhld-selfcheck.sh', dir: 'bin', to: 'tyhld-selfcheck', exe: true },
|
|
262
|
+
{ from: 'scripts/dist-source.sh', dir: 'bin', to: 'dist-source.sh', exe: false },
|
|
263
|
+
{ from: 'scripts/conductor-run.sh', dir: 'bin', to: 'conductor-run', exe: true },
|
|
264
|
+
{ from: 'scripts/hooks/skill_sync_notify.py', dir: 'bin', to: 'skill_sync_notify.py', exe: false },
|
|
265
|
+
// ★Codex の完了の受け口(便 08af46e0)。
|
|
266
|
+
// 【なぜここで揃えるか】受け口の置き場(~/.tyhld/hooks/codex-notify.py)は
|
|
267
|
+
// ~/.codex/config.toml の notify が【絶対パス】で指す1か所。版が上がって中身が変わっても、
|
|
268
|
+
// 各PCで人が設置台本(install-codex-hooks.sh)を打つまで古い受け口が残り、
|
|
269
|
+
// 新しい配り役が読む受け皿に完了の知らせが入らない。
|
|
270
|
+
// 【広げないための決め】★既に受け口が置いてあるPCだけに写す(下の `not-installed`)。
|
|
271
|
+
// 未設置のPC(Codex を使っていないPC)には1バイトも置かない。
|
|
272
|
+
// ★~/.codex/hooks.json と ~/.codex/config.toml には触らない(設定を変える道は、
|
|
273
|
+
// 今までどおり人が設置台本を打つ)。ここで揃えるのは【受け口のファイルだけ】。
|
|
274
|
+
{ from: 'scripts/codex/codex-notify.py', dir: 'hooks', to: 'codex-notify.py', exe: true },
|
|
275
|
+
];
|
|
276
|
+
/** 中身が1バイトも違わないか(★同じなら書き換えない=毎回の入れ替えで日付だけ動かない)。 */
|
|
277
|
+
function sameContent(a, b) {
|
|
278
|
+
try {
|
|
279
|
+
return readFileSync(a).equals(readFileSync(b));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 更新係・自己診断・常駐の入口と Codex の完了の受け口を、入れ替えた配布物から写し直す
|
|
287
|
+
* (★在る物だけ・原子的に置き換える)。
|
|
256
288
|
* 常駐の入口(conductor-run・dist-source.sh・skill_sync_notify.py)は毎時同期でも揃うが、
|
|
257
289
|
* 起こし直す前に揃えておく(新しい配布物の探し方で起きる)。
|
|
258
290
|
*/
|
|
259
291
|
export function refreshEntries(home, distRoot) {
|
|
260
|
-
const bin = path.join(home, '.tyhld', 'bin');
|
|
261
|
-
const pairs = [
|
|
262
|
-
['scripts/tyhld-autoupdate.sh', 'tyhld-autoupdate', true],
|
|
263
|
-
['scripts/tyhld-selfcheck.sh', 'tyhld-selfcheck', true],
|
|
264
|
-
['scripts/dist-source.sh', 'dist-source.sh', false],
|
|
265
|
-
['scripts/conductor-run.sh', 'conductor-run', true],
|
|
266
|
-
['scripts/hooks/skill_sync_notify.py', 'skill_sync_notify.py', false],
|
|
267
|
-
];
|
|
268
292
|
const out = [];
|
|
269
|
-
for (const
|
|
293
|
+
for (const { from, dir, to, exe } of ENTRY_SYNC) {
|
|
270
294
|
const src = path.join(distRoot, from);
|
|
271
|
-
const dst = path.join(
|
|
272
|
-
if (!existsSync(src)
|
|
295
|
+
const dst = path.join(home, '.tyhld', dir, to);
|
|
296
|
+
if (!existsSync(src)) {
|
|
297
|
+
out.push({ name: to, outcome: 'no-source' });
|
|
273
298
|
continue;
|
|
299
|
+
}
|
|
300
|
+
// ★既に置いてある物だけを揃える(未設置のPCには新しく置かない)。
|
|
301
|
+
if (!existsSync(dst)) {
|
|
302
|
+
out.push({ name: to, outcome: 'not-installed' });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (sameContent(src, dst)) {
|
|
306
|
+
out.push({ name: to, outcome: 'same' });
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
274
309
|
copyFileSync(src, `${dst}.tmp`);
|
|
275
310
|
if (exe)
|
|
276
311
|
chmodSync(`${dst}.tmp`, 0o755);
|
|
277
312
|
renameSync(`${dst}.tmp`, dst);
|
|
278
|
-
out.push(to);
|
|
313
|
+
out.push({ name: to, outcome: 'synced' });
|
|
279
314
|
}
|
|
280
315
|
return out;
|
|
281
316
|
}
|
|
317
|
+
/** 揃えた結果を1行の日本語にする(★記録に残して、あとから何をしたか読めるようにする)。 */
|
|
318
|
+
export function describeEntrySync(results) {
|
|
319
|
+
const label = {
|
|
320
|
+
synced: '揃えた',
|
|
321
|
+
same: '揃える必要が無かった',
|
|
322
|
+
'not-installed': '置いていないので何もしなかった',
|
|
323
|
+
'no-source': '配布物に元が無かった',
|
|
324
|
+
};
|
|
325
|
+
const parts = ['synced', 'same', 'not-installed', 'no-source']
|
|
326
|
+
.map((k) => ({ k, names: results.filter((r) => r.outcome === k).map((r) => r.name) }))
|
|
327
|
+
.filter((g) => g.names.length > 0)
|
|
328
|
+
.map((g) => `${label[g.k]}=${g.names.join(',')}`);
|
|
329
|
+
return `入口と受け口の同期: ${parts.length > 0 ? parts.join('/') : '対象なし'}`;
|
|
330
|
+
}
|
|
282
331
|
/** 常駐(conductor@*・耳・司令塔)の一覧。 */
|
|
283
332
|
function relayUnits(d) {
|
|
284
333
|
const r = d.exec('systemctl', ['--user', 'list-units', 'conductor@*.service', 'conductor-ear.service', 'conductor-build-runner.service', '--all', '--no-legend', '--plain'], { timeoutMs: 15_000 });
|
|
@@ -410,7 +459,7 @@ async function rollBack(d, s, back) {
|
|
|
410
459
|
if (comp === 'conductor') {
|
|
411
460
|
const r = installConductor(d, back, true);
|
|
412
461
|
d.log(r.detail);
|
|
413
|
-
refreshEntries(d.home, d.distRoot);
|
|
462
|
+
d.log(describeEntrySync(refreshEntries(d.home, d.distRoot)));
|
|
414
463
|
d.log(await restartRelays(d, back));
|
|
415
464
|
}
|
|
416
465
|
else {
|
|
@@ -615,7 +664,7 @@ export async function runUpdater(d) {
|
|
|
615
664
|
removeDrain(d, s);
|
|
616
665
|
return done(d, s, 'install-failed', r.detail);
|
|
617
666
|
}
|
|
618
|
-
refreshEntries(d.home, d.distRoot);
|
|
667
|
+
d.log(describeEntrySync(refreshEntries(d.home, d.distRoot)));
|
|
619
668
|
d.log(await restartRelays(d, want));
|
|
620
669
|
}
|
|
621
670
|
else {
|
package/dist/headless.js
CHANGED
|
@@ -19,7 +19,7 @@ import { createInterface } from 'node:readline';
|
|
|
19
19
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import os from 'node:os';
|
|
22
|
-
import { CENTRAL_BUSY_HOLD_MAX_MS, RELAY_DELIVER_MAX, RELAY_FETCH_TIMEOUT_MS, RELAY_HEARTBEAT_INTERVAL_MS, RELAY_RESTORE_ORPHANS_ENABLED, ackResumeSignal, buildAnchor, buildSendText, canDeliver,
|
|
22
|
+
import { CENTRAL_BUSY_HOLD_MAX_MS, RELAY_DELIVER_MAX, RELAY_FETCH_TIMEOUT_MS, RELAY_HEARTBEAT_INTERVAL_MS, RELAY_RESTORE_ORPHANS_ENABLED, ackResumeSignal, buildAnchor, buildSendText, canDeliver, completeWithReportOutcome, createRelayState, deliverJobEndFacts, deliveryReport, fetchCommand, fetchCommandStatus, fetchControl, fetchOrphans, flushPendingJobEndFacts, isTerminalCommandStatus, kindOf, markDelivered, noteCentralDown, noteCentralUp, noteDrain, noteReadiness, patchStatus, recoverStuckProcessedReports, reconnectDelayMs, WAIT_CLOSED_STATUS, FINISH_GATE_KINDS, reportDelivery, selectOrphanToRestore, sendHeartbeat, } from './relay.js';
|
|
23
23
|
import { claudePinPath, readClaudePinLine, } from './autoupdate-flags.js';
|
|
24
24
|
const log = (msg) => {
|
|
25
25
|
console.log(`[headless] ${msg}`);
|
|
@@ -1471,10 +1471,16 @@ async function checkRunningJob(cfg, state) {
|
|
|
1471
1471
|
// ★書けるまでやり直す(便 6ebba0d0)。以前は false のとき patchStatus を1回試し、
|
|
1472
1472
|
// 成否を見ずに state.job を畳んでいた=誰も done を書いていないのに便を手放していた。
|
|
1473
1473
|
// 畳まない間は次の便を配らない/管制には生存報告と理由が残る。
|
|
1474
|
-
|
|
1474
|
+
// ★「どう畳んだか」を見て、手番の記録にも実際を書く(便 f20dd761 の候補3)。
|
|
1475
|
+
// 以前は畳めたら必ず outcome=done・detail=「報告まで出しました」だった。
|
|
1476
|
+
// 本番操作の承認待ちで閉じた手番(完了は1バイトも書いていない)まで
|
|
1477
|
+
// 「報告まで出しました」になり、`kc 現場名` が「終わりました」と見せていた。
|
|
1478
|
+
// ★札(outcome)は増やさない。実際を言うのは detail の役目にする。
|
|
1479
|
+
const settle = await completeWithReportOutcome(cfg, job.commandId, log);
|
|
1480
|
+
if (settle.settled) {
|
|
1475
1481
|
state.doneWriteTries?.delete(job.commandId);
|
|
1476
|
-
log(`site=${cfg.site} id=${job.commandId}
|
|
1477
|
-
noteLastJob('done',
|
|
1482
|
+
log(`site=${cfg.site} id=${job.commandId} 畳みます(exit=0, attempt ${job.attempt}・${settle.detail})`);
|
|
1483
|
+
noteLastJob('done', settle.detail);
|
|
1478
1484
|
state.job = null;
|
|
1479
1485
|
rs.lastHeartbeatAt = null;
|
|
1480
1486
|
return;
|
package/dist/relay.js
CHANGED
|
@@ -3679,8 +3679,16 @@ export async function patchStatus(cfg, id, status, report) {
|
|
|
3679
3679
|
* 書けなければ未処理のまま残す=次tickでやり直せるし、管制も生存報告で気づける。
|
|
3680
3680
|
*
|
|
3681
3681
|
* 返り: 追跡を畳んでよいか(true = 便の終了状態が確定した/false = 書けていない=次の周でやり直す)。
|
|
3682
|
+
* ★★「どう畳んだか」も要るときは completeWithReportOutcome を呼ぶ(手番の記録に実際を書くため)。
|
|
3682
3683
|
*/
|
|
3683
3684
|
export async function completeWithReport(cfg, id, logLine = log) {
|
|
3685
|
+
return (await completeWithReportOutcome(cfg, id, logLine)).settled;
|
|
3686
|
+
}
|
|
3687
|
+
/**
|
|
3688
|
+
* 完了を1回で送り、【どう畳んだか】も返す(★中身の説明は上の completeWithReport と同じ)。
|
|
3689
|
+
* 呼び手は settled だけ見てもよいが、手番の記録を書くときは kind・detail も使う。
|
|
3690
|
+
*/
|
|
3691
|
+
export async function completeWithReportOutcome(cfg, id, logLine = log) {
|
|
3684
3692
|
// ★①報告がどの段にあるか+形が整っているか(送る工程の話)。②③の実行可否には使わない。
|
|
3685
3693
|
// ★形が整っていない報告は「届いた」と数えない(便 461be339)。ファイルは消さず・移さず残す。
|
|
3686
3694
|
const delivery = reportDelivery(id);
|
|
@@ -3697,7 +3705,7 @@ export async function completeWithReport(cfg, id, logLine = log) {
|
|
|
3697
3705
|
(phase === 'pending' && !delivery.delivered
|
|
3698
3706
|
? `→relayからは送りません(${delivery.note}=未処理のまま残します)`
|
|
3699
3707
|
: '→relayからは送りません(報告は控えとして処理済みにしました)'));
|
|
3700
|
-
return true;
|
|
3708
|
+
return { settled: true, kind: 'already-finished', detail: `管制で既に終わっていました(${already})` };
|
|
3701
3709
|
}
|
|
3702
3710
|
if (isWaitClosedCommandStatus(already)) {
|
|
3703
3711
|
// ★待ちのまま閉じられた便(本番操作の承認待ち・管制 PR #425)。★完了を1バイトも書かない。
|
|
@@ -3709,30 +3717,43 @@ export async function completeWithReport(cfg, id, logLine = log) {
|
|
|
3709
3717
|
logLine(`site=${cfg.site} id=${id} は待ちのまま閉じられています(status=${already})` +
|
|
3710
3718
|
'→完了は書きません(本番操作が済めば管制の合図で同じ便の続きから起きます)' +
|
|
3711
3719
|
(phase === 'none' ? '' : `/報告は手元の控えに残します(${phase})`));
|
|
3712
|
-
return
|
|
3720
|
+
return {
|
|
3721
|
+
settled: true,
|
|
3722
|
+
kind: 'wait-closed',
|
|
3723
|
+
// ★ここが「報告まで出しました」になっていると、承認を待っている手番が
|
|
3724
|
+
// 「終わりました」と記録される(便 f20dd761 の候補3)。実際を書く。
|
|
3725
|
+
detail: '本番操作の承認待ちのまま閉じました(完了は書いていません。承認が済めば続きから起きます)',
|
|
3726
|
+
};
|
|
3713
3727
|
}
|
|
3714
3728
|
if (phase === 'processed') {
|
|
3715
3729
|
// ★①は済み・②は未了。本文は渡してあるので、状態だけを書く(開発履歴を2件にしない)。
|
|
3716
3730
|
// 書けなければ false=次の周でやり直す(完了を落とさない)。
|
|
3717
|
-
if (!(await patchStatus(cfg, id, 'done')))
|
|
3718
|
-
return false;
|
|
3731
|
+
if (!(await patchStatus(cfg, id, 'done'))) {
|
|
3732
|
+
return { settled: false, kind: 'not-written', detail: '便の終了状態を管制へ書けませんでした' };
|
|
3733
|
+
}
|
|
3719
3734
|
logLine(`site=${cfg.site} id=${id} done(報告は回収の道で渡し済み=本文は添えない)`);
|
|
3720
|
-
return true;
|
|
3735
|
+
return { settled: true, kind: 'done', detail: '報告まで出しました' };
|
|
3721
3736
|
}
|
|
3722
3737
|
// ★形が整っていない報告は添えない(書きかけを完了の材料にしない)。便の確定は今までどおり行う
|
|
3723
3738
|
// =現場を占有したままにしない。「報告が届いていない」ことは終了の事実で管制へ届く。
|
|
3724
3739
|
const report = delivery.delivered && delivery.raw !== null ? parseReport(delivery.raw).body : null;
|
|
3725
|
-
if (!(await patchStatus(cfg, id, 'done', report)))
|
|
3726
|
-
return false;
|
|
3740
|
+
if (!(await patchStatus(cfg, id, 'done', report))) {
|
|
3741
|
+
return { settled: false, kind: 'not-written', detail: '便の終了状態を管制へ書けませんでした' };
|
|
3742
|
+
}
|
|
3727
3743
|
if (report !== null)
|
|
3728
3744
|
markReportProcessed(id); // ★消さずに処理済みへ(同じ報告を二度送らない)
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
return
|
|
3745
|
+
const how = report !== null
|
|
3746
|
+
? `(報告添付・${report.length}文字)`
|
|
3747
|
+
: phase === 'none'
|
|
3748
|
+
? '(報告ファイルなし=完了のみ)'
|
|
3749
|
+
: `(${delivery.note}=添えずに未処理のまま残します)`;
|
|
3750
|
+
logLine(`site=${cfg.site} id=${id} done${how}`);
|
|
3751
|
+
return {
|
|
3752
|
+
settled: true,
|
|
3753
|
+
kind: 'done',
|
|
3754
|
+
// ★報告が無い/形が整っていない回を「報告まで出しました」と言わない。
|
|
3755
|
+
detail: report !== null ? '報告まで出しました' : `完了は書きました${how}`,
|
|
3756
|
+
};
|
|
3736
3757
|
}
|
|
3737
3758
|
/**
|
|
3738
3759
|
* 「実プロセスが無い・報告は渡し済み・管制ではまだ終わっていない」便に、便の終了状態だけを書く
|
package/package.json
CHANGED
|
@@ -110,22 +110,32 @@ REINSTALL = ('npm install -g @tyhld/conductor '
|
|
|
110
110
|
RESYNC = 'APPLY=1 bash "$(npm root -g)/@tyhld/conductor/scripts/update-guard.sh"'
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def autoupdate_enabled():
|
|
114
|
-
"""自動更新が入っている PC か(自社PCの鍵 ~/.tyhld/bin/autoupdate/enabled・便 29babc5d)。
|
|
115
|
-
|
|
113
|
+
def autoupdate_enabled(home=None):
|
|
114
|
+
"""自動更新が入っている PC か(自社PCの鍵 ~/.tyhld/bin/autoupdate/enabled・便 29babc5d)。
|
|
115
|
+
|
|
116
|
+
【なぜ住まいを受け取れる形にしたか(便 f20dd761 の重大発見2・便 08af46e0 で直した)】
|
|
117
|
+
住まいを中で決めていたので、試験が【実機の住まい】を覗いてしまっていた。
|
|
118
|
+
自動更新が入っているPCでは案内が「次の自動更新でそろいます」に変わるため、
|
|
119
|
+
同じ試験が機体によって通ったり落ちたりした。呼び手から渡せる形にして、
|
|
120
|
+
試験は使い捨ての住まいで判定する=どのPCでも同じ結果になる。
|
|
121
|
+
"""
|
|
122
|
+
base = os.path.expanduser('~') if home is None else home
|
|
123
|
+
return os.path.isfile(os.path.join(base, '.tyhld', 'bin', 'autoupdate', 'enabled'))
|
|
116
124
|
|
|
117
125
|
|
|
118
|
-
def recovery_steps(reason):
|
|
126
|
+
def recovery_steps(reason, home=None):
|
|
119
127
|
"""うまくいかなかった理由に【合った】手順を返す(便 675e2d05)。
|
|
120
128
|
|
|
121
129
|
【なぜ理由ごとに分けるか(実地 2026-09-20・便 8692c586 の重大発見1)】
|
|
122
130
|
以前は理由によらず「update-guard を打ってください」の固定文だった。version-drift
|
|
123
131
|
(入っている版と公開の版が違う)のときにこれを打っても、配り元の確認でまた弾かれる=
|
|
124
132
|
同じ失敗を繰り返すだけで直らない。先に入れ直しが要る。
|
|
133
|
+
|
|
134
|
+
home … 自動更新の鍵を探す住まい(既定は実機の住まい。試験は使い捨てを渡す)。
|
|
125
135
|
"""
|
|
126
136
|
if reason in ('flagged-bad', 'flagged-suspect'):
|
|
127
137
|
# ★管制で不具合・疑いの印が付いた版(便 29babc5d)。入れ直しても同じ版なら直らない。
|
|
128
|
-
if autoupdate_enabled():
|
|
138
|
+
if autoupdate_enabled(home):
|
|
129
139
|
return ['再開のしかた: この PC は自動更新が入っています。管制が直した版を決めれば、'
|
|
130
140
|
'次の自動更新で入れ替わります(人の操作は要りません)。']
|
|
131
141
|
return ['再開のしかた: この版には管制で不具合の印が付いています。CC1 に知らせてください'
|
|
@@ -134,7 +144,7 @@ def recovery_steps(reason):
|
|
|
134
144
|
# ★自動更新が入っている PC は、更新係が管制の決めた版へそろえる(便 29babc5d)。
|
|
135
145
|
# 人が npm で入れると段階配布の順番が崩れるので、入れ直しを促さない。
|
|
136
146
|
# ★not-installed・incomplete は配布物そのものが無い=更新係も動けないので、自動更新の有無によらず入れ直す。
|
|
137
|
-
if autoupdate_enabled() and reason in ('unpublished', 'deprecated', 'version-drift'):
|
|
147
|
+
if autoupdate_enabled(home) and reason in ('unpublished', 'deprecated', 'version-drift'):
|
|
138
148
|
return ['再開のしかた: この PC は自動更新が入っています。次の自動更新(管制が決めた版を、'
|
|
139
149
|
'作業中の便が終わってから入れます)でそろいます。管制の「PCの状態」で確かめてください。']
|
|
140
150
|
return [f'再開のしかた: この PC で次の順に打ってください。① {REINSTALL}',
|
|
@@ -153,7 +163,7 @@ def recovery_steps(reason):
|
|
|
153
163
|
return [f'再開のしかた: この PC で次の1行を打ってください。 {RESYNC}']
|
|
154
164
|
|
|
155
165
|
|
|
156
|
-
def build_payload(rec, env=None):
|
|
166
|
+
def build_payload(rec, env=None, home=None):
|
|
157
167
|
"""カードの中身。★何が起きていて、何をすれば直るかを日本語で書く。
|
|
158
168
|
|
|
159
169
|
★本文は【その回の控え(rec)の実際の値】だけから組み立てる。固定の版番号や、
|
|
@@ -165,7 +175,7 @@ def build_payload(rec, env=None):
|
|
|
165
175
|
kind = notice_kind(rec)
|
|
166
176
|
lines = [
|
|
167
177
|
f'いま止まっているもの: この PC({machine})で、{kind["stopped"]}',
|
|
168
|
-
*recovery_steps(reason),
|
|
178
|
+
*recovery_steps(reason, home),
|
|
169
179
|
'★このカードは管制からは押せません(この PC での操作が要ります)。',
|
|
170
180
|
f'PC: {machine}/うまくいかなかった理由: {reason}',
|
|
171
181
|
f'最後に試した時刻: {rec.get("at", "不明")}',
|
|
@@ -231,13 +241,15 @@ def _close(cfg, datalink, cards, key, path=None):
|
|
|
231
241
|
return True
|
|
232
242
|
|
|
233
243
|
|
|
234
|
-
def run(last_path, cfg=None, datalink=hook_datalink, state_path=None):
|
|
244
|
+
def run(last_path, cfg=None, datalink=hook_datalink, state_path=None, home=None):
|
|
235
245
|
"""返り値: 出したカードのID / None(出さなかった・閉じただけ)。
|
|
236
246
|
|
|
237
247
|
★失敗の回 … 前に出した札を【閉じてから】出し直す。管制は同じ鍵の再登録を寿命の延長
|
|
238
248
|
として扱い、本文は最初のままになる(実地 2026-09-20・札に 4日前の版が出ていた)。
|
|
239
249
|
閉じてから出し直せば、本文は必ずその回の値になる。
|
|
240
250
|
★成功の回 … 出していた札を閉じる。直ったのに札が最大60分出たままにならない。
|
|
251
|
+
|
|
252
|
+
home … 自動更新の鍵を探す住まい(既定は実機の住まい。試験は使い捨てを渡す)。
|
|
241
253
|
"""
|
|
242
254
|
rec = parse_last(last_path)
|
|
243
255
|
status = rec.get('status')
|
|
@@ -256,7 +268,7 @@ def run(last_path, cfg=None, datalink=hook_datalink, state_path=None):
|
|
|
256
268
|
|
|
257
269
|
# 失敗。★古い本文の札が残っていたら先に閉じる(上書きできない作りへの対処)。
|
|
258
270
|
_close(cfg, datalink, cards, key, state_path)
|
|
259
|
-
reg = datalink.register(cfg, build_payload(rec))
|
|
271
|
+
reg = datalink.register(cfg, build_payload(rec, home=home))
|
|
260
272
|
if not reg or not reg.get('id'):
|
|
261
273
|
return None
|
|
262
274
|
cards[key] = reg['id']
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
(i) 成功の回は【出していた札を閉じる】=直ったのに札が最大60分出たままにならない。
|
|
24
24
|
(j) 再開の手順が【うまくいかなかった理由に合っている】=同じ失敗を繰り返させない。
|
|
25
25
|
版のずれに「配り直してください」とだけ言っても、配り元の確認でまた弾かれる。
|
|
26
|
+
|
|
27
|
+
【★便 08af46e0 で塞いだ穴(便 f20dd761 の重大発見2)】
|
|
28
|
+
(k) 自動更新が入っているかの判定は【呼び手が渡した住まい】だけを見る。
|
|
29
|
+
以前は中で実機の住まい(~/.tyhld/bin/autoupdate/enabled)を覗いていたので、
|
|
30
|
+
自動更新が入ったPCでは案内の文が変わり、(j) の判定が機体ごとに通ったり落ちたりした。
|
|
31
|
+
この試験は使い捨ての住まいを渡すので、どのPCでも同じ結果になる。
|
|
26
32
|
"""
|
|
27
33
|
import json
|
|
28
34
|
import os
|
|
@@ -279,12 +285,19 @@ with tempfile.TemporaryDirectory() as tmp:
|
|
|
279
285
|
# ── (j) 再開の手順が理由に合っている ────────────────────────────────────────
|
|
280
286
|
print('\n【(j) 再開の手順が、うまくいかなかった理由に合っている】')
|
|
281
287
|
with tempfile.TemporaryDirectory() as tmp:
|
|
282
|
-
|
|
288
|
+
# ★自動更新の鍵を探す住まいは、必ず【この回だけの使い捨て】を渡す(便 08af46e0)。
|
|
289
|
+
# 既定は実機の住まい(~/.tyhld/bin/autoupdate/enabled)なので、渡さないと
|
|
290
|
+
# 自動更新が入っているPCでは案内が別の文に変わり、同じ試験が機体によって落ちる
|
|
291
|
+
# (便 f20dd761 の重大発見2)。下の PLAIN_HOME は鍵を置かない=自動更新なしのPC。
|
|
292
|
+
PLAIN_HOME = os.path.join(tmp, 'home-plain')
|
|
293
|
+
os.makedirs(PLAIN_HOME, exist_ok=True)
|
|
294
|
+
|
|
295
|
+
def body_for(reason, what='dist-source', home=PLAIN_HOME):
|
|
283
296
|
line = (f'2026-09-20T09:00:00+0900 status=failed reason={reason} what={what} '
|
|
284
297
|
'version=0.12.6 published=0.12.31 root=/n/@tyhld/conductor')
|
|
285
298
|
link = FakeLink()
|
|
286
299
|
sn.run(write(tmp, f'{reason}.last', line + '\n'), datalink=link,
|
|
287
|
-
state_path=state(tmp, f'{reason}.json'))
|
|
300
|
+
state_path=state(tmp, f'{reason}.json'), home=home)
|
|
288
301
|
return link.sent[0]['question']
|
|
289
302
|
|
|
290
303
|
drift = body_for('version-drift')
|
|
@@ -332,21 +345,33 @@ with tempfile.TemporaryDirectory() as tmp:
|
|
|
332
345
|
|
|
333
346
|
# ★自動更新が入っている PC(鍵 ~/.tyhld/bin/autoupdate/enabled)は、人に npm で入れさせない
|
|
334
347
|
# (更新係が管制の決めた版へそろえる。人が入れると段階配布の順番が崩れる)。
|
|
335
|
-
|
|
336
|
-
os.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
# ★判定する住まいは使い捨てを渡す(実機の住まいを覗かない=どのPCでも同じ結果)。
|
|
349
|
+
AUTO_HOME = os.path.join(tmp, 'home-auto')
|
|
350
|
+
os.makedirs(os.path.join(AUTO_HOME, '.tyhld', 'bin', 'autoupdate'), exist_ok=True)
|
|
351
|
+
write(os.path.join(AUTO_HOME, '.tyhld', 'bin', 'autoupdate'), 'enabled', 'x\n')
|
|
352
|
+
auto = body_for('unpublished', home=AUTO_HOME)
|
|
353
|
+
check_true('自動更新の PC: 次の自動更新でそろうと書く', '次の自動更新' in auto)
|
|
354
|
+
check('自動更新の PC: npm で入れさせない', 'npm install -g' in auto, False)
|
|
355
|
+
check_true('自動更新の PC・不具合の印: 人の操作は要らないと書く',
|
|
356
|
+
'人の操作は要りません' in body_for('flagged-suspect', home=AUTO_HOME))
|
|
357
|
+
check_true('自動更新の PC でも、配布物が無いときは入れ直す',
|
|
358
|
+
'npm install -g' in body_for('not-installed', home=AUTO_HOME))
|
|
359
|
+
|
|
360
|
+
# ★判定は【渡した住まい】だけを見る=実機の住まいに鍵が在っても無くても結果が変わらない
|
|
361
|
+
# (便 f20dd761 の重大発見2 の根治。ここが穴だと、この試験は機体ごとに答えが変わる)。
|
|
362
|
+
check('鍵の無い住まい: 自動更新なしと判定', sn.autoupdate_enabled(PLAIN_HOME), False)
|
|
363
|
+
check('鍵の在る住まい: 自動更新ありと判定', sn.autoupdate_enabled(AUTO_HOME), True)
|
|
364
|
+
for fake_home in (PLAIN_HOME, AUTO_HOME, tmp):
|
|
365
|
+
old_home = os.environ.get('HOME')
|
|
366
|
+
os.environ['HOME'] = fake_home
|
|
367
|
+
try:
|
|
368
|
+
check(f'実機の住まいを覗かない(HOME={os.path.basename(fake_home)})',
|
|
369
|
+
sn.autoupdate_enabled(PLAIN_HOME), False)
|
|
370
|
+
finally:
|
|
371
|
+
if old_home is None:
|
|
372
|
+
os.environ.pop('HOME', None)
|
|
373
|
+
else:
|
|
374
|
+
os.environ['HOME'] = old_home
|
|
350
375
|
|
|
351
376
|
# ── ★札の覚え書き(壊れていても・書けなくても止まらない)──────────────────
|
|
352
377
|
print('\n【★札の覚え書き】')
|