@xenos1996/usa 2.0.0
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/LICENSE +21 -0
- package/README.md +335 -0
- package/USA.md +597 -0
- package/dist/bootstrap/catalog.d.ts +46 -0
- package/dist/bootstrap/catalog.d.ts.map +1 -0
- package/dist/bootstrap/catalog.js +312 -0
- package/dist/bootstrap/catalog.js.map +1 -0
- package/dist/bootstrap/index.d.ts +36 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +177 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +706 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +88 -0
- package/dist/config.js.map +1 -0
- package/dist/detect/index.d.ts +58 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +329 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/engine/audit.d.ts +32 -0
- package/dist/engine/audit.d.ts.map +1 -0
- package/dist/engine/audit.js +366 -0
- package/dist/engine/audit.js.map +1 -0
- package/dist/engine/diff.d.ts +24 -0
- package/dist/engine/diff.d.ts.map +1 -0
- package/dist/engine/diff.js +136 -0
- package/dist/engine/diff.js.map +1 -0
- package/dist/engine/evaluate.d.ts +19 -0
- package/dist/engine/evaluate.d.ts.map +1 -0
- package/dist/engine/evaluate.js +377 -0
- package/dist/engine/evaluate.js.map +1 -0
- package/dist/engine/gate.d.ts +19 -0
- package/dist/engine/gate.d.ts.map +1 -0
- package/dist/engine/gate.js +47 -0
- package/dist/engine/gate.js.map +1 -0
- package/dist/engine/loader.d.ts +37 -0
- package/dist/engine/loader.d.ts.map +1 -0
- package/dist/engine/loader.js +388 -0
- package/dist/engine/loader.js.map +1 -0
- package/dist/engine/maturity.d.ts +20 -0
- package/dist/engine/maturity.d.ts.map +1 -0
- package/dist/engine/maturity.js +157 -0
- package/dist/engine/maturity.js.map +1 -0
- package/dist/engine/score.d.ts +19 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +141 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/sections.d.ts +14 -0
- package/dist/engine/sections.d.ts.map +1 -0
- package/dist/engine/sections.js +50 -0
- package/dist/engine/sections.js.map +1 -0
- package/dist/evolution/benchmark.d.ts +12 -0
- package/dist/evolution/benchmark.d.ts.map +1 -0
- package/dist/evolution/benchmark.js +107 -0
- package/dist/evolution/benchmark.js.map +1 -0
- package/dist/evolution/capability.d.ts +22 -0
- package/dist/evolution/capability.d.ts.map +1 -0
- package/dist/evolution/capability.js +69 -0
- package/dist/evolution/capability.js.map +1 -0
- package/dist/evolution/coverage.d.ts +15 -0
- package/dist/evolution/coverage.d.ts.map +1 -0
- package/dist/evolution/coverage.js +65 -0
- package/dist/evolution/coverage.js.map +1 -0
- package/dist/evolution/gap.d.ts +10 -0
- package/dist/evolution/gap.d.ts.map +1 -0
- package/dist/evolution/gap.js +52 -0
- package/dist/evolution/gap.js.map +1 -0
- package/dist/evolution/propose.d.ts +50 -0
- package/dist/evolution/propose.d.ts.map +1 -0
- package/dist/evolution/propose.js +188 -0
- package/dist/evolution/propose.js.map +1 -0
- package/dist/evolution/queue.d.ts +64 -0
- package/dist/evolution/queue.d.ts.map +1 -0
- package/dist/evolution/queue.js +192 -0
- package/dist/evolution/queue.js.map +1 -0
- package/dist/evolution/release.d.ts +11 -0
- package/dist/evolution/release.d.ts.map +1 -0
- package/dist/evolution/release.js +55 -0
- package/dist/evolution/release.js.map +1 -0
- package/dist/evolution/run.d.ts +88 -0
- package/dist/evolution/run.d.ts.map +1 -0
- package/dist/evolution/run.js +258 -0
- package/dist/evolution/run.js.map +1 -0
- package/dist/evolution/schedule.d.ts +59 -0
- package/dist/evolution/schedule.d.ts.map +1 -0
- package/dist/evolution/schedule.js +77 -0
- package/dist/evolution/schedule.js.map +1 -0
- package/dist/evolution/types.d.ts +174 -0
- package/dist/evolution/types.d.ts.map +1 -0
- package/dist/evolution/types.js +8 -0
- package/dist/evolution/types.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/learn/index.d.ts +27 -0
- package/dist/learn/index.d.ts.map +1 -0
- package/dist/learn/index.js +196 -0
- package/dist/learn/index.js.map +1 -0
- package/dist/report/markdown.d.ts +16 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +488 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/snapshot/index.d.ts +55 -0
- package/dist/snapshot/index.d.ts.map +1 -0
- package/dist/snapshot/index.js +76 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/store/index.d.ts +31 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +69 -0
- package/dist/store/index.js.map +1 -0
- package/dist/types.d.ts +287 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/util/glob.d.ts +11 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +86 -0
- package/dist/util/glob.js.map +1 -0
- package/dist/util/project.d.ts +90 -0
- package/dist/util/project.d.ts.map +1 -0
- package/dist/util/project.js +636 -0
- package/dist/util/project.js.map +1 -0
- package/dist/util/yaml.d.ts +28 -0
- package/dist/util/yaml.d.ts.map +1 -0
- package/dist/util/yaml.js +48 -0
- package/dist/util/yaml.js.map +1 -0
- package/package.json +94 -0
- package/rules/core/architecture.yaml +190 -0
- package/rules/core/cicd.yaml +212 -0
- package/rules/core/code-quality.yaml +261 -0
- package/rules/core/dependencies.yaml +132 -0
- package/rules/core/documentation.yaml +119 -0
- package/rules/core/future-readiness.yaml +91 -0
- package/rules/core/release.yaml +131 -0
- package/rules/core/repo.yaml +313 -0
- package/rules/core/security.yaml +496 -0
- package/rules/core/supply-chain.yaml +357 -0
- package/rules/core/testing.yaml +266 -0
- package/rules/detectors.yaml +2028 -0
- package/rules/index.yaml +42 -0
- package/rules/profiles/maturity.yaml +135 -0
- package/rules/stacks/ai-era.yaml +193 -0
- package/rules/stacks/api-backend.yaml +121 -0
- package/rules/stacks/cli.yaml +105 -0
- package/rules/stacks/compliance.yaml +157 -0
- package/rules/stacks/containers.yaml +124 -0
- package/rules/stacks/data.yaml +165 -0
- package/rules/stacks/go.yaml +134 -0
- package/rules/stacks/iac.yaml +128 -0
- package/rules/stacks/jvm.yaml +150 -0
- package/rules/stacks/ml-ai.yaml +140 -0
- package/rules/stacks/mobile.yaml +121 -0
- package/rules/stacks/node-typescript.yaml +166 -0
- package/rules/stacks/python.yaml +175 -0
- package/rules/stacks/rust.yaml +129 -0
- package/rules/stacks/solidity.yaml +139 -0
- package/rules/stacks/swift.yaml +100 -0
- package/rules/stacks/web-frontend.yaml +159 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Suggestion } from '../learn/index.js';
|
|
2
|
+
import type { CapabilityGap, CandidateCapability, CoverageModel } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The PROPOSAL stage of the evolution loop.
|
|
5
|
+
*
|
|
6
|
+
* A coverage blind spot yields a `CapabilityGap`; this module turns that gap
|
|
7
|
+
* into a concrete, *unreleased* `CandidateCapability` — deterministically and
|
|
8
|
+
* without any LLM. The proposal is drawn from the same bootstrap catalog that
|
|
9
|
+
* `usa bootstrap` uses, so there is exactly one source of truth for "what could
|
|
10
|
+
* cover this language".
|
|
11
|
+
*
|
|
12
|
+
* This stage never registers anything: the candidate carries a
|
|
13
|
+
* REVIEW-REQUIRED pack and status `PROPOSED`, and is only trusted after it
|
|
14
|
+
* passes the benchmark + release gate. Producer ≠ judge holds end to end.
|
|
15
|
+
*/
|
|
16
|
+
export interface ProposeOptions {
|
|
17
|
+
/** Called with the language to propose a capability for. */
|
|
18
|
+
language?: string;
|
|
19
|
+
/** Who/what authored the proposal (provenance only). */
|
|
20
|
+
createdBy?: string;
|
|
21
|
+
/** Override the clock for reproducible tests. */
|
|
22
|
+
now?: string;
|
|
23
|
+
/** Emit a reason when no candidate can be proposed. */
|
|
24
|
+
explain?: (reason: string) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Propose a candidate capability that would close `gap`. Only
|
|
28
|
+
* `unsupported-technology` gaps are proposable today, and only when the
|
|
29
|
+
* bootstrap catalog (or its fallback) can render a pack for the language.
|
|
30
|
+
* Returns `null` when the gap is not proposable — never a half-built candidate.
|
|
31
|
+
*/
|
|
32
|
+
export declare function proposeCandidate(gap: CapabilityGap, opts?: ProposeOptions): CandidateCapability | null;
|
|
33
|
+
/**
|
|
34
|
+
* Propose candidates for every open gap in a coverage model. Gaps that cannot
|
|
35
|
+
* be turned into a candidate are reported via `onUnproposable` and skipped —
|
|
36
|
+
* the caller decides whether that is fatal.
|
|
37
|
+
*/
|
|
38
|
+
export declare function proposeCandidates(coverage: CoverageModel, gaps: CapabilityGap[], opts?: ProposeOptions & {
|
|
39
|
+
onUnproposable?: (gap: CapabilityGap, reason: string) => void;
|
|
40
|
+
}): CandidateCapability[];
|
|
41
|
+
/**
|
|
42
|
+
* Turn `usa learn` suggestions into a single candidate capability pack of
|
|
43
|
+
* manual checks. Manual checks are honest: they assert nothing automatically,
|
|
44
|
+
* they queue a judgement. They are the correct proposal when the engine cannot
|
|
45
|
+
* express the missing check as data yet. Still PROPOSED, still REVIEW-REQUIRED.
|
|
46
|
+
*/
|
|
47
|
+
export declare function proposeFromSuggestions(suggestions: Suggestion[], opts?: ProposeOptions & {
|
|
48
|
+
packId?: string;
|
|
49
|
+
}): CandidateCapability | null;
|
|
50
|
+
//# sourceMappingURL=propose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose.d.ts","sourceRoot":"","sources":["../../src/evolution/propose.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpF;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE,cAAmB,GACxB,mBAAmB,GAAG,IAAI,CAsB5B;AA4CD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,aAAa,EAAE,EACrB,IAAI,GAAE,cAAc,GAAG;IAAE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC5F,mBAAmB,EAAE,CAyBvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,UAAU,EAAE,EACzB,IAAI,GAAE,cAAc,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,mBAAmB,GAAG,IAAI,CA+C5B"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { bootstrapPacks } from '../bootstrap/index.js';
|
|
2
|
+
import { parsePackText } from '../engine/loader.js';
|
|
3
|
+
import { capabilityFromPack } from './capability.js';
|
|
4
|
+
/**
|
|
5
|
+
* Propose a candidate capability that would close `gap`. Only
|
|
6
|
+
* `unsupported-technology` gaps are proposable today, and only when the
|
|
7
|
+
* bootstrap catalog (or its fallback) can render a pack for the language.
|
|
8
|
+
* Returns `null` when the gap is not proposable — never a half-built candidate.
|
|
9
|
+
*/
|
|
10
|
+
export function proposeCandidate(gap, opts = {}) {
|
|
11
|
+
const lang = proposableLanguage(gap, opts);
|
|
12
|
+
if (lang === null)
|
|
13
|
+
return null;
|
|
14
|
+
const pack = bootstrapPackFor(gap.requiredCapability, lang, opts);
|
|
15
|
+
if (!pack)
|
|
16
|
+
return null;
|
|
17
|
+
const capability = capabilityFromPack(pack, {
|
|
18
|
+
version: pack.version ?? '0.1.0-proposed',
|
|
19
|
+
description: pack.description,
|
|
20
|
+
createdBy: opts.createdBy ?? 'evolution:propose',
|
|
21
|
+
createdAt: opts.now,
|
|
22
|
+
languages: [lang],
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
id: capability.id,
|
|
26
|
+
capability,
|
|
27
|
+
gapIds: [gap.id],
|
|
28
|
+
status: 'PROPOSED',
|
|
29
|
+
createdAt: opts.now ?? new Date().toISOString(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The language a gap can be proposed for, or null (with an explanation). */
|
|
33
|
+
function proposableLanguage(gap, opts) {
|
|
34
|
+
if (gap.kind !== 'unsupported-technology') {
|
|
35
|
+
opts.explain?.(`gap ${gap.id} is not a technology gap (kind: ${gap.kind})`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const lang = opts.language ?? gap.target;
|
|
39
|
+
if (lang !== gap.target) {
|
|
40
|
+
opts.explain?.(`requested language ${lang} does not match gap target ${gap.target}`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return lang;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Render the bootstrap starter pack for `lang` and parse it back into a
|
|
47
|
+
* RulePack. The bootstrap generator is the single source of truth for a
|
|
48
|
+
* starter pack; we feed it exactly the fact the gap was derived from so the
|
|
49
|
+
* proposal carries the same `skip_when: lang:<lang> absent` guard as a real one.
|
|
50
|
+
*/
|
|
51
|
+
function bootstrapPackFor(requiredCapability, lang, opts) {
|
|
52
|
+
const outcome = bootstrapPacks({ flags: new Set([`lang:${lang}`]), metrics: {} });
|
|
53
|
+
const proposal = outcome.packs.find((p) => p.packId === requiredCapability);
|
|
54
|
+
if (!proposal) {
|
|
55
|
+
opts.explain?.(`no bootstrap proposal exists for ${lang} (${requiredCapability})`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const warnings = [];
|
|
59
|
+
const pack = parsePackText(proposal.yaml, proposal.filename, new Map(), warnings);
|
|
60
|
+
if (!pack) {
|
|
61
|
+
opts.explain?.(`bootstrap proposal for ${lang} did not parse as a rule pack: ${warnings.join('; ')}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return pack;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Propose candidates for every open gap in a coverage model. Gaps that cannot
|
|
68
|
+
* be turned into a candidate are reported via `onUnproposable` and skipped —
|
|
69
|
+
* the caller decides whether that is fatal.
|
|
70
|
+
*/
|
|
71
|
+
export function proposeCandidates(coverage, gaps, opts = {}) {
|
|
72
|
+
const out = [];
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
// Prefer explicit language order from the coverage model so results are
|
|
75
|
+
// deterministic regardless of map/Set iteration in the caller.
|
|
76
|
+
const order = new Map(coverage.detectedLanguages.map((lang, i) => [lang, i]));
|
|
77
|
+
const ordered = [...gaps].sort((a, b) => {
|
|
78
|
+
const ai = order.get(a.target) ?? Number.MAX_SAFE_INTEGER;
|
|
79
|
+
const bi = order.get(b.target) ?? Number.MAX_SAFE_INTEGER;
|
|
80
|
+
return ai - bi || a.id.localeCompare(b.id);
|
|
81
|
+
});
|
|
82
|
+
for (const gap of ordered) {
|
|
83
|
+
if (gap.state !== 'open')
|
|
84
|
+
continue;
|
|
85
|
+
if (seen.has(gap.requiredCapability))
|
|
86
|
+
continue;
|
|
87
|
+
const candidate = proposeCandidate(gap, opts);
|
|
88
|
+
if (!candidate) {
|
|
89
|
+
opts.onUnproposable?.(gap, `no proposal for ${gap.requiredCapability}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
seen.add(gap.requiredCapability);
|
|
93
|
+
out.push(candidate);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Turn `usa learn` suggestions into a single candidate capability pack of
|
|
99
|
+
* manual checks. Manual checks are honest: they assert nothing automatically,
|
|
100
|
+
* they queue a judgement. They are the correct proposal when the engine cannot
|
|
101
|
+
* express the missing check as data yet. Still PROPOSED, still REVIEW-REQUIRED.
|
|
102
|
+
*/
|
|
103
|
+
export function proposeFromSuggestions(suggestions, opts = {}) {
|
|
104
|
+
if (suggestions.length === 0) {
|
|
105
|
+
opts.explain?.('no learn suggestions to propose from');
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const packId = opts.packId ?? 'learn-proposals';
|
|
109
|
+
const rules = suggestions.map((s) => ({
|
|
110
|
+
id: s.id,
|
|
111
|
+
title: s.title,
|
|
112
|
+
section: s.section,
|
|
113
|
+
sectionTitle: s.sectionTitle,
|
|
114
|
+
severity: s.severity,
|
|
115
|
+
ruleClass: toRuleClass(s.ruleClass),
|
|
116
|
+
check: { kind: 'manual' },
|
|
117
|
+
why: s.why,
|
|
118
|
+
remediation: s.remediation,
|
|
119
|
+
evidence: s.evidence,
|
|
120
|
+
references: s.references,
|
|
121
|
+
}));
|
|
122
|
+
const pack = parsePackText(renderProposalPackYaml(packId, rules), `${packId}.yaml`, new Map(), []);
|
|
123
|
+
if (!pack) {
|
|
124
|
+
opts.explain?.(`could not render proposal pack ${packId}`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const capability = capabilityFromPack(pack, {
|
|
128
|
+
version: pack.version ?? '0.1.0-proposed',
|
|
129
|
+
description: 'Manual checks proposed by usa learn from open findings.',
|
|
130
|
+
createdBy: opts.createdBy ?? 'evolution:propose:learn',
|
|
131
|
+
createdAt: opts.now,
|
|
132
|
+
languages: [],
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
id: capability.id,
|
|
136
|
+
capability,
|
|
137
|
+
gapIds: suggestions.map((s) => s.triggeredBy),
|
|
138
|
+
status: 'PROPOSED',
|
|
139
|
+
createdAt: opts.now ?? new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const VALID_RULE_CLASSES = new Set([
|
|
143
|
+
'security',
|
|
144
|
+
'supply-chain',
|
|
145
|
+
'correctness',
|
|
146
|
+
'maintainability',
|
|
147
|
+
'operations',
|
|
148
|
+
'performance',
|
|
149
|
+
'compliance',
|
|
150
|
+
'documentation',
|
|
151
|
+
'style',
|
|
152
|
+
]);
|
|
153
|
+
function toRuleClass(value) {
|
|
154
|
+
return VALID_RULE_CLASSES.has(value) ? value : 'maintainability';
|
|
155
|
+
}
|
|
156
|
+
/** Minimal YAML emitter for a proposal pack; values are quoted defensively. */
|
|
157
|
+
function renderProposalPackYaml(packId, rules) {
|
|
158
|
+
const q = (s) => JSON.stringify(s);
|
|
159
|
+
const lines = [
|
|
160
|
+
`id: ${packId}`,
|
|
161
|
+
`title: ${q('Proposals from usa learn')}`,
|
|
162
|
+
`section: S0`,
|
|
163
|
+
`section_title: ${q('Generated Proposals')}`,
|
|
164
|
+
`description: ${q('Unreviewed manual checks proposed from open audit findings.')}`,
|
|
165
|
+
`version: '0.1.0-proposed'`,
|
|
166
|
+
'rules:',
|
|
167
|
+
];
|
|
168
|
+
for (const r of rules) {
|
|
169
|
+
lines.push(` - id: ${r.id}`);
|
|
170
|
+
lines.push(` title: ${q(r.title)}`);
|
|
171
|
+
lines.push(` section: ${r.section}`);
|
|
172
|
+
lines.push(` section_title: ${q(r.sectionTitle ?? r.section)}`);
|
|
173
|
+
lines.push(` severity: ${r.severity}`);
|
|
174
|
+
lines.push(` class: ${r.ruleClass}`);
|
|
175
|
+
lines.push(` check:`);
|
|
176
|
+
lines.push(` kind: manual`);
|
|
177
|
+
lines.push(` why: ${q(r.why ?? '')}`);
|
|
178
|
+
if (r.remediation)
|
|
179
|
+
lines.push(` remediation: ${q(r.remediation)}`);
|
|
180
|
+
if (r.evidence)
|
|
181
|
+
lines.push(` evidence: ${q(r.evidence)}`);
|
|
182
|
+
if (r.references?.length) {
|
|
183
|
+
lines.push(` references: [${r.references.map(q).join(', ')}]`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return `${lines.join('\n')}\n`;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=propose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose.js","sourceRoot":"","sources":["../../src/evolution/propose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Store } from '../store/index.js';
|
|
2
|
+
import type { CapabilityGap } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Persistent, content-addressed gap queue.
|
|
5
|
+
*
|
|
6
|
+
* Gaps arrive with every audit; the same gap reappears run after run. The queue
|
|
7
|
+
* is the durable memory that answers "which gaps are currently open?" without
|
|
8
|
+
* mutating anything: every state change is one append-only record in the Store,
|
|
9
|
+
* and the latest state is a pure fold over those records.
|
|
10
|
+
*
|
|
11
|
+
* A record is one transition. Because records are content-addressed, recording
|
|
12
|
+
* the *same* transition twice writes the same bytes and therefore the same
|
|
13
|
+
* object — re-running a cycle is idempotent by construction.
|
|
14
|
+
*
|
|
15
|
+
* Deriving latest state from records (never from a mutable index) means the
|
|
16
|
+
* queue cannot drift: replay the records and you get exactly the same answer.
|
|
17
|
+
*/
|
|
18
|
+
export interface QueueSummary {
|
|
19
|
+
open: number;
|
|
20
|
+
closed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
byKind: Record<string, number>;
|
|
23
|
+
byPriority: Record<string, number>;
|
|
24
|
+
}
|
|
25
|
+
export interface GapTransition {
|
|
26
|
+
gapId: string;
|
|
27
|
+
from: CapabilityGap['state'] | 'absent';
|
|
28
|
+
to: CapabilityGap['state'];
|
|
29
|
+
at: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class GapQueue {
|
|
33
|
+
private readonly store;
|
|
34
|
+
private readonly namespace;
|
|
35
|
+
constructor(store: Store, namespace?: string);
|
|
36
|
+
recordGaps(gaps: CapabilityGap[]): {
|
|
37
|
+
opened: string[];
|
|
38
|
+
reopened: string[];
|
|
39
|
+
existing: string[];
|
|
40
|
+
};
|
|
41
|
+
closeGaps(gapIds: string[], reason?: string): string[];
|
|
42
|
+
acceptGaps(gapIds: string[], reason?: string): string[];
|
|
43
|
+
openGaps(): CapabilityGap[];
|
|
44
|
+
allGaps(): CapabilityGap[];
|
|
45
|
+
history(gapId: string): GapTransition[];
|
|
46
|
+
summarize(): QueueSummary;
|
|
47
|
+
/**
|
|
48
|
+
* Record one gap. Returns which bucket it landed in. Never mutates an existing
|
|
49
|
+
* record: a new state (or changed evidence) appends a new transition.
|
|
50
|
+
*/
|
|
51
|
+
private recordOne;
|
|
52
|
+
private transitionGaps;
|
|
53
|
+
/** Append a transition record; the gap's state field is set to `to`. */
|
|
54
|
+
private append;
|
|
55
|
+
private nextSeq;
|
|
56
|
+
/** Latest state for a gap, or null when unknown. Pure fold over records. */
|
|
57
|
+
private latestFor;
|
|
58
|
+
/** Latest gap per known id, unsorted. */
|
|
59
|
+
private latest;
|
|
60
|
+
/** All records in this namespace, ordered by (gapId, seq). */
|
|
61
|
+
private records;
|
|
62
|
+
private recordsFor;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/evolution/queue.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC;IACxC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,KAAK,EAAE,KAAK,EAAE,SAAS,SAAoB;IAKvD,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE;IAa/F,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAItD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAIvD,QAAQ,IAAI,aAAa,EAAE;IAM3B,OAAO,IAAI,aAAa,EAAE;IAI1B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE;IAMvC,SAAS,IAAI,YAAY;IAiBzB;;;OAGG;IACH,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,cAAc;IActB,wEAAwE;IACxE,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,OAAO;IAMf,4EAA4E;IAC5E,OAAO,CAAC,SAAS;IAKjB,yCAAyC;IACzC,OAAO,CAAC,MAAM;IAMd,8DAA8D;IAC9D,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { canonicalJson } from '../snapshot/index.js';
|
|
2
|
+
const DEFAULT_NAMESPACE = 'gaps';
|
|
3
|
+
const PRIORITY_RANK = {
|
|
4
|
+
high: 0,
|
|
5
|
+
medium: 1,
|
|
6
|
+
low: 2,
|
|
7
|
+
};
|
|
8
|
+
export class GapQueue {
|
|
9
|
+
store;
|
|
10
|
+
namespace;
|
|
11
|
+
constructor(store, namespace = DEFAULT_NAMESPACE) {
|
|
12
|
+
this.store = store;
|
|
13
|
+
this.namespace = namespace;
|
|
14
|
+
}
|
|
15
|
+
recordGaps(gaps) {
|
|
16
|
+
const opened = [];
|
|
17
|
+
const reopened = [];
|
|
18
|
+
const existing = [];
|
|
19
|
+
for (const gap of gaps) {
|
|
20
|
+
const bucket = this.recordOne(gap);
|
|
21
|
+
if (bucket === 'opened')
|
|
22
|
+
opened.push(gap.id);
|
|
23
|
+
else if (bucket === 'reopened')
|
|
24
|
+
reopened.push(gap.id);
|
|
25
|
+
else
|
|
26
|
+
existing.push(gap.id);
|
|
27
|
+
}
|
|
28
|
+
return { opened, reopened, existing };
|
|
29
|
+
}
|
|
30
|
+
closeGaps(gapIds, reason) {
|
|
31
|
+
return this.transitionGaps(gapIds, 'closed', reason);
|
|
32
|
+
}
|
|
33
|
+
acceptGaps(gapIds, reason) {
|
|
34
|
+
return this.transitionGaps(gapIds, 'accepted', reason);
|
|
35
|
+
}
|
|
36
|
+
openGaps() {
|
|
37
|
+
return this.latest()
|
|
38
|
+
.filter((g) => g.state === 'open')
|
|
39
|
+
.sort(compareForQueue);
|
|
40
|
+
}
|
|
41
|
+
allGaps() {
|
|
42
|
+
return this.latest().sort((a, b) => a.id.localeCompare(b.id));
|
|
43
|
+
}
|
|
44
|
+
history(gapId) {
|
|
45
|
+
return this.records()
|
|
46
|
+
.filter((r) => r.gapId === gapId)
|
|
47
|
+
.map((r) => r.transition);
|
|
48
|
+
}
|
|
49
|
+
summarize() {
|
|
50
|
+
const gaps = this.latest();
|
|
51
|
+
const byKind = {};
|
|
52
|
+
const byPriority = {};
|
|
53
|
+
let open = 0;
|
|
54
|
+
let closed = 0;
|
|
55
|
+
for (const gap of gaps) {
|
|
56
|
+
if (gap.state === 'closed')
|
|
57
|
+
closed += 1;
|
|
58
|
+
else
|
|
59
|
+
open += 1;
|
|
60
|
+
byKind[gap.kind] = (byKind[gap.kind] ?? 0) + 1;
|
|
61
|
+
byPriority[gap.priority] = (byPriority[gap.priority] ?? 0) + 1;
|
|
62
|
+
}
|
|
63
|
+
return { open, closed, total: gaps.length, byKind, byPriority };
|
|
64
|
+
}
|
|
65
|
+
/* ------------------------------------------------------------------- core -- */
|
|
66
|
+
/**
|
|
67
|
+
* Record one gap. Returns which bucket it landed in. Never mutates an existing
|
|
68
|
+
* record: a new state (or changed evidence) appends a new transition.
|
|
69
|
+
*/
|
|
70
|
+
recordOne(gap) {
|
|
71
|
+
const prior = this.latestFor(gap.id);
|
|
72
|
+
if (!prior) {
|
|
73
|
+
this.append(gap, 'absent', 'open');
|
|
74
|
+
return 'opened';
|
|
75
|
+
}
|
|
76
|
+
if (prior.state === 'closed') {
|
|
77
|
+
this.append(gap, 'closed', 'open');
|
|
78
|
+
return 'reopened';
|
|
79
|
+
}
|
|
80
|
+
if (sameGap(prior.gap, gap))
|
|
81
|
+
return 'existing';
|
|
82
|
+
this.append(gap, prior.state, prior.state);
|
|
83
|
+
return 'existing';
|
|
84
|
+
}
|
|
85
|
+
transitionGaps(gapIds, to, reason) {
|
|
86
|
+
const changed = [];
|
|
87
|
+
for (const gapId of gapIds) {
|
|
88
|
+
const prior = this.latestFor(gapId);
|
|
89
|
+
// Only a currently-open gap can transition: closed is terminal until a
|
|
90
|
+
// re-observation reopens it (recordGaps), and an accepted gap is not
|
|
91
|
+
// "open" either.
|
|
92
|
+
if (!prior || prior.state !== 'open')
|
|
93
|
+
continue;
|
|
94
|
+
this.append(prior.gap, prior.state, to, reason);
|
|
95
|
+
changed.push(gapId);
|
|
96
|
+
}
|
|
97
|
+
return changed;
|
|
98
|
+
}
|
|
99
|
+
/** Append a transition record; the gap's state field is set to `to`. */
|
|
100
|
+
append(gap, from, to, reason) {
|
|
101
|
+
const seq = this.nextSeq(gap.id);
|
|
102
|
+
const at = new Date().toISOString();
|
|
103
|
+
const transition = { gapId: gap.id, from, to, at };
|
|
104
|
+
if (reason !== undefined)
|
|
105
|
+
transition.reason = reason;
|
|
106
|
+
const record = {
|
|
107
|
+
kind: 'gap-record',
|
|
108
|
+
namespace: this.namespace,
|
|
109
|
+
gapId: gap.id,
|
|
110
|
+
seq,
|
|
111
|
+
state: to,
|
|
112
|
+
gap: { ...gap, state: to },
|
|
113
|
+
transition,
|
|
114
|
+
};
|
|
115
|
+
this.store.put(record);
|
|
116
|
+
}
|
|
117
|
+
nextSeq(gapId) {
|
|
118
|
+
const records = this.recordsFor(gapId);
|
|
119
|
+
const last = records[records.length - 1];
|
|
120
|
+
return last ? last.seq + 1 : 1;
|
|
121
|
+
}
|
|
122
|
+
/** Latest state for a gap, or null when unknown. Pure fold over records. */
|
|
123
|
+
latestFor(gapId) {
|
|
124
|
+
const records = this.recordsFor(gapId);
|
|
125
|
+
return records.length > 0 ? records[records.length - 1] : null;
|
|
126
|
+
}
|
|
127
|
+
/** Latest gap per known id, unsorted. */
|
|
128
|
+
latest() {
|
|
129
|
+
const byId = new Map();
|
|
130
|
+
for (const record of this.records())
|
|
131
|
+
byId.set(record.gapId, record);
|
|
132
|
+
return [...byId.values()].map((r) => r.gap);
|
|
133
|
+
}
|
|
134
|
+
/** All records in this namespace, ordered by (gapId, seq). */
|
|
135
|
+
records() {
|
|
136
|
+
return this.store
|
|
137
|
+
.ids()
|
|
138
|
+
.map((id) => this.store.get(id))
|
|
139
|
+
.filter((r) => isGapRecord(r, this.namespace))
|
|
140
|
+
.sort(compareRecords);
|
|
141
|
+
}
|
|
142
|
+
recordsFor(gapId) {
|
|
143
|
+
return this.records().filter((r) => r.gapId === gapId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/* -------------------------------------------------------------------- utils -- */
|
|
147
|
+
const GAP_STATES = new Set([
|
|
148
|
+
'open',
|
|
149
|
+
'accepted',
|
|
150
|
+
'closed',
|
|
151
|
+
]);
|
|
152
|
+
function isGapRecord(value, namespace) {
|
|
153
|
+
if (!value || typeof value !== 'object')
|
|
154
|
+
return false;
|
|
155
|
+
const r = value;
|
|
156
|
+
return (r.kind === 'gap-record' &&
|
|
157
|
+
r.namespace === namespace &&
|
|
158
|
+
typeof r.gapId === 'string' &&
|
|
159
|
+
typeof r.seq === 'number' &&
|
|
160
|
+
GAP_STATES.has(r.state) &&
|
|
161
|
+
!!r.gap &&
|
|
162
|
+
!!r.transition);
|
|
163
|
+
}
|
|
164
|
+
function compareRecords(a, b) {
|
|
165
|
+
return a.gapId.localeCompare(b.gapId) || a.seq - b.seq;
|
|
166
|
+
}
|
|
167
|
+
function compareForQueue(a, b) {
|
|
168
|
+
return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority] || a.id.localeCompare(b.id);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether two observations describe the same gap *logically*. Provenance that
|
|
172
|
+
* changes on every run (audit run id, snapshot, timestamp) is excluded so
|
|
173
|
+
* re-observing an unchanged gap is idempotent; the evidence trail IS compared,
|
|
174
|
+
* because new facts on a known gap are a genuine update worth recording.
|
|
175
|
+
*/
|
|
176
|
+
function sameGap(a, b) {
|
|
177
|
+
return canonicalJson(stableIdentity(a)) === canonicalJson(stableIdentity(b));
|
|
178
|
+
}
|
|
179
|
+
/** The parts of a gap that decide whether it is "the same, unchanged" gap. */
|
|
180
|
+
function stableIdentity(gap) {
|
|
181
|
+
return {
|
|
182
|
+
id: gap.id,
|
|
183
|
+
kind: gap.kind,
|
|
184
|
+
target: gap.target,
|
|
185
|
+
problem: gap.problem,
|
|
186
|
+
requiredCapability: gap.requiredCapability,
|
|
187
|
+
priority: gap.priority,
|
|
188
|
+
state: gap.state,
|
|
189
|
+
evidence: gap.evidence,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/evolution/queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8CrD,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,aAAa,GAA8C;IAC/D,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;CACP,CAAC;AAEF,MAAM,OAAO,QAAQ;IACF,KAAK,CAAQ;IACb,SAAS,CAAS;IAEnC,YAAY,KAAY,EAAE,SAAS,GAAG,iBAAiB;QACrD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,UAAU,CAAC,IAAqB;QAC9B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,MAAM,KAAK,QAAQ;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;iBACxC,IAAI,MAAM,KAAK,UAAU;gBAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;;gBACjD,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,SAAS,CAAC,MAAgB,EAAE,MAAe;QACzC,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IAED,UAAU,CAAC,MAAgB,EAAE,MAAe;QAC1C,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,EAAE;aACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC;aACjC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,CAAC,KAAa;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS;QACP,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,MAAM,UAAU,GAA2B,EAAE,CAAC;QAC9C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ;gBAAE,MAAM,IAAI,CAAC,CAAC;;gBACnC,IAAI,IAAI,CAAC,CAAC;YACf,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC/C,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAClE,CAAC;IAED,iFAAiF;IAEjF;;;OAGG;IACK,SAAS,CAAC,GAAkB;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACnC,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACnC,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO,UAAU,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3C,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,cAAc,CAAC,MAAgB,EAAE,EAA0B,EAAE,MAAe;QAClF,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACpC,uEAAuE;YACvE,qEAAqE;YACrE,iBAAiB;YACjB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM;gBAAE,SAAS;YAC/C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,wEAAwE;IAChE,MAAM,CACZ,GAAkB,EAClB,IAAuC,EACvC,EAA0B,EAC1B,MAAe;QAEf,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,UAAU,GAAkB,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;QAClE,IAAI,MAAM,KAAK,SAAS;YAAE,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QACrD,MAAM,MAAM,GAAc;YACxB,IAAI,EAAE,YAAY;YAClB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,GAAG,CAAC,EAAE;YACb,GAAG;YACH,KAAK,EAAE,EAAE;YACT,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;YAC1B,UAAU;SACX,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;IAEO,OAAO,CAAC,KAAa;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,4EAA4E;IACpE,SAAS,CAAC,KAAa;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,CAAC;IAED,yCAAyC;IACjC,MAAM;QACZ,MAAM,IAAI,GAAG,IAAI,GAAG,EAAqB,CAAC;QAC1C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,8DAA8D;IACtD,OAAO;QACb,OAAO,IAAI,CAAC,KAAK;aACd,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,EAAE,CAAC,CAAC;aAC1C,MAAM,CAAC,CAAC,CAAC,EAAkB,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;aAC7D,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC;IAEO,UAAU,CAAC,KAAa;QAC9B,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IACzD,CAAC;CACF;AAED,mFAAmF;AAEnF,MAAM,UAAU,GAAyB,IAAI,GAAG,CAAyB;IACvE,MAAM;IACN,UAAU;IACV,QAAQ;CACT,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,KAAc,EAAE,SAAiB;IACpD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAA2B,CAAC;IACtC,OAAO,CACL,CAAC,CAAC,IAAI,KAAK,YAAY;QACvB,CAAC,CAAC,SAAS,KAAK,SAAS;QACzB,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QACzB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;QACvB,CAAC,CAAC,CAAC,CAAC,GAAG;QACP,CAAC,CAAC,CAAC,CAAC,UAAU,CACf,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,CAAY,EAAE,CAAY;IAChD,OAAO,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC;AAED,SAAS,eAAe,CAAC,CAAgB,EAAE,CAAgB;IACzD,OAAO,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;GAKG;AACH,SAAS,OAAO,CAAC,CAAgB,EAAE,CAAgB;IACjD,OAAO,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,GAAkB;IACxC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BenchmarkResult, ReleaseDecision, ReleaseGate } from './types.js';
|
|
2
|
+
/** Default gate: perfect precision/recall on the fleet, zero regressions. */
|
|
3
|
+
export declare const DEFAULT_RELEASE_GATE: ReleaseGate;
|
|
4
|
+
/**
|
|
5
|
+
* Deterministic release decision. A candidate ships only when the benchmark
|
|
6
|
+
* fixtures match ground truth (no FP, no FN), precision/recall meet policy, and
|
|
7
|
+
* no existing rule regressed. The decision is a pure function of the benchmark
|
|
8
|
+
* result + gate, so it is auditable and reproducible.
|
|
9
|
+
*/
|
|
10
|
+
export declare function evaluateRelease(bench: BenchmarkResult, gate: ReleaseGate): ReleaseDecision;
|
|
11
|
+
//# sourceMappingURL=release.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../src/evolution/release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEhF,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,GAAG,eAAe,CA6B1F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Default gate: perfect precision/recall on the fleet, zero regressions. */
|
|
2
|
+
export const DEFAULT_RELEASE_GATE = {
|
|
3
|
+
minPrecision: 1,
|
|
4
|
+
minRecall: 1,
|
|
5
|
+
requireNoRegressions: true,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Deterministic release decision. A candidate ships only when the benchmark
|
|
9
|
+
* fixtures match ground truth (no FP, no FN), precision/recall meet policy, and
|
|
10
|
+
* no existing rule regressed. The decision is a pure function of the benchmark
|
|
11
|
+
* result + gate, so it is auditable and reproducible.
|
|
12
|
+
*/
|
|
13
|
+
export function evaluateRelease(bench, gate) {
|
|
14
|
+
const s = bench.summary;
|
|
15
|
+
const precision = s.tp + s.fp > 0 ? s.tp / (s.tp + s.fp) : 1;
|
|
16
|
+
const recall = s.tp + s.fn > 0 ? s.tp / (s.tp + s.fn) : 1;
|
|
17
|
+
// Evidence over claims: a benchmark with no cases proves nothing, so it can
|
|
18
|
+
// never release. Otherwise an unbenchmarked candidate would sail through on
|
|
19
|
+
// vacuous 1.000/1.000 scores.
|
|
20
|
+
const hasEvidence = bench.cases.length > 0;
|
|
21
|
+
const testsPassed = hasEvidence && s.fp === 0 && s.fn === 0;
|
|
22
|
+
const benchmarkPassed = hasEvidence && precision >= gate.minPrecision && recall >= gate.minRecall;
|
|
23
|
+
const noRegressions = !gate.requireNoRegressions || s.regressions === 0;
|
|
24
|
+
const decision = decide(hasEvidence, testsPassed, benchmarkPassed, noRegressions);
|
|
25
|
+
return {
|
|
26
|
+
decision,
|
|
27
|
+
reasons: buildReasons(bench.cases.length, s, precision, recall, gate, testsPassed, noRegressions),
|
|
28
|
+
testsPassed,
|
|
29
|
+
benchmarkPassed,
|
|
30
|
+
precision,
|
|
31
|
+
recall,
|
|
32
|
+
regressions: s.regressions,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** All four conditions must hold; a missing benchmark is never a pass. */
|
|
36
|
+
function decide(hasEvidence, testsPassed, benchmarkPassed, noRegressions) {
|
|
37
|
+
if (!hasEvidence || !testsPassed || !benchmarkPassed || !noRegressions)
|
|
38
|
+
return 'REJECT';
|
|
39
|
+
return 'ACCEPT';
|
|
40
|
+
}
|
|
41
|
+
function buildReasons(cases, s, precision, recall, gate, testsPassed, noRegressions) {
|
|
42
|
+
const reasons = [];
|
|
43
|
+
if (cases === 0)
|
|
44
|
+
reasons.push('no benchmark cases: a release requires positive evidence');
|
|
45
|
+
if (cases > 0 && !testsPassed)
|
|
46
|
+
reasons.push(`fixture results diverge from ground truth (${s.fp} FP, ${s.fn} FN)`);
|
|
47
|
+
if (!noRegressions)
|
|
48
|
+
reasons.push(`${s.regressions} regression(s) against existing rules`);
|
|
49
|
+
if (cases > 0 && precision < gate.minPrecision)
|
|
50
|
+
reasons.push(`precision ${precision.toFixed(3)} < ${gate.minPrecision}`);
|
|
51
|
+
if (cases > 0 && recall < gate.minRecall)
|
|
52
|
+
reasons.push(`recall ${recall.toFixed(3)} < ${gate.minRecall}`);
|
|
53
|
+
return reasons;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=release.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.js","sourceRoot":"","sources":["../../src/evolution/release.ts"],"names":[],"mappings":"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"}
|