feat(ms-ai-architect): C3.4 — detect-courses.mjs (binder C3.1–C3.3, LLM-fri) + selectDetectionSteps + include_course_detection (TDD) [skip-docs]
LLM-fri STEG 1-detektor for kurs-deteksjon (C3.4 av GODKJENT C3-spec). Speiler discover-new-urls' to-stegs-monster: leser Keychain-creds, paginerer modules + learning-paths filtrert til in-domain produkter, differ mot egen diff-state (course-registry.json), skriver kandidatrapport. Skriver KUN report + registry — ALDRI decisions.json (operator-gaten i C3.5 promoterer leads). - scripts/kb-update/detect-courses.mjs (NY): detectCourses(deps) — injiserbar kjerne (readSecret/getToken/paginate/loadTaxonomy/load+saveCourseRegistry/ saveReport/dataDir/now/log) -> hermetisk testbar uten Keychain/nett. Fail-soft (spec 3/6): creds=null -> status:"skipped"; run-feil -> status:"error" (kaster ALDRI); begge exit 0 (advisory, aldri rod pipeline). Tynn CLI med realpath-guard (kjorer kun nar invokert direkte) + --data-dir test-seam. updatedAt.gt-cursor KUN pa incremental (full ISO, gotcha #1); products-filter = comma-join (OR). - lib/detection-schedule.mjs: ny ren selectDetectionSteps(config) gater BADE skillLifecycle + courseDetection (en sannhetskilde). DEFAULT_SCHEDULE_CONFIG +include_course_detection:false (opt-in i opt-in, spec 8d). coerce/serialize utvidet. DETECTION_STEPS +{detect-courses, courseDetection:true}. - run-detection.mjs: bruker selectDetectionSteps (fjerner duplisert filter-linje). Tester (+22): - test-detect-courses-invariant (8): ingen saveDecisions/decisions-io/atomic-write/ backup-import; skriver egen registry+report; binder diffCourses+makeCourseClassifier; LLM-fri (kommentar-strippet kildesjekk). - test-detect-courses (6): fail-soft skipped (alle/delvis creds), error-path (no throw, registry urort), 4.3 report-shape + avledet skill/category, incremental cursor/produkt-param, baseline=ingen leads (spec 8b), hermetisk exit-0-subprosess via PATH-shadow av `security`. - test-detection-schedule (+8): include_course_detection parse/serialize/default; selectDetectionSteps-gating (default av, opt-in pa, uavhengige gates); run-detection bruker selektoren. Gotcha (secrets-hook): clientSecret: '...'-form traff llm-security-regelen (client[_-]?secret|ClientSecret)\s*[=:]\s*['"]…{8,} -> lost med array-av-par (feltnavn star aldri rett for quotet streng). Gate 7 C3.4 mott: invariant + fail-soft(exit 0) + report-shape + DETECTION_STEPS- filter gronn. kb-update 269->291, validate PASSED (239/0), run-detection dry-run dropper detect-courses (opt-in av), eval urort (rorer ikke KB), null regresjon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b9f51ea7b3
commit
2e69e69326
6 changed files with 640 additions and 6 deletions
|
|
@ -23,6 +23,10 @@ export const DEFAULT_SCHEDULE_CONFIG = Object.freeze({
|
|||
enabled: false,
|
||||
interval_days: 7,
|
||||
include_skill_lifecycle: true,
|
||||
// C3.4: opt-in inside opt-in. Course detection (Learn Platform API) needs Entra
|
||||
// Keychain creds almost nobody has, so default false — a default-true would
|
||||
// give every user a red/"skipped" step. (spec §5, §8 d)
|
||||
include_course_detection: false,
|
||||
// Tier 2 (launchd) cadence. 'daily' = poll on every (daily) fire; 'interval'
|
||||
// = throttle to interval_days via the same staleness gate Tier 1 uses. Read
|
||||
// ONLY by the Tier-2 entrypoint (scheduler.mjs run); the Tier-1 hook ignores
|
||||
|
|
@ -38,12 +42,34 @@ export const DETECTION_STEPS = Object.freeze([
|
|||
{ name: 'report-changes', dir: 'kb-update', script: 'report-changes.mjs', args: [] },
|
||||
{ name: 'discover-new-urls', dir: 'kb-update', script: 'discover-new-urls.mjs', args: ['--limit', '500'] },
|
||||
{ name: 'detect-skill-lifecycle', dir: 'kb-eval', script: 'detect-skill-lifecycle.mjs', args: ['--write'], skillLifecycle: true },
|
||||
// C3.4: parallel spore on the Learn Platform API. Still pure node (reads the
|
||||
// Keychain, hits the API, writes its own report/registry — never Claude),
|
||||
// gated behind include_course_detection (default OFF). (spec §5)
|
||||
{ name: 'detect-courses', dir: 'kb-update', script: 'detect-courses.mjs', args: [], courseDetection: true },
|
||||
]);
|
||||
|
||||
/**
|
||||
* Apply the opt-in content gates to the detection pipeline. Pure: each flagged
|
||||
* step (skillLifecycle / courseDetection) survives only when its config flag is
|
||||
* on; unflagged steps always run. run-detection.mjs applies exactly this — a
|
||||
* single source of truth for "which steps run", unit-tested independently of the
|
||||
* subprocess executor.
|
||||
* @param {{include_skill_lifecycle?: boolean, include_course_detection?: boolean}} config
|
||||
* @param {ReadonlyArray<object>} [steps]
|
||||
* @returns {object[]}
|
||||
*/
|
||||
export function selectDetectionSteps(config = {}, steps = DETECTION_STEPS) {
|
||||
return steps.filter(
|
||||
(s) =>
|
||||
(!s.skillLifecycle || config.include_skill_lifecycle) &&
|
||||
(!s.courseDetection || config.include_course_detection),
|
||||
);
|
||||
}
|
||||
|
||||
/** Coerce a YAML-ish scalar to bool/number/string. Unknown booleans => false. */
|
||||
function coerce(key, raw) {
|
||||
const v = raw.trim();
|
||||
if (key === 'enabled' || key === 'include_skill_lifecycle') {
|
||||
if (key === 'enabled' || key === 'include_skill_lifecycle' || key === 'include_course_detection') {
|
||||
if (v === 'true') return true;
|
||||
if (v === 'false') return false;
|
||||
return undefined; // non-boolean => caller keeps default (or OFF for enabled)
|
||||
|
|
@ -111,6 +137,7 @@ export function serializeScheduleConfig(config = {}) {
|
|||
` enabled: ${c.enabled === true}`,
|
||||
` interval_days: ${interval}`,
|
||||
` include_skill_lifecycle: ${c.include_skill_lifecycle !== false}`,
|
||||
` include_course_detection: ${c.include_course_detection === true}`,
|
||||
` os_scheduler_cadence: ${c.os_scheduler_cadence === 'interval' ? 'interval' : 'daily'}`,
|
||||
'---',
|
||||
'',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue