@inflexa-ai/harness 0.28.1 → 0.29.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.
Files changed (83) hide show
  1. package/dist/app/data-profile-orientation.d.ts +52 -27
  2. package/dist/app/data-profile-orientation.js +331 -92
  3. package/dist/app/data-profile-view.d.ts +70 -0
  4. package/dist/app/data-profile-view.js +123 -0
  5. package/dist/app/synthesize-run.js +1 -1
  6. package/dist/contracts/data-profile.d.ts +276 -25
  7. package/dist/contracts/index.d.ts +3 -1
  8. package/dist/contracts/index.js +1 -0
  9. package/dist/contracts/profile-vocabulary.d.ts +103 -0
  10. package/dist/contracts/profile-vocabulary.js +449 -0
  11. package/dist/contracts/target-dossier.d.ts +21 -21
  12. package/dist/execution/input-signature.d.ts +16 -3
  13. package/dist/execution/input-signature.js +0 -0
  14. package/dist/execution/post-step-pipeline.js +1 -1
  15. package/dist/execution/staged-input.d.ts +4 -4
  16. package/dist/index.d.ts +5 -1
  17. package/dist/index.js +7 -0
  18. package/dist/input-scan/clustering.d.ts +63 -0
  19. package/dist/input-scan/clustering.js +208 -0
  20. package/dist/input-scan/companions.d.ts +30 -0
  21. package/dist/input-scan/companions.js +138 -0
  22. package/dist/input-scan/decoder/container_readout.py +171 -0
  23. package/dist/input-scan/detect-sets.d.ts +24 -0
  24. package/dist/input-scan/detect-sets.js +279 -0
  25. package/dist/input-scan/enrich.d.ts +62 -19
  26. package/dist/input-scan/enrich.js +132 -187
  27. package/dist/input-scan/formats.d.ts +13 -5
  28. package/dist/input-scan/formats.js +13 -5
  29. package/dist/input-scan/index.d.ts +10 -4
  30. package/dist/input-scan/index.js +7 -4
  31. package/dist/input-scan/markers.d.ts +21 -0
  32. package/dist/input-scan/markers.js +89 -0
  33. package/dist/input-scan/menu.d.ts +127 -0
  34. package/dist/input-scan/menu.js +0 -0
  35. package/dist/input-scan/quarantine.d.ts +17 -0
  36. package/dist/input-scan/quarantine.js +69 -0
  37. package/dist/input-scan/readout-budget.d.ts +32 -0
  38. package/dist/input-scan/readout-budget.js +61 -0
  39. package/dist/input-scan/readout.d.ts +33 -0
  40. package/dist/input-scan/readout.js +336 -0
  41. package/dist/input-scan/scan.d.ts +5 -4
  42. package/dist/input-scan/scan.js +5 -5
  43. package/dist/input-scan/set-types.d.ts +209 -0
  44. package/dist/input-scan/set-types.js +15 -0
  45. package/dist/input-scan/templates.d.ts +93 -0
  46. package/dist/input-scan/templates.js +369 -0
  47. package/dist/input-scan/tokens.d.ts +94 -0
  48. package/dist/input-scan/tokens.js +267 -0
  49. package/dist/input-scan/tuning.d.ts +55 -0
  50. package/dist/input-scan/tuning.js +55 -0
  51. package/dist/input-scan/types.d.ts +2 -2
  52. package/dist/prompts/conversation.d.ts +1 -1
  53. package/dist/prompts/conversation.js +8 -6
  54. package/dist/prompts/report-session.d.ts +1 -1
  55. package/dist/prompts/report-session.js +3 -2
  56. package/dist/prompts/sandbox/data-profiler.d.ts +10 -1
  57. package/dist/prompts/sandbox/data-profiler.js +197 -53
  58. package/dist/prompts/sandbox-standards.d.ts +1 -1
  59. package/dist/prompts/sandbox-standards.js +10 -7
  60. package/dist/schemas/data-profile-schemas.d.ts +943 -80
  61. package/dist/schemas/data-profile-schemas.js +300 -115
  62. package/dist/state/data-profile.d.ts +1 -1
  63. package/dist/state/index.d.ts +1 -1
  64. package/dist/state/vector-store.d.ts +13 -0
  65. package/dist/state/vector-store.js +8 -0
  66. package/dist/tasks/data-profile-absorb.d.ts +78 -0
  67. package/dist/tasks/data-profile-absorb.js +285 -0
  68. package/dist/tasks/data-profile-index.d.ts +34 -25
  69. package/dist/tasks/data-profile-index.js +160 -59
  70. package/dist/tasks/data-profile-resolve.d.ts +72 -0
  71. package/dist/tasks/data-profile-resolve.js +639 -0
  72. package/dist/tasks/data-profile.d.ts +16 -5
  73. package/dist/tasks/data-profile.js +270 -131
  74. package/dist/tools/research/inspect-data-profile.d.ts +48 -22
  75. package/dist/tools/research/inspect-data-profile.js +73 -54
  76. package/dist/tools/workspace/scan-inputs.d.ts +4 -4
  77. package/dist/tools/workspace/scan-inputs.js +13 -14
  78. package/dist/tools/workspace/workspace-search.d.ts +11 -2
  79. package/dist/tools/workspace/workspace-search.js +44 -16
  80. package/dist/workflows/target-assessment/phase4-assemble.d.ts +21 -21
  81. package/package.json +5 -3
  82. package/dist/input-scan/coverage.d.ts +0 -22
  83. 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 scan crossed with the submitted kinds: for each kind,
5
- * one entry; for each entity, one entry templated from the kinds it participates in and
6
- * its own axis value. It has no persisted representation of its own and nothing derives
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
- * Two tiers, because they answer different queries. A query naming a KIND of data
12
- * ("variant calls") wants one result carrying the set's count and path pattern; a
13
- * per-entity tier would return five arbitrary subjects out of thousands of
14
- * indistinguishable ones. A query naming an ENTITY ("PT0421") wants that entity. A
15
- * per-file tier adds nothing: a file is identified by its kind and its entity, and its
16
- * path is on the filesystem.
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 for the entity tier, so searches written
19
- * against it keep matching; `type: "input-kind"` is additive. Renaming `"input"` would
20
- * have silently broken existing search on every new analysis.
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
- /** Entity entries built per analysis. Past this the entity tier is a liability, not a search aid. */
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
- /** Kind names named in one entity's entry text. */
25
- const MAX_KINDS_IN_ENTITY_TEXT = 6;
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 variable position
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 — 1171 subjects, against three timepoints and two
39
- * replicates. Taking the widest position rather than the cross product is deliberate:
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 axis label for an entity set. The agent labels axes; the scan counts values, so
60
- * the axis whose cardinality matches the observed set is the one the agent was naming.
61
- * With no match the entries still index under a neutral label rather than not at all.
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 entityAxisLabel(axes, distinct) {
64
- if (!axes || axes.length === 0)
147
+ function entityDimensionLabel(dimensions, distinct) {
148
+ if (dimensions.length === 0)
65
149
  return "entity";
66
- const exact = axes.find((axis) => axis.cardinality === distinct);
150
+ const exact = dimensions.find((dimension) => dimension.cardinalities.includes(distinct));
67
151
  if (exact)
68
152
  return exact.label;
69
- const widest = [...axes].sort((a, b) => b.cardinality - a.cardinality)[0];
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, kind) {
73
- const facts = [`${kind.count} files`, kind.format, kind.pathPattern].filter((f) => Boolean(f)).join(", ");
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(kind.name)}`,
76
- text: `${kind.name} — ${kind.description} One member is ${kind.memberRepresents}. (${facts})`,
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: kind.name,
80
- pathPattern: kind.pathPattern,
81
- count: kind.count,
82
- ...(kind.format ? { format: kind.format } : {}),
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, kinds) {
88
- const named = kinds.slice(0, MAX_KINDS_IN_ENTITY_TEXT);
89
- const carried = named.map((kind) => `${kind.name} (${kind.memberRepresents})`).join("; ");
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((kind) => kind.name),
180
+ kinds: named.map((group) => group.name),
98
181
  },
99
182
  };
100
183
  }
101
- /** A file entry, for the degenerate case where a profile submitted no kinds at all. */
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
- * Build every index entry for a completed profile. Deterministic and model-free: the
116
- * text is templated from the kind's own description and the entity's axis value, so
117
- * indexing costs no LLM tokens whatever the dataset's size.
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
- export function buildProfileIndexEntries(args) {
120
- const { analysisId, kinds, axes, scan, files } = args;
121
- if (kinds.length === 0) {
122
- // No kinds is a degenerate profile, but the notable files it did describe stay
123
- // discoverable — the same losslessness the per-file fallback description had.
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 = entityAxisLabel(axes, distinct);
210
+ const label = entityDimensionLabel(profileDimensions(result), distinct);
130
211
  for (const value of values)
131
- entries.push(entityEntry(analysisId, label, value, kinds));
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;