model-policy-freshness.ts
58 lines · scripts/model-policy-freshness.ts at 3f79d81
1import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
2import { collectFreshnessSources, type Fetcher } from '../lib/model-policy-freshness-sources';
3import { assessFreshness, freshnessCatalogIdentity, renderFreshnessReport, type FreshnessObservation } from '../lib/model-policy-freshness';
4import { freshnessGitHub } from '../lib/model-policy-freshness-github';
5import { publishFreshness, type FreshnessGitHub } from '../lib/model-policy-freshness-publication';
7export async function runModelPolicyFreshness(options: {
8 env?: NodeJS.ProcessEnv; fetcher?: Fetcher; github?: FreshnessGitHub; now?: () => string; publish?: boolean;
9}) {
10 const env = options.env ?? process.env;
11 const now = options.now ?? (() => new Date().toISOString());
12 const startedAt = now();
13 const catalog = freshnessCatalogIdentity(readFileSync(new URL('../lib/model-catalog.ts', import.meta.url), 'utf8'));
14 const sources = await collectFreshnessSources(options.fetcher);
15 const observation: FreshnessObservation = {
16 catalog,
17 checkedAt: now(), run: { id: env.GITHUB_RUN_ID ?? '0', attempt: Number(env.GITHUB_RUN_ATTEMPT ?? '1'), startedAt, headSha: env.GITHUB_SHA ?? '0'.repeat(40) }, sources,
18 };
19 let report = assessFreshness(observation);
20 let publication;
21 if (options.publish) {
22 if (!env.GITHUB_REF) throw new Error('publication-ref-missing');
23 if (env.GITHUB_REF === 'refs/heads/main' && (!env.GITHUB_REPOSITORY || !env.GH_TOKEN || !env.GITHUB_RUN_ID || !env.GITHUB_SHA)) throw new Error('github-publication-prerequisites-missing');
24 try {
25 const github = options.github ?? (env.GITHUB_REF === 'refs/heads/main' ? freshnessGitHub(env.GITHUB_REPOSITORY!, env.GH_TOKEN!, options.fetcher) : null);
26 publication = github ? await publishFreshness(observation, github, env.GITHUB_REF) : { action: 'report-only' as const };
27 if ('report' in publication && publication.report) report = publication.report;
28 } catch (error) {
29 const reason = error instanceof Error && /^[a-z0-9: /.-]{1,160}$/i.test(error.message) ? error.message : 'publication-unavailable';
30 publication = { action: 'failed' as const, error: reason };
31 report = { ...report, status: 'unknown/source-unavailable', complete: false, errors: [...report.errors, `publication:${reason}`] };
32 }
33 }
34 return { schemaVersion: 1, observation, report, publication };
35}
37async function main(): Promise<number> {
38 const args = process.argv.slice(2);
39 if (args.some(arg => !['--publish', '--help'].includes(arg))) { console.error('Usage: bun scripts/model-policy-freshness.ts [--publish]'); return 2; }
40 if (args.includes('--help')) {
41 console.log('Check four official public model recommendation/lifecycle sources without paid calls. Default: report-only. --publish requires main/default-branch identity and GITHUB_TOKEN-backed GH_TOKEN; no catalog or pin writes. JSON goes to stdout. Optional MODEL_FRESHNESS_REPORT writes the same artifact; GITHUB_STEP_SUMMARY receives the human report.');
42 return 0;
43 }
44 const result = await runModelPolicyFreshness({ publish: args.includes('--publish') });
45 const output = `${JSON.stringify(result, null, 2)}\n`;
46 if (process.env.MODEL_FRESHNESS_REPORT) writeFileSync(process.env.MODEL_FRESHNESS_REPORT, output, { mode: 0o600 });
47 if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${renderFreshnessReport(result.report)}\n`);
48 console.log(output.trimEnd());
49 if (result.publication?.action.startsWith('rejected-') || result.publication?.action === 'failed') return 1;
50 return result.report.status === 'unknown/source-unavailable' || result.report.status === 'stale' ? 1 : 0;
51}
53if (import.meta.main) {
54 main().then(code => { process.exitCode = code; }).catch(() => {
55 console.error('Model-policy freshness or report delivery failed. Confirm the tracking issue state; do not infer freshness from this run. Check the workflow logs, official sources, issue permissions/state, and docs/troubleshooting.md#model-policy-freshness.');
56 process.exitCode = 1;
57 });
58}