agentera 3.0.0-dev.72 → 3.0.0-dev.75
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/README.md +111 -0
- package/bundle/.agentera-build-source.json +8 -0
- package/bundle/CHANGELOG.md +17 -17
- package/bundle/extract-corpus-parity.json +1 -1
- package/bundle/references/adapters/package-publication.json +49 -53
- package/bundle/references/adapters/package-registry.yaml +5 -1
- package/bundle/references/adapters/package-surface-characterization.md +6 -4
- package/bundle/references/analysis/evidence-tier-authority.yaml +45 -4
- package/bundle/references/analysis/personal-glossary-evaluation-authority.yaml +175 -0
- package/bundle/references/analysis/personal-glossary-evaluation-corpus.yaml +291 -0
- package/bundle/references/analysis/personal-glossary-holdout.yaml +192 -0
- package/bundle/references/analysis/verification-policy.yaml +8 -13
- package/bundle/references/artifacts/glossary-entry-contract.yaml +1533 -35
- package/bundle/references/cli/update-channels.yaml +2 -2
- package/bundle/references/cli/vocabulary.md +1 -1
- package/bundle/references/meta/retained-reference-authority.yaml +16 -1
- package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +11 -7
- package/bundle/skills/agentera/capabilities/profile/schemas/exit.yaml +6 -3
- package/bundle/skills/agentera/capabilities/profile/schemas/validation.yaml +13 -7
- package/bundle/skills/agentera/schemas/artifacts/glossary.yaml +5 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +7 -0
- package/dist/.agentera-build-source.json +8 -0
- package/dist/analytics/extractCorpus/copilotSessions.js +13 -4
- package/dist/analytics/extractCorpus/core.js +223 -3
- package/dist/analytics/extractCorpus/corpus.js +18 -1
- package/dist/analytics/extractCorpus/cursorSessions.js +22 -4
- package/dist/analytics/extractCorpus/evidenceTiers.js +123 -17
- package/dist/analytics/extractCorpus/filesystemSources.js +4 -0
- package/dist/analytics/extractCorpus/index.js +2 -2
- package/dist/analytics/extractCorpus/jsonlSessions.js +52 -3
- package/dist/analytics/extractCorpus/sqliteSessions.js +12 -3
- package/dist/analytics/personalGlossaryAdmission.js +26 -46
- package/dist/analytics/personalGlossaryCandidateProjection.js +692 -0
- package/dist/analytics/personalGlossaryCandidateProjectionExcerpts.js +144 -0
- package/dist/analytics/personalGlossaryCandidateReadView.js +23 -0
- package/dist/analytics/personalGlossaryCurrentGeneration.js +25 -0
- package/dist/analytics/personalGlossaryDecision.js +203 -0
- package/dist/analytics/personalGlossaryExplicit.js +788 -0
- package/dist/analytics/personalGlossaryExplicitMining.js +285 -0
- package/dist/analytics/personalGlossaryExplicitScope.js +41 -0
- package/dist/analytics/personalGlossaryExplicitSegments.js +418 -0
- package/dist/analytics/personalGlossaryExplicitTypes.js +26 -0
- package/dist/analytics/personalGlossaryLexicalClassifier.js +175 -0
- package/dist/analytics/personalGlossaryProfile.js +13 -3
- package/dist/analytics/personalGlossaryRecurrence.js +618 -0
- package/dist/analytics/personalGlossaryReviewRecordStorage.js +428 -0
- package/dist/analytics/personalGlossaryReviewRecords.js +332 -0
- package/dist/capabilities/index.js +5 -1
- package/dist/capabilities/profile/instructions.js +50 -15
- package/dist/cli/commands/personalGlossaryCandidateReads.js +744 -0
- package/dist/cli/commands/personalGlossaryDecision.js +349 -0
- package/dist/cli/commands/personalGlossaryPublish.js +484 -0
- package/dist/cli/commands/personalGlossaryReviewRecordReads.js +209 -0
- package/dist/cli/commands/personalGlossaryReviewRecords.js +623 -0
- package/dist/cli/commands/prime/briefOrientation.js +118 -19
- package/dist/cli/commands/report.js +18 -2
- package/dist/cli/commands/schema.js +179 -0
- package/dist/cli/commands/verify.js +51 -9
- package/dist/cli/dispatch/lifecycle.js +51 -6
- package/dist/cli/help.js +48 -2
- package/dist/cli/migrationRequired.js +4 -1
- package/dist/core/atomicWriter.js +4 -1
- package/dist/core/developmentInvocation.js +4 -1
- package/dist/core/yaml.js +53 -1
- package/dist/eval/glossaryEvaluation.js +751 -0
- package/dist/eval/glossaryEvaluationProcess.js +29 -0
- package/dist/eval/glossaryEvaluationRunner.js +571 -0
- package/dist/eval/glossaryEvaluationSuccessReport.js +297 -0
- package/dist/registries/activationTuples.js +31 -3
- package/dist/registries/evidenceTierContract.js +4 -0
- package/dist/registries/explicitSegmentGrammarContract.js +619 -0
- package/dist/registries/glossaryCandidateContractPaths.js +6 -0
- package/dist/registries/glossaryCandidateContracts.js +820 -0
- package/dist/registries/glossaryCandidateDecisionAuthority.js +119 -0
- package/dist/registries/glossaryCandidateDecisionContract.js +62 -0
- package/dist/registries/glossaryCandidateProjectionAuthority.js +197 -0
- package/dist/registries/glossaryCandidateProjectionContract.js +104 -0
- package/dist/registries/glossaryEntryContract.js +105 -103
- package/dist/registries/glossaryEntryTemporal.js +12 -0
- package/dist/registries/glossaryMiningAuthority.js +764 -0
- package/dist/registries/glossaryProfileFullContract.js +24 -0
- package/dist/registries/glossaryReviewRecordsAuthority.js +426 -0
- package/dist/registries/glossaryReviewRecordsContract.js +129 -0
- package/dist/registries/glossaryTermIdentity.js +122 -0
- package/dist/registries/packagePublication.js +3 -3
- package/dist/registries/personalGlossaryContracts.js +1 -0
- package/dist/release/releaseMetadata.js +15 -8
- package/dist/upgrade/npxPlatformStatus.js +2 -1
- package/dist/upgrade/projectIntegration.js +7 -1
- package/dist/validate/activationArtifactEvidence.js +18 -4
- package/dist/validate/activationEvidenceManifest.js +101 -22
- package/package.json +2 -4
- package/dist/cli/commands/personalGlossary.js +0 -176
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolveSourceRoot } from "../core/sourceRoot.js";
|
|
4
|
+
let testRunnerPath = null;
|
|
5
|
+
/** Source tests supply their transient compiled runner without changing CLI input. */
|
|
6
|
+
export function setGlossaryEvaluationRunnerForTest(path) {
|
|
7
|
+
const previous = testRunnerPath;
|
|
8
|
+
testRunnerPath = path;
|
|
9
|
+
return () => {
|
|
10
|
+
testRunnerPath = previous;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function runnerPath() {
|
|
14
|
+
if (testRunnerPath !== null)
|
|
15
|
+
return testRunnerPath;
|
|
16
|
+
return fileURLToPath(new URL("./glossaryEvaluationRunner.js", import.meta.url));
|
|
17
|
+
}
|
|
18
|
+
/** Run the heavyweight frozen evaluator only for a request that needs its gate. */
|
|
19
|
+
export function runGlossaryEvaluationProcess(sourceRoot = resolveSourceRoot()) {
|
|
20
|
+
const result = spawnSync(process.execPath, [runnerPath(), sourceRoot], {
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
maxBuffer: 1_048_576,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
returncode: result.status ?? 1,
|
|
26
|
+
stdout: String(result.stdout ?? ""),
|
|
27
|
+
stderr: String(result.stderr ?? result.error?.message ?? ""),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { BOOTSTRAP_SOURCE_ROOT_ENV, resolveSourceRoot } from "../core/sourceRoot.js";
|
|
6
|
+
import { withReadOnlyYamlMappingCache } from "../core/yaml.js";
|
|
7
|
+
import { persistPersonalGlossaryCandidateProjection, projectPersonalGlossaryCandidates, } from "../analytics/personalGlossaryCandidateProjection.js";
|
|
8
|
+
import { ADAPTER_VERSION, contentFingerprint, originIdentity, } from "../analytics/extractCorpus/core.js";
|
|
9
|
+
import { publishEvidenceTiers } from "../analytics/extractCorpus/evidenceTiers.js";
|
|
10
|
+
import { mineExplicitGlossaryCandidates } from "../analytics/personalGlossaryExplicitMining.js";
|
|
11
|
+
import { mineRecurringGlossaryCandidates } from "../analytics/personalGlossaryRecurrence.js";
|
|
12
|
+
import { readCurrentPersonalGlossaryCandidateProjection, } from "../analytics/personalGlossaryCurrentGeneration.js";
|
|
13
|
+
import { runPersonalGlossaryEvaluationDecisionCommand } from "../cli/commands/personalGlossaryDecision.js";
|
|
14
|
+
import { personalGlossaryCandidateDecisionContract } from "../registries/glossaryCandidateDecisionContract.js";
|
|
15
|
+
import { personalGlossaryCandidateProjectionContract } from "../registries/glossaryCandidateProjectionContract.js";
|
|
16
|
+
import { calculateDiscoveryRecall, calculateExplicitAdmissionPrecision, calculateInferredReviewPrecision, calculateScopeAccuracy, canonicalDigest, glossaryEvaluationCaseCounts, glossaryEvaluationLabelDigest, GLOSSARY_EVALUATION_SCHEMA_VERSION, GLOSSARY_OBSERVATIONS_SCHEMA_VERSION, glossaryEvaluationAuthorityPath, glossaryEvaluationBehaviorFixturePath, glossaryEvaluationHoldoutPath, loadGlossaryEvaluationAuthority, loadGlossaryEvaluationBehaviorFixture, loadGlossaryEvaluationHoldout, mappings, metricRule, nonEmptyString, sha256, validateFrozenGlossaryBehaviorFixture, validateFrozenGlossaryHoldout, validateGlossaryEvaluationAuthority, validateGlossaryObservations, mapping, } from "./glossaryEvaluation.js";
|
|
17
|
+
const FIXED_TIMESTAMP = "2026-08-10T00:00:00.000Z";
|
|
18
|
+
const FIXED_RETAINED_AT = "2026-08-10T00:00:00.000Z";
|
|
19
|
+
function syntheticSourceId(id, index = 0) {
|
|
20
|
+
return `synthetic:${id}:${index}`;
|
|
21
|
+
}
|
|
22
|
+
function syntheticRecord(id, sourceKind, text, index = 0, actor = "user") {
|
|
23
|
+
const sourceId = syntheticSourceId(id, index);
|
|
24
|
+
const conversation = sourceKind === "conversation_turn";
|
|
25
|
+
const data = sourceKind === "instruction_document"
|
|
26
|
+
? { content: text, signal_type: "instruction" }
|
|
27
|
+
: sourceKind === "project_config_signal"
|
|
28
|
+
? { signals: [text], signal_type: "configuration" }
|
|
29
|
+
: { text, signal_type: "correction", actor: actor === "user" ? "user" : "assistant" };
|
|
30
|
+
return {
|
|
31
|
+
source_id: sourceId,
|
|
32
|
+
source_kind: sourceKind,
|
|
33
|
+
timestamp: FIXED_TIMESTAMP,
|
|
34
|
+
project_id: `evaluation-${id}-${index}`,
|
|
35
|
+
runtime: conversation ? "opencode" : "filesystem",
|
|
36
|
+
source_class: conversation ? "active_runtime" : "project",
|
|
37
|
+
source_product: conversation ? "opencode" : "filesystem",
|
|
38
|
+
active_runtime: conversation,
|
|
39
|
+
adapter_version: ADAPTER_VERSION,
|
|
40
|
+
data,
|
|
41
|
+
origin_id: originIdentity(`personal-glossary-evaluation:${id}:${index}`),
|
|
42
|
+
content_fingerprint: contentFingerprint(text),
|
|
43
|
+
...(conversation
|
|
44
|
+
? {
|
|
45
|
+
session_id: `evaluation-session-${id}-${index}`,
|
|
46
|
+
conversation_key: `evaluation-session-${id}-${index}`,
|
|
47
|
+
author_class: actor,
|
|
48
|
+
}
|
|
49
|
+
: {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function withBehaviorWorkspace(label, run) {
|
|
53
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `agentera-glossary-evaluation-${label}-`));
|
|
54
|
+
try {
|
|
55
|
+
return run(path.join(root, "profile", "intermediate", "tiers"), root);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function behaviorList(fixture, key) {
|
|
62
|
+
const records = mappings(fixture[key]);
|
|
63
|
+
if (records.length === 0)
|
|
64
|
+
throw new Error(`behavior fixture ${key} is unavailable`);
|
|
65
|
+
return records;
|
|
66
|
+
}
|
|
67
|
+
function mapExplicitCandidates(candidates) {
|
|
68
|
+
return new Map(candidates.flatMap((candidate) => {
|
|
69
|
+
const sourceId = candidate.capsule.evidence[0]?.source_id;
|
|
70
|
+
return typeof sourceId === "string" ? [[sourceId, candidate]] : [];
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
function compareText(left, right) {
|
|
74
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
75
|
+
}
|
|
76
|
+
function candidateIdentity(capsule) {
|
|
77
|
+
return `${capsule.candidate_id}\0${capsule.candidate_revision}\0${capsule.capsule_sha256}`;
|
|
78
|
+
}
|
|
79
|
+
function evaluationDecisionBatches(candidates) {
|
|
80
|
+
const maximum = personalGlossaryCandidateProjectionContract().candidatesMax;
|
|
81
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) {
|
|
82
|
+
throw new Error("candidate projection maximum is unavailable");
|
|
83
|
+
}
|
|
84
|
+
const byGeneration = new Map();
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const key = `${candidate.capsule.generation}\0${candidate.capsule.policy_version}`;
|
|
87
|
+
const group = byGeneration.get(key) ?? [];
|
|
88
|
+
group.push(candidate);
|
|
89
|
+
byGeneration.set(key, group);
|
|
90
|
+
}
|
|
91
|
+
const batches = [];
|
|
92
|
+
for (const key of [...byGeneration.keys()].sort(compareText)) {
|
|
93
|
+
const group = byGeneration.get(key);
|
|
94
|
+
const ordered = [...group].sort((left, right) => compareText(`${candidateIdentity(left.capsule)}\0${left.id}`, `${candidateIdentity(right.capsule)}\0${right.id}`));
|
|
95
|
+
let batch = [];
|
|
96
|
+
let candidateIds = new Set();
|
|
97
|
+
for (const candidate of ordered) {
|
|
98
|
+
if (batch.length === maximum || candidateIds.has(candidate.capsule.candidate_id)) {
|
|
99
|
+
batches.push(batch);
|
|
100
|
+
batch = [];
|
|
101
|
+
candidateIds = new Set();
|
|
102
|
+
}
|
|
103
|
+
batch.push(candidate);
|
|
104
|
+
candidateIds.add(candidate.capsule.candidate_id);
|
|
105
|
+
}
|
|
106
|
+
if (batch.length > 0)
|
|
107
|
+
batches.push(batch);
|
|
108
|
+
}
|
|
109
|
+
return batches;
|
|
110
|
+
}
|
|
111
|
+
function assertCompleteEvaluationBatch(projection, batch) {
|
|
112
|
+
const capsule = batch[0]?.capsule;
|
|
113
|
+
if (capsule === undefined ||
|
|
114
|
+
projection.generation !== capsule.generation ||
|
|
115
|
+
projection.policy_version !== capsule.policy_version ||
|
|
116
|
+
projection.report.cap.applied ||
|
|
117
|
+
projection.candidates.length !== batch.length) {
|
|
118
|
+
throw new Error("evaluation projection did not retain its complete batch");
|
|
119
|
+
}
|
|
120
|
+
const retained = new Set(projection.candidates.map((candidate) => candidateIdentity(candidate.capsule)));
|
|
121
|
+
if (batch.some((candidate) => !retained.has(candidateIdentity(candidate.capsule)))) {
|
|
122
|
+
throw new Error("evaluation projection omitted a decision candidate");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function runEvaluationDecision(capsule, projection, env, tiersDir, precomputedCurrentProjection, precomputedDecisionContract, precomputedExplicitMining) {
|
|
126
|
+
const request = JSON.stringify({
|
|
127
|
+
schema_version: "agentera.personalGlossaryAdmissionRequest.v2",
|
|
128
|
+
candidate_id: capsule.candidate_id,
|
|
129
|
+
candidate_revision: capsule.candidate_revision,
|
|
130
|
+
candidate_capsule_sha256: capsule.capsule_sha256,
|
|
131
|
+
candidate_projection_sha256: projection.projection_sha256,
|
|
132
|
+
generation: capsule.generation,
|
|
133
|
+
policy_version: capsule.policy_version,
|
|
134
|
+
classification: {
|
|
135
|
+
term: capsule.term,
|
|
136
|
+
meaning: capsule.meaning,
|
|
137
|
+
scope: capsule.scope,
|
|
138
|
+
permanence: "durable",
|
|
139
|
+
consistency: "consistent",
|
|
140
|
+
confidence: 80,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
let stdout = "";
|
|
144
|
+
const exit = runPersonalGlossaryEvaluationDecisionCommand(["--input", "-", "--format", "json"], {
|
|
145
|
+
stdin: () => request,
|
|
146
|
+
out: (line) => {
|
|
147
|
+
stdout += line;
|
|
148
|
+
},
|
|
149
|
+
err: () => undefined,
|
|
150
|
+
}, {
|
|
151
|
+
env,
|
|
152
|
+
tiersDir,
|
|
153
|
+
precomputedCurrentProjection,
|
|
154
|
+
precomputedDecisionContract,
|
|
155
|
+
...(precomputedExplicitMining === undefined ? {} : { precomputedExplicitMining }),
|
|
156
|
+
});
|
|
157
|
+
if (exit !== 0)
|
|
158
|
+
throw new Error("evaluation decision command failed");
|
|
159
|
+
let result = null;
|
|
160
|
+
try {
|
|
161
|
+
result = mapping(JSON.parse(stdout));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
throw new Error("evaluation decision command did not return JSON");
|
|
165
|
+
}
|
|
166
|
+
if (result?.schemaVersion !== "agentera.personalGlossaryAdmissionResult.v2" ||
|
|
167
|
+
mapping(result.receipt) === null ||
|
|
168
|
+
!["automatic_admission", "review_required", "abstain"].includes(String(result.status))) {
|
|
169
|
+
throw new Error("evaluation decision command did not construct a current receipt");
|
|
170
|
+
}
|
|
171
|
+
return result.status;
|
|
172
|
+
}
|
|
173
|
+
function runEvaluationDecisions(candidates, tiersDir, root, precomputedExplicitMining) {
|
|
174
|
+
const outcomes = new Map();
|
|
175
|
+
const decisionContract = personalGlossaryCandidateDecisionContract();
|
|
176
|
+
for (const [index, batch] of evaluationDecisionBatches(candidates).entries()) {
|
|
177
|
+
const capsule = batch[0].capsule;
|
|
178
|
+
const env = { AGENTERA_PROFILE_DIR: path.join(root, "profiles", `batch-${index + 1}`) };
|
|
179
|
+
const projection = projectPersonalGlossaryCandidates({
|
|
180
|
+
generation: capsule.generation,
|
|
181
|
+
policy_version: capsule.policy_version,
|
|
182
|
+
retained_at: FIXED_RETAINED_AT,
|
|
183
|
+
candidates: batch.map((candidate) => ({
|
|
184
|
+
capsule: candidate.capsule,
|
|
185
|
+
project_ids: [`evaluation-${candidate.id}`],
|
|
186
|
+
})),
|
|
187
|
+
});
|
|
188
|
+
assertCompleteEvaluationBatch(projection, batch);
|
|
189
|
+
persistPersonalGlossaryCandidateProjection(projection, { env });
|
|
190
|
+
const current = readCurrentPersonalGlossaryCandidateProjection({ env, tiersDir });
|
|
191
|
+
if (current.status !== "current" ||
|
|
192
|
+
current.projection === null ||
|
|
193
|
+
current.projection.projection_sha256 !== projection.projection_sha256) {
|
|
194
|
+
throw new Error("evaluation projection is not bound to its current tier");
|
|
195
|
+
}
|
|
196
|
+
for (const candidate of batch) {
|
|
197
|
+
if (outcomes.has(candidate.id))
|
|
198
|
+
throw new Error("evaluation decision IDs must be unique");
|
|
199
|
+
outcomes.set(candidate.id, runEvaluationDecision(candidate.capsule, projection, env, tiersDir, current, decisionContract, precomputedExplicitMining));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return outcomes;
|
|
203
|
+
}
|
|
204
|
+
function executeExplicitDiscovery(fixture) {
|
|
205
|
+
return withBehaviorWorkspace("discovery", (tiersDir) => {
|
|
206
|
+
const cases = behaviorList(fixture, "discovery");
|
|
207
|
+
publishEvidenceTiers(cases.map((record) => syntheticRecord(String(record.id), "conversation_turn", String(record.text))), { tiersDir, adapterVersion: ADAPTER_VERSION, publishedAt: FIXED_TIMESTAMP });
|
|
208
|
+
const mined = mineExplicitGlossaryCandidates({ tiersDir });
|
|
209
|
+
if (mined.state !== "current")
|
|
210
|
+
throw new Error("explicit discovery did not receive a current generation");
|
|
211
|
+
const bySource = mapExplicitCandidates(mined.candidates);
|
|
212
|
+
return {
|
|
213
|
+
observations: cases.map((record) => ({
|
|
214
|
+
id: record.id,
|
|
215
|
+
observed_discovered: bySource.has(syntheticSourceId(String(record.id))),
|
|
216
|
+
})),
|
|
217
|
+
seam: {
|
|
218
|
+
metric: "discovery_recall",
|
|
219
|
+
producer: "mineExplicitGlossaryCandidates",
|
|
220
|
+
cases: cases.length,
|
|
221
|
+
candidates: mined.candidates.length,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function executeScopeClassification(fixture) {
|
|
227
|
+
return withBehaviorWorkspace("scope", (tiersDir) => {
|
|
228
|
+
const cases = behaviorList(fixture, "scope");
|
|
229
|
+
publishEvidenceTiers(cases.map((record) => syntheticRecord(String(record.id), "conversation_turn", String(record.text), 0, record.actor === "agent" ? "agent" : "user")), { tiersDir, adapterVersion: ADAPTER_VERSION, publishedAt: FIXED_TIMESTAMP });
|
|
230
|
+
const mined = mineExplicitGlossaryCandidates({ tiersDir });
|
|
231
|
+
if (mined.state !== "current")
|
|
232
|
+
throw new Error("scope classification did not receive a current generation");
|
|
233
|
+
const bySource = mapExplicitCandidates(mined.candidates);
|
|
234
|
+
const projectSources = new Set(mined.abstentions
|
|
235
|
+
.filter((abstention) => abstention.reason === "project_only_scope")
|
|
236
|
+
.map((abstention) => abstention.source_id));
|
|
237
|
+
return {
|
|
238
|
+
observations: cases.map((record) => {
|
|
239
|
+
const sourceId = syntheticSourceId(String(record.id));
|
|
240
|
+
return {
|
|
241
|
+
id: record.id,
|
|
242
|
+
observed_scope: bySource.has(sourceId)
|
|
243
|
+
? "personal"
|
|
244
|
+
: projectSources.has(sourceId)
|
|
245
|
+
? "project"
|
|
246
|
+
: "neither",
|
|
247
|
+
};
|
|
248
|
+
}),
|
|
249
|
+
seam: {
|
|
250
|
+
metric: "scope_accuracy",
|
|
251
|
+
producer: "mineExplicitGlossaryCandidates",
|
|
252
|
+
cases: cases.length,
|
|
253
|
+
candidates: mined.candidates.length,
|
|
254
|
+
abstentions: mined.abstentions.length,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function executeInferredReview(fixture) {
|
|
260
|
+
return withBehaviorWorkspace("inferred", (tiersDir, root) => {
|
|
261
|
+
const cases = behaviorList(fixture, "inferred_review");
|
|
262
|
+
const records = cases.flatMap((record) => {
|
|
263
|
+
const id = String(record.id);
|
|
264
|
+
return mappings(record.sources).map((source, index) => syntheticRecord(id, String(source.source_kind), String(source.text), index));
|
|
265
|
+
});
|
|
266
|
+
publishEvidenceTiers(records, {
|
|
267
|
+
tiersDir,
|
|
268
|
+
adapterVersion: ADAPTER_VERSION,
|
|
269
|
+
publishedAt: FIXED_TIMESTAMP,
|
|
270
|
+
});
|
|
271
|
+
const mined = mineRecurringGlossaryCandidates({
|
|
272
|
+
tiersDir,
|
|
273
|
+
requestedTerms: cases.map((record) => String(record.term)),
|
|
274
|
+
});
|
|
275
|
+
if (mined.state !== "current")
|
|
276
|
+
throw new Error("inferred review did not receive a current generation");
|
|
277
|
+
const byTerm = new Map(mined.candidates.map((candidate) => [candidate.capsule.term, candidate]));
|
|
278
|
+
const decisionCandidates = cases.flatMap((record) => {
|
|
279
|
+
const candidate = byTerm.get(String(record.term));
|
|
280
|
+
return candidate === undefined ? [] : [{ id: String(record.id), capsule: candidate.capsule }];
|
|
281
|
+
});
|
|
282
|
+
const decisions = runEvaluationDecisions(decisionCandidates, tiersDir, root);
|
|
283
|
+
const observations = cases.map((record) => {
|
|
284
|
+
const id = String(record.id);
|
|
285
|
+
const candidate = byTerm.get(String(record.term));
|
|
286
|
+
if (candidate === undefined)
|
|
287
|
+
return { id: record.id, observed_reviewed: false };
|
|
288
|
+
const decision = decisions.get(id);
|
|
289
|
+
if (decision === undefined)
|
|
290
|
+
throw new Error("inferred review decision is unavailable");
|
|
291
|
+
return { id: record.id, observed_reviewed: decision === "review_required" };
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
observations,
|
|
295
|
+
seam: {
|
|
296
|
+
metric: "inferred_review_precision",
|
|
297
|
+
producer: "mineRecurringGlossaryCandidates+personalGlossaryDecision.v2",
|
|
298
|
+
cases: cases.length,
|
|
299
|
+
candidates: decisionCandidates.length,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function executeExplicitAdmission(fixture) {
|
|
305
|
+
return withBehaviorWorkspace("admission", (tiersDir, root) => {
|
|
306
|
+
const cases = behaviorList(fixture, "explicit_admission");
|
|
307
|
+
publishEvidenceTiers(cases.map((record) => syntheticRecord(String(record.id), "conversation_turn", String(record.text))), { tiersDir, adapterVersion: ADAPTER_VERSION, publishedAt: FIXED_TIMESTAMP });
|
|
308
|
+
const mined = mineExplicitGlossaryCandidates({ tiersDir });
|
|
309
|
+
if (mined.state !== "current")
|
|
310
|
+
throw new Error("explicit admission did not receive a current generation");
|
|
311
|
+
const bySource = mapExplicitCandidates(mined.candidates);
|
|
312
|
+
const decisionCandidates = cases.flatMap((record) => {
|
|
313
|
+
const id = String(record.id);
|
|
314
|
+
const candidate = bySource.get(syntheticSourceId(id));
|
|
315
|
+
return candidate === undefined ? [] : [{ id, capsule: candidate.capsule }];
|
|
316
|
+
});
|
|
317
|
+
const decisions = runEvaluationDecisions(decisionCandidates, tiersDir, root, mined);
|
|
318
|
+
return {
|
|
319
|
+
observations: cases.map((record) => {
|
|
320
|
+
const id = String(record.id);
|
|
321
|
+
const candidate = bySource.get(syntheticSourceId(id));
|
|
322
|
+
if (candidate === undefined)
|
|
323
|
+
return { id: record.id, observed_admitted: false };
|
|
324
|
+
const decision = decisions.get(id);
|
|
325
|
+
if (decision === undefined)
|
|
326
|
+
throw new Error("explicit admission decision is unavailable");
|
|
327
|
+
return { id: record.id, observed_admitted: decision === "automatic_admission" };
|
|
328
|
+
}),
|
|
329
|
+
seam: {
|
|
330
|
+
metric: "explicit_admission_precision",
|
|
331
|
+
producer: "mineExplicitGlossaryCandidates+personalGlossaryDecision.v2",
|
|
332
|
+
cases: cases.length,
|
|
333
|
+
candidates: mined.candidates.length,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Run only frozen synthetic input through current discovery, classification, and
|
|
340
|
+
* V2 decision seams. It receives no expected labels and cannot persist effects
|
|
341
|
+
* outside its deleted user-local temporary workspace.
|
|
342
|
+
*/
|
|
343
|
+
export function evaluateGlossaryBehavior(fixture) {
|
|
344
|
+
return withReadOnlyYamlMappingCache(() => {
|
|
345
|
+
const discovery = executeExplicitDiscovery(fixture);
|
|
346
|
+
const scope = executeScopeClassification(fixture);
|
|
347
|
+
const inferredReview = executeInferredReview(fixture);
|
|
348
|
+
const explicitAdmission = executeExplicitAdmission(fixture);
|
|
349
|
+
return {
|
|
350
|
+
discovery: discovery.observations,
|
|
351
|
+
scope: scope.observations,
|
|
352
|
+
inferred_review: inferredReview.observations,
|
|
353
|
+
explicit_admission: explicitAdmission.observations,
|
|
354
|
+
seams: [discovery.seam, scope.seam, inferredReview.seam, explicitAdmission.seam],
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function binaryExamples(holdout, observations, key, expected, observed) {
|
|
359
|
+
const observedById = new Map(mappings(observations[key]).flatMap((record) => nonEmptyString(record.id) ? [[record.id, record]] : []));
|
|
360
|
+
return mappings(holdout[key]).flatMap((record) => {
|
|
361
|
+
const evaluated = nonEmptyString(record.id) ? observedById.get(record.id) : undefined;
|
|
362
|
+
if (!record || !evaluated || !nonEmptyString(record.id) || typeof record[expected] !== "boolean" || typeof evaluated[observed] !== "boolean") {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
return [{ id: record.id, expected: record[expected], observed: evaluated[observed] }];
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
function scopeExamples(holdout, observations) {
|
|
369
|
+
const observedById = new Map(mappings(observations.scope).flatMap((record) => nonEmptyString(record.id) ? [[record.id, record]] : []));
|
|
370
|
+
return mappings(holdout.scope).flatMap((record) => {
|
|
371
|
+
const evaluated = nonEmptyString(record.id) ? observedById.get(record.id) : undefined;
|
|
372
|
+
if (!record || !evaluated || !nonEmptyString(record.id) || !nonEmptyString(record.expected_scope) || !nonEmptyString(evaluated.observed_scope)) {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
return [{ id: record.id, expected: record.expected_scope, observed: evaluated.observed_scope }];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function metricCounts(results) {
|
|
379
|
+
return Object.fromEntries(results.map((result) => [result.metric, result.denominator]));
|
|
380
|
+
}
|
|
381
|
+
function productPolicyProvenance() {
|
|
382
|
+
const sourceRoot = resolveSourceRoot();
|
|
383
|
+
const policyPath = path.join(sourceRoot, "references", "artifacts", "glossary-entry-contract.yaml");
|
|
384
|
+
return {
|
|
385
|
+
path: "references/artifacts/glossary-entry-contract.yaml",
|
|
386
|
+
sha256: sha256(fs.readFileSync(policyPath)),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function failureReport(errors) {
|
|
390
|
+
return {
|
|
391
|
+
schemaVersion: GLOSSARY_EVALUATION_SCHEMA_VERSION,
|
|
392
|
+
status: "fail",
|
|
393
|
+
report: {
|
|
394
|
+
exploratory: { release_authorizing: false, status: "not_run" },
|
|
395
|
+
release_gate: { release_authorizing: true, release_authorized: false, status: "fail", failure_reasons: errors },
|
|
396
|
+
},
|
|
397
|
+
authority: { path: "references/analysis/personal-glossary-evaluation-authority.yaml" },
|
|
398
|
+
holdout: { path: "references/analysis/personal-glossary-holdout.yaml", status: "not_verified" },
|
|
399
|
+
metrics: [],
|
|
400
|
+
metrics_sha256: canonicalDigest([]),
|
|
401
|
+
gates: {
|
|
402
|
+
explicit_admission: {
|
|
403
|
+
metric: "explicit_admission_precision",
|
|
404
|
+
status: "fail",
|
|
405
|
+
qualification_blocker: true,
|
|
406
|
+
outcome: "explicit_automatic_admission_only",
|
|
407
|
+
},
|
|
408
|
+
inferred_automatic_admission: { status: "disabled", enabled: false, measured_result: "cannot_enable" },
|
|
409
|
+
release_authorizing: "fail_closed",
|
|
410
|
+
},
|
|
411
|
+
errors,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
export function evaluateGlossaryHoldout(root = resolveSourceRoot()) {
|
|
415
|
+
const authorityPath = glossaryEvaluationAuthorityPath(root);
|
|
416
|
+
let authority;
|
|
417
|
+
try {
|
|
418
|
+
authority = loadGlossaryEvaluationAuthority(root);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return failureReport(["cannot load evaluation authority"]);
|
|
422
|
+
}
|
|
423
|
+
const authorityErrors = validateGlossaryEvaluationAuthority(authority);
|
|
424
|
+
const holdoutPath = glossaryEvaluationHoldoutPath(root);
|
|
425
|
+
const behaviorPath = glossaryEvaluationBehaviorFixturePath(root);
|
|
426
|
+
let holdout;
|
|
427
|
+
let holdoutBytes;
|
|
428
|
+
let behavior;
|
|
429
|
+
let behaviorBytes;
|
|
430
|
+
try {
|
|
431
|
+
holdoutBytes = fs.readFileSync(holdoutPath);
|
|
432
|
+
holdout = loadGlossaryEvaluationHoldout(root);
|
|
433
|
+
behaviorBytes = fs.readFileSync(behaviorPath);
|
|
434
|
+
behavior = loadGlossaryEvaluationBehaviorFixture(root);
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return failureReport([...authorityErrors, "cannot load frozen glossary evaluation fixture"]);
|
|
438
|
+
}
|
|
439
|
+
const fixtureErrors = [
|
|
440
|
+
...validateFrozenGlossaryHoldout(authority, holdout, holdoutBytes),
|
|
441
|
+
...validateFrozenGlossaryBehaviorFixture(authority, holdout, behavior, behaviorBytes),
|
|
442
|
+
];
|
|
443
|
+
if (authorityErrors.length > 0 || fixtureErrors.length > 0) {
|
|
444
|
+
return failureReport([...authorityErrors, ...fixtureErrors]);
|
|
445
|
+
}
|
|
446
|
+
let execution;
|
|
447
|
+
try {
|
|
448
|
+
// Labels are deliberately absent from this production-seam execution.
|
|
449
|
+
execution = evaluateGlossaryBehavior(behavior);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
return failureReport(["current personal glossary behavior could not be evaluated"]);
|
|
453
|
+
}
|
|
454
|
+
const observations = {
|
|
455
|
+
schema_version: GLOSSARY_OBSERVATIONS_SCHEMA_VERSION,
|
|
456
|
+
holdout_id: holdout.holdout_id,
|
|
457
|
+
holdout_fixture_sha256: sha256(holdoutBytes),
|
|
458
|
+
behavior_fixture_sha256: sha256(behaviorBytes),
|
|
459
|
+
discovery: execution.discovery,
|
|
460
|
+
scope: execution.scope,
|
|
461
|
+
inferred_review: execution.inferred_review,
|
|
462
|
+
explicit_admission: execution.explicit_admission,
|
|
463
|
+
};
|
|
464
|
+
const observationErrors = validateGlossaryObservations(authority, holdout, observations);
|
|
465
|
+
if (observationErrors.length > 0)
|
|
466
|
+
return failureReport(observationErrors);
|
|
467
|
+
let policy;
|
|
468
|
+
try {
|
|
469
|
+
policy = productPolicyProvenance();
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
return failureReport(["current personal glossary policy could not be recorded"]);
|
|
473
|
+
}
|
|
474
|
+
const results = [
|
|
475
|
+
calculateDiscoveryRecall(binaryExamples(holdout, observations, "discovery", "expected_discoverable", "observed_discovered"), metricRule(authority, "discovery_recall")),
|
|
476
|
+
calculateScopeAccuracy(scopeExamples(holdout, observations), metricRule(authority, "scope_accuracy")),
|
|
477
|
+
calculateInferredReviewPrecision(binaryExamples(holdout, observations, "inferred_review", "expected_reviewable", "observed_reviewed"), metricRule(authority, "inferred_review_precision")),
|
|
478
|
+
calculateExplicitAdmissionPrecision(binaryExamples(holdout, observations, "explicit_admission", "expected_admissible", "observed_admitted"), metricRule(authority, "explicit_admission_precision")),
|
|
479
|
+
];
|
|
480
|
+
const releaseGatePass = results.every((result) => result.status === "pass");
|
|
481
|
+
const releaseFailures = results
|
|
482
|
+
.filter((result) => result.status !== "pass")
|
|
483
|
+
.map((result) => `${result.metric}:${result.failure_reasons.join(",")}`);
|
|
484
|
+
return {
|
|
485
|
+
schemaVersion: GLOSSARY_EVALUATION_SCHEMA_VERSION,
|
|
486
|
+
status: releaseGatePass ? "pass" : "fail",
|
|
487
|
+
report: {
|
|
488
|
+
exploratory: { release_authorizing: false, status: "report_only", metrics: results },
|
|
489
|
+
release_gate: {
|
|
490
|
+
release_authorizing: true,
|
|
491
|
+
release_authorized: releaseGatePass,
|
|
492
|
+
status: releaseGatePass ? "pass" : "fail",
|
|
493
|
+
failure_reasons: releaseFailures,
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
authority: {
|
|
497
|
+
schema_version: authority.schema_version,
|
|
498
|
+
path: "references/analysis/personal-glossary-evaluation-authority.yaml",
|
|
499
|
+
sha256: sha256(fs.readFileSync(authorityPath)),
|
|
500
|
+
},
|
|
501
|
+
policy,
|
|
502
|
+
holdout: {
|
|
503
|
+
id: holdout.holdout_id,
|
|
504
|
+
status: holdout.status,
|
|
505
|
+
fixture_sha256: sha256(holdoutBytes),
|
|
506
|
+
labels_sha256: glossaryEvaluationLabelDigest(holdout),
|
|
507
|
+
provenance: holdout.provenance,
|
|
508
|
+
case_counts: glossaryEvaluationCaseCounts(holdout),
|
|
509
|
+
},
|
|
510
|
+
behavior_fixture: {
|
|
511
|
+
id: behavior.fixture_id,
|
|
512
|
+
status: behavior.status,
|
|
513
|
+
path: "references/analysis/personal-glossary-evaluation-corpus.yaml",
|
|
514
|
+
fixture_sha256: sha256(behaviorBytes),
|
|
515
|
+
provenance: behavior.provenance,
|
|
516
|
+
case_counts: glossaryEvaluationCaseCounts(behavior),
|
|
517
|
+
},
|
|
518
|
+
observations: {
|
|
519
|
+
schema_version: observations.schema_version,
|
|
520
|
+
source: "current_product_behavior",
|
|
521
|
+
sha256: canonicalDigest(observations),
|
|
522
|
+
holdout_fixture_sha256: observations.holdout_fixture_sha256,
|
|
523
|
+
behavior_fixture_sha256: observations.behavior_fixture_sha256,
|
|
524
|
+
case_counts: glossaryEvaluationCaseCounts(observations),
|
|
525
|
+
seams: execution.seams,
|
|
526
|
+
effects: [],
|
|
527
|
+
},
|
|
528
|
+
metrics: results,
|
|
529
|
+
metrics_sha256: canonicalDigest(results),
|
|
530
|
+
gates: {
|
|
531
|
+
explicit_admission: {
|
|
532
|
+
metric: "explicit_admission_precision",
|
|
533
|
+
status: results[3].status,
|
|
534
|
+
qualification_blocker: true,
|
|
535
|
+
outcome: "explicit_automatic_admission_only",
|
|
536
|
+
},
|
|
537
|
+
inferred_review: {
|
|
538
|
+
metric: "inferred_review_precision",
|
|
539
|
+
status: results[2].status,
|
|
540
|
+
outcome: "review_suggestions_only",
|
|
541
|
+
},
|
|
542
|
+
inferred_automatic_admission: {
|
|
543
|
+
status: "disabled",
|
|
544
|
+
enabled: false,
|
|
545
|
+
measured_result: "cannot_enable",
|
|
546
|
+
},
|
|
547
|
+
release_authorizing: releaseGatePass ? "pass" : "fail_closed",
|
|
548
|
+
},
|
|
549
|
+
denominator_counts: metricCounts(results),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
export function main(out = (line) => process.stdout.write(line), root = resolveSourceRoot()) {
|
|
553
|
+
const report = evaluateGlossaryHoldout(root);
|
|
554
|
+
out(JSON.stringify(report, null, 2) + "\n");
|
|
555
|
+
return report.status === "pass" ? 0 : 1;
|
|
556
|
+
}
|
|
557
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
558
|
+
const root = process.argv[2];
|
|
559
|
+
const previous = process.env[BOOTSTRAP_SOURCE_ROOT_ENV];
|
|
560
|
+
if (root)
|
|
561
|
+
process.env[BOOTSTRAP_SOURCE_ROOT_ENV] = root;
|
|
562
|
+
try {
|
|
563
|
+
process.exitCode = main();
|
|
564
|
+
}
|
|
565
|
+
finally {
|
|
566
|
+
if (previous === undefined)
|
|
567
|
+
delete process.env[BOOTSTRAP_SOURCE_ROOT_ENV];
|
|
568
|
+
else
|
|
569
|
+
process.env[BOOTSTRAP_SOURCE_ROOT_ENV] = previous;
|
|
570
|
+
}
|
|
571
|
+
}
|