@inflexa-ai/harness 0.28.1 → 0.30.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/dist/agents/conversation-agent.js +7 -4
- package/dist/agents/report-session-agent.js +1 -1
- package/dist/agents/sandbox/data-profiler.js +0 -1
- package/dist/agents/sandbox/immune-profiling-agent.js +3 -1
- package/dist/agents/sandbox/network-agent.js +0 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/sandbox/types.js +1 -1
- package/dist/app/data-profile-orientation.d.ts +52 -27
- package/dist/app/data-profile-orientation.js +331 -92
- package/dist/app/data-profile-view.d.ts +70 -0
- package/dist/app/data-profile-view.js +123 -0
- package/dist/app/synthesize-run.js +1 -1
- package/dist/config/resource-limits.d.ts +1 -1
- package/dist/config/resource-limits.js +1 -1
- package/dist/contracts/data-profile.d.ts +276 -25
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/profile-vocabulary.d.ts +103 -0
- package/dist/contracts/profile-vocabulary.js +449 -0
- package/dist/contracts/schemas/chat-parts.d.ts +12 -12
- package/dist/contracts/severity.d.ts +2 -2
- package/dist/contracts/target-dossier.d.ts +91 -91
- package/dist/data/safety-panel-schema.d.ts +4 -4
- package/dist/execution/input-signature.d.ts +16 -3
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/post-step-pipeline.js +1 -1
- package/dist/execution/staged-input.d.ts +4 -4
- package/dist/index.d.ts +7 -2
- package/dist/index.js +14 -0
- package/dist/input-scan/clustering.d.ts +63 -0
- package/dist/input-scan/clustering.js +208 -0
- package/dist/input-scan/companions.d.ts +30 -0
- package/dist/input-scan/companions.js +138 -0
- package/dist/input-scan/decoder/container_readout.py +171 -0
- package/dist/input-scan/detect-sets.d.ts +24 -0
- package/dist/input-scan/detect-sets.js +279 -0
- package/dist/input-scan/enrich.d.ts +62 -19
- package/dist/input-scan/enrich.js +132 -187
- package/dist/input-scan/formats.d.ts +13 -5
- package/dist/input-scan/formats.js +13 -5
- package/dist/input-scan/index.d.ts +10 -4
- package/dist/input-scan/index.js +7 -4
- package/dist/input-scan/markers.d.ts +21 -0
- package/dist/input-scan/markers.js +89 -0
- package/dist/input-scan/menu.d.ts +127 -0
- package/dist/input-scan/menu.js +0 -0
- package/dist/input-scan/quarantine.d.ts +17 -0
- package/dist/input-scan/quarantine.js +69 -0
- package/dist/input-scan/readout-budget.d.ts +32 -0
- package/dist/input-scan/readout-budget.js +61 -0
- package/dist/input-scan/readout.d.ts +33 -0
- package/dist/input-scan/readout.js +336 -0
- package/dist/input-scan/scan.d.ts +5 -4
- package/dist/input-scan/scan.js +5 -5
- package/dist/input-scan/set-types.d.ts +209 -0
- package/dist/input-scan/set-types.js +15 -0
- package/dist/input-scan/templates.d.ts +93 -0
- package/dist/input-scan/templates.js +369 -0
- package/dist/input-scan/tokens.d.ts +94 -0
- package/dist/input-scan/tokens.js +267 -0
- package/dist/input-scan/tuning.d.ts +55 -0
- package/dist/input-scan/tuning.js +55 -0
- package/dist/input-scan/types.d.ts +2 -2
- package/dist/loop/run-agent.d.ts +13 -1
- package/dist/loop/run-agent.js +2 -1
- package/dist/prompts/SOUL.d.ts +2 -2
- package/dist/prompts/SOUL.js +17 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +45 -71
- package/dist/prompts/planner.js +57 -10
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +3 -2
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -1
- package/dist/prompts/sandbox/data-profiler.d.ts +10 -1
- package/dist/prompts/sandbox/data-profiler.js +197 -53
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +18 -12
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +6 -2
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +8 -4
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +12 -6
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +9 -6
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +4 -2
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +10 -7
- package/dist/providers/ai-sdk.js +34 -1
- package/dist/providers/reasoning.d.ts +65 -0
- package/dist/providers/reasoning.js +81 -0
- package/dist/providers/types.d.ts +14 -0
- package/dist/schemas/data-profile-schemas.d.ts +947 -84
- package/dist/schemas/data-profile-schemas.js +300 -115
- package/dist/schemas/run-synthesis.d.ts +4 -4
- package/dist/state/data-profile.d.ts +1 -1
- package/dist/state/index.d.ts +1 -1
- package/dist/state/vector-store.d.ts +13 -0
- package/dist/state/vector-store.js +8 -0
- package/dist/tasks/data-profile-absorb.d.ts +78 -0
- package/dist/tasks/data-profile-absorb.js +285 -0
- package/dist/tasks/data-profile-index.d.ts +34 -25
- package/dist/tasks/data-profile-index.js +160 -59
- package/dist/tasks/data-profile-resolve.d.ts +72 -0
- package/dist/tasks/data-profile-resolve.js +639 -0
- package/dist/tasks/data-profile.d.ts +16 -5
- package/dist/tasks/data-profile.js +270 -131
- package/dist/tools/bio/chembl.d.ts +1 -1
- package/dist/tools/bio/chembl.js +15 -10
- package/dist/tools/bio/comptox.js +5 -1
- package/dist/tools/bio/drug-gene-interactions.js +5 -2
- package/dist/tools/bio/gene-disease-evidence.d.ts +19 -9
- package/dist/tools/bio/gene-disease-evidence.js +78 -22
- package/dist/tools/bio/gene-preclinical-profile.d.ts +33 -13
- package/dist/tools/bio/gene-preclinical-profile.js +63 -22
- package/dist/tools/bio/lookup-annotation.d.ts +1 -0
- package/dist/tools/bio/lookup-annotation.js +28 -10
- package/dist/tools/bio/opentargets.d.ts +10 -2
- package/dist/tools/bio/opentargets.js +42 -13
- package/dist/tools/bio/pubchem.js +5 -2
- package/dist/tools/bio/pubmed.js +9 -3
- package/dist/tools/bio/search-clinical-trials.d.ts +25 -25
- package/dist/tools/bio/search-clinical-trials.js +56 -18
- package/dist/tools/bio/search-faers.d.ts +26 -6
- package/dist/tools/bio/search-faers.js +70 -18
- package/dist/tools/bio/search-gene.d.ts +19 -3
- package/dist/tools/bio/search-gene.js +69 -12
- package/dist/tools/bio/search-geo-datasets.js +4 -1
- package/dist/tools/bio/search-interactions.js +6 -2
- package/dist/tools/bio/target-safety.d.ts +2 -2
- package/dist/tools/bio/target-safety.js +3 -2
- package/dist/tools/lib/cbioportal-client.js +25 -6
- package/dist/tools/lib/chembl-client.d.ts +3 -1
- package/dist/tools/lib/chembl-client.js +6 -1
- package/dist/tools/lib/gwas-catalog-client.d.ts +7 -6
- package/dist/tools/lib/gwas-catalog-client.js +18 -6
- package/dist/tools/lib/openfda-client.d.ts +7 -5
- package/dist/tools/lib/openfda-client.js +17 -9
- package/dist/tools/lib/opentargets-client.d.ts +20 -0
- package/dist/tools/lib/opentargets-client.js +45 -0
- package/dist/tools/lib/pathway-client.d.ts +7 -0
- package/dist/tools/lib/pathway-client.js +68 -1
- package/dist/tools/research/context7-docs.js +3 -3
- package/dist/tools/research/generate-analogy-report.js +2 -2
- package/dist/tools/research/generate-plan.d.ts +3 -0
- package/dist/tools/research/generate-plan.js +64 -5
- package/dist/tools/research/inspect-data-profile.d.ts +48 -22
- package/dist/tools/research/inspect-data-profile.js +73 -54
- package/dist/tools/research/resolve-citation.js +8 -3
- package/dist/tools/research/search-arxiv.js +8 -3
- package/dist/tools/research/search-semantic-scholar.js +10 -4
- package/dist/tools/workspace/scan-inputs.d.ts +4 -4
- package/dist/tools/workspace/scan-inputs.js +13 -14
- package/dist/tools/workspace/workspace-search.d.ts +11 -2
- package/dist/tools/workspace/workspace-search.js +44 -16
- package/dist/workflows/sandbox-step.d.ts +6 -0
- package/dist/workflows/sandbox-step.js +31 -14
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +1 -1
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +38 -38
- package/dist/workflows/target-assessment/schemas.d.ts +12 -12
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +6 -6
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +6 -6
- package/dist/workflows/target-assessment/synthesis/index.d.ts +12 -12
- package/package.json +5 -3
- package/dist/input-scan/coverage.d.ts +0 -22
- package/dist/input-scan/coverage.js +0 -85
|
@@ -1,43 +1,127 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The profile's vector index, as a pure projection.
|
|
3
3
|
*
|
|
4
|
-
* The index is a function of the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* from it, so it can be rebuilt at any time by re-running the scan and reading `kinds`
|
|
8
|
-
* off the existing profile — no model call, no re-profile. Improving the template,
|
|
9
|
-
* adding a tier, or folding in entity attributes later are all re-projections.
|
|
4
|
+
* The index is a function of the persisted profile crossed with the scan it was taken
|
|
5
|
+
* against. It has no durable representation of its own and nothing derives from it, so it
|
|
6
|
+
* can be rebuilt at any time from those two things — no model call, no re-profile.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
* ("variant calls") wants
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
8
|
+
* Three tiers, because they answer three different queries. A query naming a KIND of data
|
|
9
|
+
* ("variant calls") wants the group: its meaning, its count, its template. A query naming
|
|
10
|
+
* what VARIES ("timepoint") wants the dimension and the evidence behind it. A query naming
|
|
11
|
+
* a particular FILE wants that file — but only the members an agent wrote about
|
|
12
|
+
* individually are worth an entry, because a member entry templated from its group's text
|
|
13
|
+
* is a near-duplicate of the group entry, and thousands of them are recall noise rather
|
|
14
|
+
* than recall.
|
|
17
15
|
*
|
|
18
|
-
* `type: "input"` keeps its existing meaning
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* `type: "input"` keeps its existing meaning — one entry addressing one workspace path —
|
|
17
|
+
* so a search filtered to it matches whichever era wrote the profile it reads.
|
|
18
|
+
*
|
|
19
|
+
* Every file-addressed entry stamps its workspace path into `metadata.path`; pattern
|
|
20
|
+
* entries (group, dimension, kind, entity) never do. That presence is the discriminator
|
|
21
|
+
* `workspace_search` documents, so a consumer needs no id parsing to tell them apart.
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
import { dimensionCardinalities, profileDimensions, profileFileRecords, profileGroups } from "../app/data-profile-view.js";
|
|
24
|
+
/** Entity entries built for a legacy profile. Past this the entity tier is a liability, not a search aid. */
|
|
23
25
|
export const MAX_ENTITY_ENTRIES = 20_000;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Every metadata `type` a profile writes, in either era.
|
|
28
|
+
*
|
|
29
|
+
* The index is a pure PROJECTION, so re-indexing replaces rather than merges: an upsert
|
|
30
|
+
* keyed by entry id leaves the entries of a renamed group, a dropped dimension, or a
|
|
31
|
+
* de-annotated member behind forever, searchable and wrong. Clearing exactly these types
|
|
32
|
+
* clears exactly what a profile wrote — step outputs, summaries, and syntheses carry their
|
|
33
|
+
* own types and are untouched.
|
|
34
|
+
*/
|
|
35
|
+
export const PROFILE_INDEX_TYPES = ["input-group", "input-dimension", "input", "input-kind"];
|
|
36
|
+
/** Group names named in one legacy entity entry's text. */
|
|
37
|
+
const MAX_GROUPS_IN_ENTITY_TEXT = 6;
|
|
26
38
|
function slug(value) {
|
|
27
|
-
return value
|
|
39
|
+
return (value
|
|
28
40
|
.toLowerCase()
|
|
29
41
|
.replace(/[^a-z0-9]+/g, "-")
|
|
30
42
|
.replace(/^-|-$/g, "")
|
|
31
|
-
.slice(0, 60);
|
|
43
|
+
.slice(0, 60) || "unnamed");
|
|
44
|
+
}
|
|
45
|
+
function slotSummary(slot) {
|
|
46
|
+
return { id: slot.id, location: slot.location, tokenClass: slot.tokenClass, distinctValues: slot.distinctValues };
|
|
47
|
+
}
|
|
48
|
+
function groupEntry(analysisId, group) {
|
|
49
|
+
const category = group.categoryLabel ?? group.category;
|
|
50
|
+
const facts = [`${group.count} members`, `${group.fileCount} files`, group.displayPattern].join(", ");
|
|
51
|
+
return {
|
|
52
|
+
id: `/${analysisId}/group/${group.id}`,
|
|
53
|
+
text: `${group.name} — one member is ${group.memberRepresents}. ${group.description} ${category} held as ${group.role}. (${facts})`,
|
|
54
|
+
metadata: {
|
|
55
|
+
type: "input-group",
|
|
56
|
+
group: group.id,
|
|
57
|
+
name: group.name,
|
|
58
|
+
role: group.role,
|
|
59
|
+
category: group.category,
|
|
60
|
+
...(group.categoryLabel ? { categoryLabel: group.categoryLabel } : {}),
|
|
61
|
+
pathPattern: group.displayPattern,
|
|
62
|
+
count: group.count,
|
|
63
|
+
fileCount: group.fileCount,
|
|
64
|
+
...(group.slots && group.slots.length > 0 ? { slots: group.slots.map(slotSummary) } : {}),
|
|
65
|
+
...(group.unclassified ? { unclassified: true } : {}),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** One observation as a searchable clause — where the dimension was seen, and what was seen there. */
|
|
70
|
+
function observationSummary(observation) {
|
|
71
|
+
if (observation.kind === "slot") {
|
|
72
|
+
const values = observation.sampleValues.length > 0 ? `, e.g. ${observation.sampleValues.join(", ")}` : "";
|
|
73
|
+
return `slot ${observation.slotId} (${observation.tokenClass}, ${observation.cardinality} values${values})`;
|
|
74
|
+
}
|
|
75
|
+
if (observation.kind === "column") {
|
|
76
|
+
const distinct = observation.distinctValues !== undefined ? `, ${observation.distinctValues} values` : "";
|
|
77
|
+
return `column ${observation.column} of ${observation.path}${distinct}`;
|
|
78
|
+
}
|
|
79
|
+
return `document ${observation.path} (${observation.citation})`;
|
|
80
|
+
}
|
|
81
|
+
function dimensionEntry(analysisId, dimension) {
|
|
82
|
+
const category = dimension.categoryLabel ?? dimension.category;
|
|
83
|
+
const description = dimension.description ? ` ${dimension.description}` : "";
|
|
84
|
+
const observations = dimension.observations.map(observationSummary).join("; ");
|
|
85
|
+
return {
|
|
86
|
+
id: `/${analysisId}/dimension/${slug(dimension.label)}`,
|
|
87
|
+
text: `${dimension.label} — ${category}, a ${dimension.scope} dimension of this dataset.${description} Observed as: ${observations}.`,
|
|
88
|
+
metadata: {
|
|
89
|
+
type: "input-dimension",
|
|
90
|
+
dimension: dimension.label,
|
|
91
|
+
category: dimension.category,
|
|
92
|
+
...(dimension.categoryLabel ? { categoryLabel: dimension.categoryLabel } : {}),
|
|
93
|
+
scope: dimension.scope,
|
|
94
|
+
cardinalities: dimensionCardinalities(dimension),
|
|
95
|
+
groups: [...new Set(dimension.observations.flatMap((observation) => (observation.kind === "slot" ? observation.groupIds : [])))],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One annotated member. The id addresses the member's workspace path, so a search hit
|
|
101
|
+
* resolves to a file that exists rather than to a synthetic key.
|
|
102
|
+
*/
|
|
103
|
+
function memberEntry(analysisId, group, annotation) {
|
|
104
|
+
const format = group.formats.length === 1 ? group.formats[0].format : undefined;
|
|
105
|
+
return {
|
|
106
|
+
id: `/${analysisId}/${annotation.path}`,
|
|
107
|
+
text: `${annotation.path} — ${annotation.note} In ${group.name}, where one member is ${group.memberRepresents}.`,
|
|
108
|
+
metadata: {
|
|
109
|
+
type: "input",
|
|
110
|
+
path: annotation.path,
|
|
111
|
+
group: group.id,
|
|
112
|
+
groupName: group.name,
|
|
113
|
+
category: group.category,
|
|
114
|
+
...(format ? { format } : {}),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
32
117
|
}
|
|
33
118
|
/**
|
|
34
|
-
* The entity value set: the largest distinct-value set any single
|
|
35
|
-
* carries.
|
|
119
|
+
* The entity value set of a legacy profile: the largest distinct-value set any single
|
|
120
|
+
* variable position carries.
|
|
36
121
|
*
|
|
37
122
|
* Entities are what a dataset is ABOUT, and the position that varies most widely across
|
|
38
|
-
* one shape is what identifies them
|
|
39
|
-
*
|
|
40
|
-
* the cross product is the file set, which is the tier this projection exists to avoid.
|
|
123
|
+
* one shape is what identifies them. Taking the widest position rather than the cross
|
|
124
|
+
* product is deliberate: the cross product is the file set.
|
|
41
125
|
*/
|
|
42
126
|
function entityValues(scan) {
|
|
43
127
|
if (!scan)
|
|
@@ -56,37 +140,36 @@ function entityValues(scan) {
|
|
|
56
140
|
return { values: [...best].sort((a, b) => a.localeCompare(b, "en")), distinct: best.length };
|
|
57
141
|
}
|
|
58
142
|
/**
|
|
59
|
-
* The
|
|
60
|
-
* the
|
|
61
|
-
*
|
|
143
|
+
* The dimension label for a legacy entity set. The agent labels dimensions; the scan
|
|
144
|
+
* counts values, so the dimension whose cardinality matches the observed set is the one
|
|
145
|
+
* the agent was naming.
|
|
62
146
|
*/
|
|
63
|
-
function
|
|
64
|
-
if (
|
|
147
|
+
function entityDimensionLabel(dimensions, distinct) {
|
|
148
|
+
if (dimensions.length === 0)
|
|
65
149
|
return "entity";
|
|
66
|
-
const exact =
|
|
150
|
+
const exact = dimensions.find((dimension) => dimension.cardinalities.includes(distinct));
|
|
67
151
|
if (exact)
|
|
68
152
|
return exact.label;
|
|
69
|
-
const widest = [...
|
|
153
|
+
const widest = [...dimensions].sort((a, b) => Math.max(0, ...b.cardinalities) - Math.max(0, ...a.cardinalities))[0];
|
|
70
154
|
return widest?.label ?? "entity";
|
|
71
155
|
}
|
|
72
|
-
function kindEntry(analysisId,
|
|
73
|
-
const facts = [`${
|
|
156
|
+
function kindEntry(analysisId, group) {
|
|
157
|
+
const facts = [`${group.count} files`, group.format, group.pattern].filter((fact) => Boolean(fact)).join(", ");
|
|
74
158
|
return {
|
|
75
|
-
id: `/${analysisId}/kind/${slug(
|
|
76
|
-
text: `${
|
|
159
|
+
id: `/${analysisId}/kind/${slug(group.name)}`,
|
|
160
|
+
text: `${group.name} — ${group.description} One member is ${group.memberRepresents}. (${facts})`,
|
|
77
161
|
metadata: {
|
|
78
162
|
type: "input-kind",
|
|
79
|
-
kind:
|
|
80
|
-
pathPattern:
|
|
81
|
-
count:
|
|
82
|
-
...(
|
|
83
|
-
...(kind.axisLabels ? { axisLabels: kind.axisLabels } : {}),
|
|
163
|
+
kind: group.name,
|
|
164
|
+
pathPattern: group.pattern,
|
|
165
|
+
count: group.count,
|
|
166
|
+
...(group.format ? { format: group.format } : {}),
|
|
84
167
|
},
|
|
85
168
|
};
|
|
86
169
|
}
|
|
87
|
-
function entityEntry(analysisId, label, value,
|
|
88
|
-
const named =
|
|
89
|
-
const carried = named.map((
|
|
170
|
+
function entityEntry(analysisId, label, value, groups) {
|
|
171
|
+
const named = groups.slice(0, MAX_GROUPS_IN_ENTITY_TEXT);
|
|
172
|
+
const carried = named.map((group) => `${group.name} (${group.memberRepresents})`).join("; ");
|
|
90
173
|
return {
|
|
91
174
|
id: `/${analysisId}/entity/${encodeURIComponent(value)}`,
|
|
92
175
|
text: `${label} ${value} — input data for this ${label}: ${carried}.`,
|
|
@@ -94,17 +177,18 @@ function entityEntry(analysisId, label, value, kinds) {
|
|
|
94
177
|
type: "input",
|
|
95
178
|
entity: value,
|
|
96
179
|
axis: label,
|
|
97
|
-
kinds: named.map((
|
|
180
|
+
kinds: named.map((group) => group.name),
|
|
98
181
|
},
|
|
99
182
|
};
|
|
100
183
|
}
|
|
101
|
-
/** A file entry, for
|
|
184
|
+
/** A file entry, for a legacy profile that resolved to no structure at all. */
|
|
102
185
|
function fileEntry(analysisId, file) {
|
|
103
186
|
return {
|
|
104
187
|
id: `/${analysisId}/${file.path}`,
|
|
105
188
|
text: `${file.path} — ${file.description}`,
|
|
106
189
|
metadata: {
|
|
107
190
|
type: "input",
|
|
191
|
+
path: file.path,
|
|
108
192
|
...(file.dataType ? { dataType: file.dataType } : {}),
|
|
109
193
|
...(file.format ? { format: file.format } : {}),
|
|
110
194
|
...(file.tags ? { tags: file.tags } : {}),
|
|
@@ -112,23 +196,40 @@ function fileEntry(analysisId, file) {
|
|
|
112
196
|
};
|
|
113
197
|
}
|
|
114
198
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
199
|
+
* A snapshot of the kinds era, indexed as that era indexed it: one entry per kind and one
|
|
200
|
+
* per entity value. Its groups were never resolved to a membership, so there is no
|
|
201
|
+
* annotated-member tier to write.
|
|
118
202
|
*/
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return (files ?? []).map((file) => fileEntry(analysisId, file));
|
|
125
|
-
}
|
|
126
|
-
const entries = kinds.map((kind) => kindEntry(analysisId, kind));
|
|
203
|
+
function legacyEntries(analysisId, result, scan) {
|
|
204
|
+
const groups = profileGroups(result);
|
|
205
|
+
if (groups.length === 0)
|
|
206
|
+
return profileFileRecords(result).map((file) => fileEntry(analysisId, file));
|
|
207
|
+
const entries = groups.map((group) => kindEntry(analysisId, group));
|
|
127
208
|
const { values, distinct } = entityValues(scan);
|
|
128
209
|
if (values.length > 1 && values.length <= MAX_ENTITY_ENTRIES) {
|
|
129
|
-
const label =
|
|
210
|
+
const label = entityDimensionLabel(profileDimensions(result), distinct);
|
|
130
211
|
for (const value of values)
|
|
131
|
-
entries.push(entityEntry(analysisId, label, value,
|
|
212
|
+
entries.push(entityEntry(analysisId, label, value, groups));
|
|
213
|
+
}
|
|
214
|
+
return entries;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Build every index entry for a completed profile. Deterministic and model-free: each
|
|
218
|
+
* entry's text is templated from what the agent already wrote and what resolution already
|
|
219
|
+
* computed, so indexing costs no LLM tokens whatever the dataset's size.
|
|
220
|
+
*/
|
|
221
|
+
export function buildProfileIndexEntries(args) {
|
|
222
|
+
const { analysisId, result, scan } = args;
|
|
223
|
+
// Presence, not truthiness: a tree the scan kept nothing of resolves to `groups: []`,
|
|
224
|
+
// and that is a groups-era record with no groups, not a record from before groups.
|
|
225
|
+
if (result.groups === undefined)
|
|
226
|
+
return legacyEntries(analysisId, result, scan);
|
|
227
|
+
const entries = result.groups.map((group) => groupEntry(analysisId, group));
|
|
228
|
+
for (const dimension of result.dimensions ?? [])
|
|
229
|
+
entries.push(dimensionEntry(analysisId, dimension));
|
|
230
|
+
for (const group of result.groups) {
|
|
231
|
+
for (const annotation of group.memberAnnotations ?? [])
|
|
232
|
+
entries.push(memberEntry(analysisId, group, annotation));
|
|
132
233
|
}
|
|
133
234
|
return entries;
|
|
134
235
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit-time resolution — the half of the authoring contract the agent does not write.
|
|
3
|
+
*
|
|
4
|
+
* The agent submits operations on the menu; this computes what they MEAN over the scanned
|
|
5
|
+
* tree: which members each group holds, how many files that is, what each group's slots
|
|
6
|
+
* take once a split has narrowed them, and whether the whole thing partitions the kept
|
|
7
|
+
* files. A count the resolution can compute is not the agent's to assert, so nothing here
|
|
8
|
+
* reads a number out of the submission.
|
|
9
|
+
*
|
|
10
|
+
* The partition is the load-bearing invariant: every kept file lands in exactly one
|
|
11
|
+
* group. Overlapping operations are an error returned for repair rather than resolved by
|
|
12
|
+
* precedence — precedence would silently pick a winner and the profile would still read
|
|
13
|
+
* as complete. Past the last repair round there is nobody left to return them to, so a
|
|
14
|
+
* still-contested file is removed from EVERY claimant and swept, recorded as a machine
|
|
15
|
+
* finding: unowned and counted once beats owned by a winner nobody chose. Files no
|
|
16
|
+
* operation claims are swept into the same visible `unclassified` group, so "how much did
|
|
17
|
+
* the profile cover" stops being a question and becomes a census.
|
|
18
|
+
*/
|
|
19
|
+
import type { SetMenu } from "../input-scan/menu.js";
|
|
20
|
+
import type { DetectedSets } from "../input-scan/set-types.js";
|
|
21
|
+
import type { DataProfileDimension, DataProfileGroup, DataProfilePartition, DataProfileProbeReport, DataProfileRecipeStep } from "../contracts/data-profile.js";
|
|
22
|
+
import type { ProfileSubmission } from "../schemas/data-profile-schemas.js";
|
|
23
|
+
/** The id the swept residue carries. Visible in the record and in the accounting. */
|
|
24
|
+
export declare const UNCLASSIFIED_GROUP_ID = "unclassified";
|
|
25
|
+
/**
|
|
26
|
+
* Sweep paths the recipe carries, so a replay can tell a file the last profile declined
|
|
27
|
+
* to classify from one that is new to the tree.
|
|
28
|
+
*
|
|
29
|
+
* Bounded because the record is a thin ledger and the row is detoasted by every reader.
|
|
30
|
+
* A sweep past the bound records a prefix and says so; its replay wakes the agent, which
|
|
31
|
+
* is the honest outcome for a profile that left that many files unaccounted for.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MAX_UNCLASSIFIED_RECIPE_PATHS = 1000;
|
|
34
|
+
/** Contested paths named in the accounting before the count stands alone. */
|
|
35
|
+
export declare const MAX_CONTESTED_SAMPLE = 10;
|
|
36
|
+
/** A resolved group plus the membership the record deliberately does not carry. */
|
|
37
|
+
export interface ResolvedGroup extends DataProfileGroup {
|
|
38
|
+
/** Member paths — what the index projects from. The persisted record holds counts only. */
|
|
39
|
+
readonly memberPaths: readonly string[];
|
|
40
|
+
}
|
|
41
|
+
export interface ProfileResolution {
|
|
42
|
+
/** Repairable faults. A submission carrying any of these is handed back to the agent whole. */
|
|
43
|
+
readonly errors: readonly string[];
|
|
44
|
+
/** Kept members no operation claimed. Already swept into the `unclassified` group below. */
|
|
45
|
+
readonly unclaimed: readonly string[];
|
|
46
|
+
/** Kept members more than one operation claimed. Swept rather than awarded on the final round. */
|
|
47
|
+
readonly contested: readonly string[];
|
|
48
|
+
readonly groups: readonly ResolvedGroup[];
|
|
49
|
+
readonly dimensions: readonly DataProfileDimension[];
|
|
50
|
+
readonly probes: readonly DataProfileProbeReport[];
|
|
51
|
+
readonly partition: DataProfilePartition;
|
|
52
|
+
readonly recipe: readonly DataProfileRecipeStep[];
|
|
53
|
+
}
|
|
54
|
+
export interface ResolveOptions {
|
|
55
|
+
/**
|
|
56
|
+
* True when no repair round follows. A contested file is then swept rather than
|
|
57
|
+
* reported: there is nobody left to hand the error to, and leaving it with whichever
|
|
58
|
+
* operation ran first would be the precedence the partition forbids.
|
|
59
|
+
*/
|
|
60
|
+
readonly finalRound?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a submission against the scan it was authored over.
|
|
64
|
+
*
|
|
65
|
+
* Pure and total: it always returns a resolution, and the caller decides what to do with
|
|
66
|
+
* `errors` and `unclaimed`. A first round hands both back to the live agent; the repair
|
|
67
|
+
* round is a full resubmit, and after it the residue sweeps rather than blocking a
|
|
68
|
+
* profile that gates planning.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveProfileSubmission(submission: ProfileSubmission, detected: DetectedSets, menu?: SetMenu, options?: ResolveOptions): ProfileResolution;
|
|
71
|
+
/** The repair round's errors, phrased for the agent that must fix them in one resubmit. */
|
|
72
|
+
export declare function formatResolutionErrors(resolution: ProfileResolution, unclaimedSample?: number): string;
|