ms-ai-architect/scripts/kb-update/lib/authority.mjs
Kjell Tore Guttormsen e42a84fb7b feat(ms-ai-architect): Sesjon 6 - lag 3 verifisering-INN (authority-binding)
Lag 3 etablerer per-fil autoritetsbinding som mater verify-out regel 3 (autoritets-mismatch — regresjonens rotårsak, inert til nå pga 0% dekning). Designvalg (operatør 2026-06-19): HEADER-AS-TRUTH — en fils **Source:**-header ER den utpekte autoriteten. Gjetter ALDRI fra siterte URLer (251/303 filer siterer 4-10 → gjetning ville fått regel 3 til å bomme + 303 operatør-beslutninger = primær-fallgruven).

- lib/authority.mjs (ren lib, SKRIVER ALDRI; kun read-only kb-headers+url-normalize): resolveAuthority (validert http(s); fri tekst avvist; ikke-MS-autoritet som EU-forordning tillatt), collectDeclaredSources (normalisert Set), markUrlAuthority, authorityCoverage. - build-registry: recomputer authority_source per URL-entry fra headere (ikke carry-over) + dekningslogg. - commands/kb-update.md §4 c1: authority_source=resolveAuthority(fil) → lag-5 c2.

TDD: 11 tester FØR kode inkl. import-invariant + ende-til-ende regel 3 (source_url ≠ header-autoritet → flagged). Tester: validate 239 · kb-update 122 (+11) · kb-eval 15 · kb-integrity 115/115.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REiKFhP4w6xGXXqWKpPCJJ
2026-06-19 22:58:10 +02:00

80 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// authority.mjs — Lag 3: VERIFISERING-INN. Establishes the per-file authority
// binding that feeds verify-out rule 3 (authority mismatch — the regression's
// root cause) and build-registry's per-URL authority_source field.
//
// Design (operatør-valg 2026-06-19): HEADER-AS-TRUTH. A file's **Source:** header
// IS the designated authority for that file's claims — full stop. We never GUESS
// an authority from a file's many cited URLs (251/303 files cite 410 URLs, so a
// derivation would be a guess, and a wrong authority makes rule 3 misfire). Coverage
// grows as files adopt the header (lag 4 writes it for every regenerated file).
//
// Pure lib (mirrors decisions-io / verify-out / transform): the only imports are
// read-only sibling parsers (kb-headers, url-normalize). It NEVER writes.
import { parseSourceHeader } from './kb-headers.mjs';
import { normalizeUrl } from './url-normalize.mjs';
const RE_HTTP_URL = /^https?:\/\/\S+$/i;
/**
* The designated authority for a file's claims = its declared **Source:** header,
* IF that value is a real http(s) URL. A free-text "source" is not an authority
* binding (verifiseringsplikt: no guessed authority). The URL is returned as
* declared (not normalized) — it may legitimately be a non-Microsoft authority
* (e.g. an EU regulation for a governance file); verify-out normalizes when it
* compares. Returns null when absent or not a URL.
* @param {string} content — full reference-file content (header scanned)
* @returns {string|null}
*/
export function resolveAuthority(content) {
const src = parseSourceHeader(content);
if (!src) return null;
return RE_HTTP_URL.test(src.trim()) ? src.trim() : null;
}
/**
* Collect the set of declared authority sources across many files, normalized to
* match registry keys. Only learn.microsoft.com authorities survive normalization
* (normalizeUrl returns null otherwise) — which is correct, since the registry
* only tracks Microsoft Learn URLs. A non-MS authority is a valid per-file binding
* but is simply not a tracked registry entry to mark.
* @param {string[]} contents — reference-file contents
* @returns {Set<string>} normalized declared-source URLs
*/
export function collectDeclaredSources(contents) {
const set = new Set();
for (const c of contents || []) {
const a = resolveAuthority(c);
if (!a) continue;
const n = normalizeUrl(a);
if (n) set.add(n);
}
return set;
}
/**
* The authority_source value for a registry URL entry: the URL itself when some
* file declares it as its **Source:** (i.e. this URL is a designated authority),
* else null. The registry is URL-keyed, so storing the URL is type-consistent with
* the change.authority_source field rule 3 consumes; null keeps the field honest
* where no file vouches for the URL.
* @param {string} normUrl — a normalized registry URL key
* @param {Set<string>} declaredSet — output of collectDeclaredSources
* @returns {string|null}
*/
export function markUrlAuthority(normUrl, declaredSet) {
return declaredSet && declaredSet.has(normUrl) ? normUrl : null;
}
/**
* Coverage metric for the criterion ("authority_source-dekning > 0%").
* @param {{urls: object}} registry
* @returns {{withAuthority: number, total: number, ratio: number}}
*/
export function authorityCoverage(registry) {
const urls = (registry && registry.urls) || {};
const entries = Object.values(urls);
const withAuthority = entries.filter((e) => e && e.authority_source).length;
const total = entries.length;
return { withAuthority, total, ratio: total ? withAuthority / total : 0 };
}