feat(ms-ai-architect): Sesjon 24 — C1 Tier 2 lokal launchd-scheduler (deteksjon-only, ToS-trygt) [skip-docs]
Tier 1 (SessionStart-hook) kjører deteksjon kun når en Claude-sesjon starter.
Tier 2 installerer en lokal launchd LaunchAgent som fyrer SAMME Claude-frie
entrypoint daglig (kl. 03:00), uavhengig av sesjoner = ekte bakgrunn mellom
sesjoner. ToS-trygt: entrypointet kan strukturelt ikke invokere claude.
Søk-først-verifisert (2026-06): launchd > cron (Apple-anbefalt; cron krever
Full Disk Access); StartCalendarInterval fanger opp etter dvale; launchd har
minimalt PATH + ekspanderer ikke ~; process.execPath = Cellar-sti som dør ved
`brew upgrade node` → plistens PATH inkluderer /usr/local/bin.
Levert (TDD, test FØR kode):
- lib/launchd-plist.mjs (REN, SKRIVER ALDRI): renderPlist (XML-escape,
StartCalendarInterval, PATH-fix), defaultLabel, resolvePaths.
- scheduler.mjs (TYNN CLI): install|uninstall|status|run-now|print|run.
Strukturelt Claude-fri (kun process.execPath på run-detection.mjs +
launchctl; kilde-grep-testet). run gater på os_scheduler_cadence →
delegerer til run-detection.mjs. uid-0-nekt; idempotent bootout.
- detection-schedule.mjs (additiv, Tier-1 urørt): os_scheduler_cadence
('daily' default | 'interval') + daysSinceLastPoll ren helper.
- commands/kb-update.md: Tier-1/Tier-2-doc + caveats (uninstall=pause;
re-install etter node-upgrade).
Operatør-valg: daglig default, kadens egen innstilling (settes i onboarding/C2).
Verifisert: launchd-plist 13/13 · detection-schedule 21/21 · kb-update 173 ·
kb-eval 100 · validate 239/0 · test-hooks 6/6 · plutil -lint OK · live
install→status→idempotent→uninstall på maskinen (gui/501, rent).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f8a160585f
commit
071601bb9e
6 changed files with 624 additions and 5 deletions
|
|
@ -23,6 +23,7 @@ import {
|
|||
loadScheduleConfig,
|
||||
shouldRunDetection,
|
||||
summarizeSkillLifecycle,
|
||||
daysSinceLastPoll,
|
||||
} from '../../scripts/kb-update/lib/detection-schedule.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -73,6 +74,36 @@ test('parseScheduleConfig — malformed input never throws, falls back to OFF',
|
|||
'a non-boolean enabled is treated as OFF');
|
||||
});
|
||||
|
||||
// --- Tier 2 (launchd) cadence: os_scheduler_cadence (Sesjon 24) ---
|
||||
|
||||
test('DEFAULT_SCHEDULE_CONFIG — os_scheduler_cadence defaults to daily', () => {
|
||||
assert.equal(DEFAULT_SCHEDULE_CONFIG.os_scheduler_cadence, 'daily');
|
||||
});
|
||||
|
||||
test('parseScheduleConfig — reads os_scheduler_cadence: interval', () => {
|
||||
const cfg = parseScheduleConfig('scheduled_detection:\n os_scheduler_cadence: interval\n');
|
||||
assert.equal(cfg.os_scheduler_cadence, 'interval');
|
||||
});
|
||||
|
||||
test('parseScheduleConfig — unknown/garbage cadence falls back to daily', () => {
|
||||
assert.equal(parseScheduleConfig('scheduled_detection:\n os_scheduler_cadence: weekly\n').os_scheduler_cadence, 'daily');
|
||||
assert.equal(parseScheduleConfig('scheduled_detection:\n enabled: true\n').os_scheduler_cadence, 'daily',
|
||||
'unspecified cadence => daily default');
|
||||
});
|
||||
|
||||
test('daysSinceLastPoll — Infinity when report missing or last_poll absent/invalid', () => {
|
||||
const now = Date.UTC(2026, 5, 21);
|
||||
assert.equal(daysSinceLastPoll(null, now), Infinity);
|
||||
assert.equal(daysSinceLastPoll({}, now), Infinity);
|
||||
assert.equal(daysSinceLastPoll({ last_poll: 'not-a-date' }, now), Infinity);
|
||||
});
|
||||
|
||||
test('daysSinceLastPoll — computes whole days since last_poll', () => {
|
||||
const now = Date.UTC(2026, 5, 21, 0, 0, 0);
|
||||
const tenDaysAgo = new Date(Date.UTC(2026, 5, 11, 0, 0, 0)).toISOString();
|
||||
assert.equal(daysSinceLastPoll({ last_poll: tenDaysAgo }, now), 10);
|
||||
});
|
||||
|
||||
test('loadScheduleConfig — missing config file => OFF default', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'sched-'));
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue