@inflexa-ai/harness 0.17.0 → 0.18.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 (221) hide show
  1. package/dist/agents/report-builder.d.ts +2 -0
  2. package/dist/agents/report-builder.js +2 -0
  3. package/dist/app/spawn-report-session.d.ts +85 -0
  4. package/dist/app/spawn-report-session.js +83 -0
  5. package/dist/contracts/chat-events.d.ts +9 -0
  6. package/dist/contracts/index.d.ts +6 -2
  7. package/dist/contracts/index.js +3 -1
  8. package/dist/contracts/organ-system.d.ts +49 -0
  9. package/dist/contracts/organ-system.js +67 -0
  10. package/dist/contracts/report-blocks.d.ts +564 -0
  11. package/dist/contracts/report-blocks.js +117 -0
  12. package/dist/contracts/report-reference.d.ts +459 -0
  13. package/dist/contracts/report-reference.js +256 -0
  14. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  15. package/dist/contracts/schemas/chat-events.js +2 -0
  16. package/dist/contracts/severity.d.ts +15 -0
  17. package/dist/contracts/severity.js +10 -0
  18. package/dist/contracts/target-dossier.d.ts +8675 -12972
  19. package/dist/contracts/target-dossier.js +534 -591
  20. package/dist/data/safety-panel-schema.d.ts +18 -5
  21. package/dist/data/safety-panel-schema.js +4 -4
  22. package/dist/execution/artifact-metadata.js +1 -0
  23. package/dist/execution/report-runner.js +1 -3
  24. package/dist/execution/run-synthesis.js +2 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +19 -0
  27. package/dist/lib/async-utils.d.ts +16 -4
  28. package/dist/lib/async-utils.js +20 -15
  29. package/dist/lib/host-concurrency.js +1 -0
  30. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  31. package/dist/loop/run-agent.js +58 -10
  32. package/dist/loop/tool-detail.d.ts +40 -0
  33. package/dist/loop/tool-detail.js +35 -3
  34. package/dist/loop/types.d.ts +13 -0
  35. package/dist/memory/thread-history.d.ts +10 -0
  36. package/dist/memory/thread-history.js +12 -1
  37. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  38. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  39. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  40. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  41. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  42. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  43. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  44. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  45. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  46. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  47. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  48. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  49. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  50. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  51. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  52. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  53. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  54. package/dist/providers/__fixtures__/session.d.ts +5 -4
  55. package/dist/providers/__fixtures__/session.js +4 -4
  56. package/dist/report-model/block-walk.d.ts +56 -0
  57. package/dist/report-model/block-walk.js +89 -0
  58. package/dist/report-model/draft-finish.d.ts +60 -0
  59. package/dist/report-model/draft-finish.js +56 -0
  60. package/dist/report-model/draft-operations.d.ts +125 -0
  61. package/dist/report-model/draft-operations.js +341 -0
  62. package/dist/report-model/draft-read.d.ts +59 -0
  63. package/dist/report-model/draft-read.js +109 -0
  64. package/dist/report-model/draft.d.ts +51 -0
  65. package/dist/report-model/draft.js +32 -0
  66. package/dist/report-model/fixture-resolver.d.ts +8 -0
  67. package/dist/report-model/fixture-resolver.js +252 -0
  68. package/dist/report-model/mint-snapshot.d.ts +30 -0
  69. package/dist/report-model/mint-snapshot.js +40 -0
  70. package/dist/report-model/reference-resolver.d.ts +113 -0
  71. package/dist/report-model/reference-resolver.js +60 -0
  72. package/dist/report-model/structural-validation.d.ts +22 -0
  73. package/dist/report-model/structural-validation.js +68 -0
  74. package/dist/report-model/validate.d.ts +40 -0
  75. package/dist/report-model/validate.js +80 -0
  76. package/dist/report-render/chart.d.ts +33 -0
  77. package/dist/report-render/chart.js +538 -0
  78. package/dist/report-render/page.d.ts +127 -0
  79. package/dist/report-render/page.js +307 -0
  80. package/dist/report-render/references.d.ts +32 -0
  81. package/dist/report-render/references.js +44 -0
  82. package/dist/report-render/render.d.ts +25 -0
  83. package/dist/report-render/render.js +125 -0
  84. package/dist/report-render/script-json.d.ts +13 -0
  85. package/dist/report-render/script-json.js +15 -0
  86. package/dist/report-render/types.d.ts +39 -0
  87. package/dist/report-render/types.js +10 -0
  88. package/dist/report-render/views/chart-view.d.ts +11 -0
  89. package/dist/report-render/views/chart-view.js +23 -0
  90. package/dist/report-render/views/page-view.d.ts +20 -0
  91. package/dist/report-render/views/page-view.js +49 -0
  92. package/dist/report-render/views/prose.d.ts +28 -0
  93. package/dist/report-render/views/prose.js +75 -0
  94. package/dist/report-render/views/references-view.d.ts +20 -0
  95. package/dist/report-render/views/references-view.js +64 -0
  96. package/dist/report-render/views/values.d.ts +42 -0
  97. package/dist/report-render/views/values.js +43 -0
  98. package/dist/runtime/boot.d.ts +3 -3
  99. package/dist/runtime/boot.js +15 -4
  100. package/dist/runtime/dbos.d.ts +4 -4
  101. package/dist/state/artifacts.d.ts +16 -0
  102. package/dist/state/artifacts.js +18 -0
  103. package/dist/state/init.js +31 -0
  104. package/dist/state/purge-analysis.js +1 -0
  105. package/dist/state/report-versions.d.ts +132 -0
  106. package/dist/state/report-versions.js +194 -0
  107. package/dist/tasks/data-profile.js +1 -0
  108. package/dist/tools/bio/chembl.js +1 -0
  109. package/dist/tools/bio/comptox.js +1 -0
  110. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  111. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  112. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  113. package/dist/tools/bio/keys.d.ts +3 -2
  114. package/dist/tools/bio/keys.js +3 -2
  115. package/dist/tools/bio/lookup-annotation.js +1 -0
  116. package/dist/tools/bio/opentargets.d.ts +3 -3
  117. package/dist/tools/bio/opentargets.js +4 -3
  118. package/dist/tools/bio/pubchem.js +1 -0
  119. package/dist/tools/bio/search-clinical-trials.js +1 -0
  120. package/dist/tools/bio/search-faers.js +1 -0
  121. package/dist/tools/bio/search-geo-datasets.js +1 -0
  122. package/dist/tools/bio/search-interactions.js +1 -0
  123. package/dist/tools/bio/target-safety.d.ts +4 -6
  124. package/dist/tools/bio/target-safety.js +7 -6
  125. package/dist/tools/define-tool.d.ts +19 -12
  126. package/dist/tools/define-tool.js +4 -2
  127. package/dist/tools/display/show-user.js +1 -0
  128. package/dist/tools/iterate-report.js +2 -0
  129. package/dist/tools/lib/chembl-client.d.ts +2 -0
  130. package/dist/tools/lib/chembl-client.js +1 -0
  131. package/dist/tools/lib/monarch-client.d.ts +42 -0
  132. package/dist/tools/lib/monarch-client.js +100 -0
  133. package/dist/tools/report/build-report.js +1 -0
  134. package/dist/tools/report/mint-preview-url.js +1 -0
  135. package/dist/tools/report/preview-snapshot.js +1 -0
  136. package/dist/tools/report/submit-report.js +1 -0
  137. package/dist/tools/report/version-fs.js +4 -0
  138. package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
  139. package/dist/tools/report-authoring/authoring-tools.js +352 -0
  140. package/dist/tools/research/context7-docs.js +2 -0
  141. package/dist/tools/research/generate-analogy-report.js +1 -0
  142. package/dist/tools/research/generate-plan.js +3 -0
  143. package/dist/tools/research/inspect-data-profile.js +4 -0
  144. package/dist/tools/research/literature-reviewer.js +1 -0
  145. package/dist/tools/research/search-arxiv.js +1 -0
  146. package/dist/tools/research/search-github-repos.js +1 -0
  147. package/dist/tools/research/search-semantic-scholar.js +1 -0
  148. package/dist/tools/sandbox/list-available-packages.js +23 -0
  149. package/dist/tools/sandbox/list-available-refs.js +36 -0
  150. package/dist/tools/sandbox/report-blocker.js +1 -0
  151. package/dist/tools/sandbox/skills.js +2 -0
  152. package/dist/tools/workspace/file-stat.js +1 -0
  153. package/dist/tools/workspace/show-file.js +2 -0
  154. package/dist/tools/workspace/show-plan.js +1 -0
  155. package/dist/workflows/execute-analysis.d.ts +5 -1
  156. package/dist/workflows/execute-analysis.js +5 -5
  157. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  158. package/dist/workflows/execute-target-assessment.js +78 -16
  159. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  160. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  161. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  162. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  163. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  164. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  165. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  166. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  167. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  168. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  169. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  170. package/dist/workflows/target-assessment/coverage.js +33 -29
  171. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  172. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  173. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  174. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  175. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  176. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  177. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  178. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  179. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  180. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  181. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  182. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  183. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  184. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  185. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  186. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  187. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  188. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  189. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  190. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  191. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  192. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  193. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  194. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  195. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  196. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  197. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  198. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  199. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  200. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  201. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  202. package/dist/workflows/target-assessment/metrics.js +1 -13
  203. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
  204. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  205. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  206. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  207. package/dist/workflows/target-assessment/schemas.d.ts +46 -1
  208. package/dist/workflows/target-assessment/schemas.js +19 -0
  209. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  210. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  211. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
  212. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  213. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
  214. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  215. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  216. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  217. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  218. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  219. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  220. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  221. package/package.json +4 -1
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The read-back surface of a draft.
3
+ *
4
+ * The agent holds the outline as its primary view. The outline gives one entry for each block: the id,
5
+ * the kind, the nesting depth, and a short label. It carries no binding and no full prose. Thus the full
6
+ * tree stays out of the agent context.
7
+ *
8
+ * A read of one block by its id gives the full block. The agent pulls a block only when it needs one. A
9
+ * section gives its own fields and the ids of its children, and never the subtree. The outline already
10
+ * names every descendant, thus a subtree here would return the whole tree that the outline exists to keep
11
+ * out.
12
+ */
13
+ import { locate } from "./draft-operations.js";
14
+ /** The label keeps a prose head of at most this many code points. A clipped label ends with the marker. */
15
+ const PROSE_CLIP = 80;
16
+ /** The marker that a clipped label ends with. It counts toward `PROSE_CLIP`. */
17
+ const CLIP_MARKER = "…";
18
+ /**
19
+ * Clip a label to `PROSE_CLIP` code points, and mark a clipped label.
20
+ *
21
+ * The cut counts code points and not UTF-16 units, because a cut at a fixed index splits a surrogate pair
22
+ * whenever an astral character straddles the boundary. A lone surrogate has no UTF-8 encoding, thus the
23
+ * agent would read a replacement character in place of the character the author wrote.
24
+ *
25
+ * A clipped label carries the marker, because a bare head and a whole short label read the same. Without
26
+ * the marker the agent cannot tell whether the prose ends there, and it acts on a truncated claim as if it
27
+ * were the whole one.
28
+ */
29
+ function clip(text) {
30
+ const points = [...text];
31
+ if (points.length <= PROSE_CLIP) {
32
+ return text;
33
+ }
34
+ return `${points.slice(0, PROSE_CLIP - CLIP_MARKER.length).join("")}${CLIP_MARKER}`;
35
+ }
36
+ /**
37
+ * The short label of one block.
38
+ *
39
+ * A section, a metric, a table, and a chart give a titled name. A figure and a citation give a caption or
40
+ * a note. A text and a claim carry prose. Every one of them clips: a caption, a note, and a title are free
41
+ * prose too, thus an unclipped arm would put a paragraph into each outline that names the block.
42
+ *
43
+ * A block with an absent optional field gives an empty label.
44
+ */
45
+ function labelOf(block) {
46
+ switch (block.kind) {
47
+ case "section":
48
+ return clip(block.title);
49
+ case "metric":
50
+ return clip(block.label);
51
+ case "table":
52
+ return clip(block.title ?? "");
53
+ case "chart":
54
+ return clip(block.title ?? "");
55
+ case "figure":
56
+ return clip(block.caption ?? "");
57
+ case "citation":
58
+ return clip(block.note ?? "");
59
+ case "text":
60
+ return clip(block.content.prose);
61
+ case "claim":
62
+ return clip(block.content.prose);
63
+ }
64
+ }
65
+ /**
66
+ * Build the outline of a draft. The walk is pre-order, thus the entries follow the document order. A
67
+ * section comes before its children, and a child sits one depth deeper than its parent.
68
+ */
69
+ export function buildOutline(draft) {
70
+ const entries = [];
71
+ const visit = (block, depth) => {
72
+ entries.push({ id: block.id, kind: block.kind, depth, label: labelOf(block) });
73
+ if (block.kind === "section") {
74
+ for (const child of block.blocks) {
75
+ visit(child, depth + 1);
76
+ }
77
+ }
78
+ };
79
+ for (const section of draft.sections) {
80
+ visit(section, 0);
81
+ }
82
+ return entries;
83
+ }
84
+ /**
85
+ * Build the outline of one container: an entry for each direct child, and nothing deeper.
86
+ *
87
+ * A landed operation reports the container that it changed, and the whole outline would grow the context
88
+ * by the size of the draft on every call. The child order is what the agent cannot know on its own, and a
89
+ * descendant did not move, thus one level answers the question that a landing raises.
90
+ *
91
+ * `depth` is the depth of the children, thus the entries read the same as the entries of a full outline.
92
+ */
93
+ export function childOutline(blocks, depth) {
94
+ return blocks.map((block) => ({ id: block.id, kind: block.kind, depth, label: labelOf(block) }));
95
+ }
96
+ /**
97
+ * Read one block by its id. An atom gives the block as it is, with its bindings. A section gives its own
98
+ * fields and the id of each child. A block that no id holds gives `undefined`.
99
+ */
100
+ export function readBlock(draft, id) {
101
+ const found = locate(draft, id)?.block;
102
+ if (found === undefined) {
103
+ return undefined;
104
+ }
105
+ if (found.kind === "section") {
106
+ return { kind: "section", id: found.id, title: found.title, childIds: found.blocks.map((child) => child.id) };
107
+ }
108
+ return found;
109
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The draft grammar of a report document under composition.
3
+ *
4
+ * An edit refuses what is wrong, and the finish refuses what is missing. Thus a draft permits emptiness,
5
+ * and the contract does not. The draft admits an empty section list and an empty section, and the
6
+ * completeness rules of `ReportDocumentSchema` gate one time, at the finish.
7
+ *
8
+ * The grammar composes from the contract atoms, and it does not copy them. The draft spreads
9
+ * `ATOM_BLOCK_SCHEMAS`, and the relaxed section is the only new part. Thus a ninth block kind reaches the
10
+ * draft the moment that the contract declares it, and the draft never drifts from the contract.
11
+ *
12
+ * The recursion mirrors `SectionBlockSchema`. The `blocks` field is a `z.lazy` and not a getter. Thus the
13
+ * discriminated union reads the `kind` discriminator at construction time, and it needs no reference to
14
+ * `DraftBlockSchema` before it exists. The relaxed rule holds at every depth, because a nested section is
15
+ * a `DraftSectionBlock` too.
16
+ */
17
+ import { z } from "zod";
18
+ import { type AtomBlock } from "../contracts/report-blocks.js";
19
+ /**
20
+ * The draft section shape as a plain type. TypeScript cannot infer the block tree through the union
21
+ * cycle, thus the recursive members carry an explicit type. `DraftSectionBlock` and `DraftBlock` break
22
+ * the cycle.
23
+ */
24
+ export interface DraftSectionBlock {
25
+ kind: "section";
26
+ id: string;
27
+ title: string;
28
+ blocks: DraftBlock[];
29
+ }
30
+ /** One draft block of any of the eight kinds. The atoms keep the contract shape, and only the section relaxes. */
31
+ export type DraftBlock = AtomBlock | DraftSectionBlock;
32
+ /** A draft section permits zero child blocks, thus an empty section is a legal draft state. */
33
+ export declare const DraftSectionBlockSchema: z.ZodObject<{
34
+ kind: z.ZodLiteral<"section">;
35
+ id: z.ZodString;
36
+ title: z.ZodString;
37
+ blocks: z.ZodLazy<z.ZodArray<z.ZodType<DraftBlock, unknown, z.core.$ZodTypeInternals<DraftBlock, unknown>>>>;
38
+ }, z.core.$strict>;
39
+ /** The draft block union. A `z.ZodType<DraftBlock>` annotation gives the recursive members a stable type. */
40
+ export declare const DraftBlockSchema: z.ZodType<DraftBlock>;
41
+ /** The root of a draft. It permits zero sections, thus an empty draft is a legal state. */
42
+ export declare const DraftDocumentSchema: z.ZodObject<{
43
+ title: z.ZodString;
44
+ sections: z.ZodArray<z.ZodObject<{
45
+ kind: z.ZodLiteral<"section">;
46
+ id: z.ZodString;
47
+ title: z.ZodString;
48
+ blocks: z.ZodLazy<z.ZodArray<z.ZodType<DraftBlock, unknown, z.core.$ZodTypeInternals<DraftBlock, unknown>>>>;
49
+ }, z.core.$strict>>;
50
+ }, z.core.$strict>;
51
+ export type DraftDocument = z.infer<typeof DraftDocumentSchema>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The draft grammar of a report document under composition.
3
+ *
4
+ * An edit refuses what is wrong, and the finish refuses what is missing. Thus a draft permits emptiness,
5
+ * and the contract does not. The draft admits an empty section list and an empty section, and the
6
+ * completeness rules of `ReportDocumentSchema` gate one time, at the finish.
7
+ *
8
+ * The grammar composes from the contract atoms, and it does not copy them. The draft spreads
9
+ * `ATOM_BLOCK_SCHEMAS`, and the relaxed section is the only new part. Thus a ninth block kind reaches the
10
+ * draft the moment that the contract declares it, and the draft never drifts from the contract.
11
+ *
12
+ * The recursion mirrors `SectionBlockSchema`. The `blocks` field is a `z.lazy` and not a getter. Thus the
13
+ * discriminated union reads the `kind` discriminator at construction time, and it needs no reference to
14
+ * `DraftBlockSchema` before it exists. The relaxed rule holds at every depth, because a nested section is
15
+ * a `DraftSectionBlock` too.
16
+ */
17
+ import { z } from "zod";
18
+ import { ATOM_BLOCK_SCHEMAS } from "../contracts/report-blocks.js";
19
+ /** A draft section permits zero child blocks, thus an empty section is a legal draft state. */
20
+ export const DraftSectionBlockSchema = z.strictObject({
21
+ kind: z.literal("section"),
22
+ id: z.string().min(1),
23
+ title: z.string(),
24
+ blocks: z.lazy(() => z.array(DraftBlockSchema)),
25
+ });
26
+ /** The draft block union. A `z.ZodType<DraftBlock>` annotation gives the recursive members a stable type. */
27
+ export const DraftBlockSchema = z.discriminatedUnion("kind", [DraftSectionBlockSchema, ...ATOM_BLOCK_SCHEMAS]);
28
+ /** The root of a draft. It permits zero sections, thus an empty draft is a legal state. */
29
+ export const DraftDocumentSchema = z.strictObject({
30
+ title: z.string(),
31
+ sections: z.array(DraftSectionBlockSchema),
32
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A local, in-memory realization of the reference resolver. It reads only the snapshot that it is given,
3
+ * thus it needs no storage and no network. A test and a benchmark use it to exercise the resolution and
4
+ * assertion rules without a host.
5
+ */
6
+ import { type ReferenceResolver } from "./reference-resolver.js";
7
+ /** Make a resolver that reads only the snapshot. It holds no state, thus one instance serves every call. */
8
+ export declare function createFixtureResolver(): ReferenceResolver;
@@ -0,0 +1,252 @@
1
+ /**
2
+ * A local, in-memory realization of the reference resolver. It reads only the snapshot that it is given,
3
+ * thus it needs no storage and no network. A test and a benchmark use it to exercise the resolution and
4
+ * assertion rules without a host.
5
+ */
6
+ import { err, ok } from "neverthrow";
7
+ import { columnsHeldByNoRow, fileTypeHoldsNoCell, snapshotEntry, } from "./reference-resolver.js";
8
+ /**
9
+ * The relative epsilon of a value match with no authored tolerance.
10
+ *
11
+ * Float arithmetic shifts a computed value in its last bits. For example, `0.3 - 0.1` gives
12
+ * 0.19999999999999998, and exact equality would report that noise as a fabrication.
13
+ *
14
+ * The epsilon absorbs the noise and nothing else. It is far below the difference that a rounded figure
15
+ * makes, thus an author who writes 0.05 against a true 0.0499 still fails. An author who wants a rounded
16
+ * figure to pass states a `tolerance`, which is the one place where the intent belongs.
17
+ */
18
+ const RELATIVE_EPSILON = 1e-9;
19
+ /** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
20
+ function fail(reference, reason, detail) {
21
+ const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
22
+ return err(failure);
23
+ }
24
+ /** A short account of a derivation input, for a detail that names which input broke the arithmetic. */
25
+ function describeReference(reference) {
26
+ return `artifact value at ${reference.path} column ${reference.locator.column}`;
27
+ }
28
+ /**
29
+ * Read an authored value or a resolved cell as a finite number.
30
+ *
31
+ * A text-backed artifact such as a CSV holds every cell as a string, thus a numeric column arrives as
32
+ * `"0.01"` and not as `0.01`. A comparison that respects the JavaScript type would fail an author who
33
+ * states the number, and the arithmetic of a derivation could never run over a real CSV.
34
+ *
35
+ * The parse reads the whole string, thus `"12 genes"` stays text and never reads as 12. `Number` accepts
36
+ * the exponent form that a p-value uses, for example `"1.2e-45"`. A blank string is not a number, because
37
+ * `Number("")` gives 0 and an empty cell must never read as zero.
38
+ */
39
+ function asFiniteNumber(value) {
40
+ if (typeof value === "number") {
41
+ return Number.isFinite(value) ? value : undefined;
42
+ }
43
+ if (value.trim() === "") {
44
+ return undefined;
45
+ }
46
+ const parsed = Number(value);
47
+ return Number.isFinite(parsed) ? parsed : undefined;
48
+ }
49
+ /** Name a value with its type, so that a failed match never reads as two identical values. */
50
+ function describeValue(value) {
51
+ return typeof value === "string" ? `the string ${JSON.stringify(value)}` : `the number ${value}`;
52
+ }
53
+ /**
54
+ * Match a resolved value against an authored one.
55
+ *
56
+ * The comparison is numeric whenever both sides read as a finite number, thus the CSV cell `"0.01"`
57
+ * matches the number 0.01 that an author states. An authored tolerance is an absolute difference, and it
58
+ * is the author's own statement of how close is close enough. With no tolerance the comparison is
59
+ * relative, because an exact match would fail on the float noise of a computed value. Any other pair
60
+ * matches on exact equality.
61
+ */
62
+ function valuesMatch(expected, actual, tolerance) {
63
+ const expectedNumber = asFiniteNumber(expected);
64
+ const actualNumber = asFiniteNumber(actual);
65
+ if (expectedNumber !== undefined && actualNumber !== undefined) {
66
+ if (tolerance !== undefined) {
67
+ return Math.abs(expectedNumber - actualNumber) <= tolerance;
68
+ }
69
+ return Math.abs(expectedNumber - actualNumber) <= RELATIVE_EPSILON * Math.max(1, Math.abs(expectedNumber), Math.abs(actualNumber));
70
+ }
71
+ return expected === actual;
72
+ }
73
+ /** Match a resolved scalar against the authored value, under the authored tolerance. */
74
+ function checkValueAssertion(reference, expected, tolerance, resolved) {
75
+ if (expected === undefined || valuesMatch(expected, resolved, tolerance)) {
76
+ return undefined;
77
+ }
78
+ return { reference, reason: "assertion-failed", detail: `expected ${describeValue(expected)} but resolved ${describeValue(resolved)}` };
79
+ }
80
+ /** Match the resolved citation key against the authored value. The key carries its `idKind:` prefix. */
81
+ function checkCitationAssertion(reference, expected, resolved) {
82
+ if (expected === undefined || expected === resolved) {
83
+ return undefined;
84
+ }
85
+ return { reference, reason: "assertion-failed", detail: `expected ${String(expected)} but resolved citation ${resolved}` };
86
+ }
87
+ function resolveArtifactValue(reference, snapshot) {
88
+ const artifact = snapshotEntry(snapshot, reference.path);
89
+ if (artifact === undefined) {
90
+ return fail(reference, "artifact-missing", `no artifact at ${reference.path}`);
91
+ }
92
+ if (artifact.hash !== reference.hash) {
93
+ return fail(reference, "hash-mismatch", `expected ${reference.hash} but the artifact hash is ${artifact.hash}`);
94
+ }
95
+ // A caller supplies the rows of a fixture. A fixture that supplies rows for a figure would resolve a
96
+ // cell that no real artifact holds. Thus the refusal comes before the read of the rows.
97
+ const valueFileType = artifact.fileType;
98
+ if (fileTypeHoldsNoCell(valueFileType)) {
99
+ return fail(reference, "unreadable-artifact", `the ${valueFileType} at ${reference.path} holds no cell to read`);
100
+ }
101
+ // An artifact with no rows is pinned whole, thus it addresses no cell and every locator over it lands
102
+ // out of range.
103
+ const rows = artifact.rows ?? [];
104
+ const locator = reference.locator;
105
+ let selectedRow;
106
+ if (locator.row !== undefined) {
107
+ if (locator.row < 0 || locator.row >= rows.length) {
108
+ return fail(reference, "locator-out-of-range", `row ${locator.row} is outside the ${rows.length} rows`);
109
+ }
110
+ selectedRow = rows[locator.row];
111
+ }
112
+ else if (locator.rowFilter !== undefined) {
113
+ const filter = locator.rowFilter;
114
+ const matches = rows.filter((row) => row[filter.column] === filter.value);
115
+ if (matches.length === 0) {
116
+ return fail(reference, "locator-out-of-range", `no row where ${filter.column} equals ${String(filter.value)}`);
117
+ }
118
+ if (matches.length > 1) {
119
+ return fail(reference, "ambiguous-match", `${matches.length} rows where ${filter.column} equals ${String(filter.value)}`);
120
+ }
121
+ selectedRow = matches[0];
122
+ }
123
+ else {
124
+ // The locator schema forbids a locator with neither selector. The resolver cannot depend on a
125
+ // prior parse, thus it treats a row that no selector addresses as out of range.
126
+ return fail(reference, "locator-out-of-range", "the locator selects no row");
127
+ }
128
+ // A real table holds an empty cell, and a parser gives it back as an absent key, `undefined`, or
129
+ // `null`. None of the three is a value that a scalar reference can bind. An empty string is a value,
130
+ // thus it stays valid. The wider annotation admits the three, because the row type promises a value
131
+ // for each key that it holds.
132
+ const cell = selectedRow[locator.column];
133
+ if (cell === undefined || cell === null) {
134
+ return fail(reference, "locator-out-of-range", `column ${locator.column} holds no value in the selected row`);
135
+ }
136
+ const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, cell);
137
+ return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: cell });
138
+ }
139
+ function resolveArtifactTable(reference, snapshot) {
140
+ const artifact = snapshotEntry(snapshot, reference.path);
141
+ if (artifact === undefined) {
142
+ return fail(reference, "artifact-missing", `no artifact at ${reference.path}`);
143
+ }
144
+ if (artifact.hash !== reference.hash) {
145
+ return fail(reference, "hash-mismatch", `expected ${reference.hash} but the artifact hash is ${artifact.hash}`);
146
+ }
147
+ const tableFileType = artifact.fileType;
148
+ if (fileTypeHoldsNoCell(tableFileType)) {
149
+ return fail(reference, "unreadable-artifact", `the ${tableFileType} at ${reference.path} holds no cell to read`);
150
+ }
151
+ const rows = artifact.rows ?? [];
152
+ const columns = reference.columns;
153
+ if (columns === undefined) {
154
+ return ok({ type: "table", rows });
155
+ }
156
+ // A name that no row holds addresses nothing. Without this check a projection onto an invented column
157
+ // gives an empty cell for each row and still resolves, which is the fabrication that grounding exists
158
+ // to reject.
159
+ const absent = columnsHeldByNoRow(rows, columns);
160
+ if (absent.length > 0) {
161
+ return fail(reference, "locator-out-of-range", `the table at ${reference.path} holds no column ${absent.join(", ")}`);
162
+ }
163
+ // Project each row onto the requested columns. A table tolerates a ragged row, thus a column that a
164
+ // given row lacks is left out of that row and is not a failure.
165
+ const projectedRows = rows.map((row) => {
166
+ const projected = {};
167
+ for (const column of columns) {
168
+ if (column in row) {
169
+ projected[column] = row[column];
170
+ }
171
+ }
172
+ return projected;
173
+ });
174
+ return ok({ type: "table", rows: projectedRows, columns });
175
+ }
176
+ function resolveArtifactFile(reference, snapshot) {
177
+ const artifact = snapshotEntry(snapshot, reference.path);
178
+ if (artifact === undefined) {
179
+ return fail(reference, "artifact-missing", `no artifact at ${reference.path}`);
180
+ }
181
+ if (artifact.hash !== reference.hash) {
182
+ return fail(reference, "hash-mismatch", `expected ${reference.hash} but the artifact hash is ${artifact.hash}`);
183
+ }
184
+ return ok({ type: "file", path: reference.path, hash: artifact.hash });
185
+ }
186
+ async function resolveDerivation(reference, snapshot) {
187
+ const numbers = [];
188
+ for (const input of reference.inputs) {
189
+ const inputResult = await resolve(input, snapshot);
190
+ if (inputResult.isErr()) {
191
+ // Keep the inner reason so a reviewer sees the real cause, for example a missing artifact under
192
+ // the derivation. The derivation itself did not fail on its own terms.
193
+ return fail(reference, inputResult.error.reason, inputResult.error.detail);
194
+ }
195
+ const resolved = inputResult.value;
196
+ const numeric = resolved.type === "scalar" ? asFiniteNumber(resolved.value) : undefined;
197
+ if (numeric === undefined) {
198
+ // The arithmetic needs a finite number. A cell that holds text does not address a usable
199
+ // scalar, thus the closest reason is that the coordinate is out of range.
200
+ return fail(reference, "locator-out-of-range", `input ${describeReference(input)} did not resolve to a finite number`);
201
+ }
202
+ numbers.push(numeric);
203
+ }
204
+ const a = numbers[0];
205
+ const b = numbers[1];
206
+ let result;
207
+ switch (reference.op) {
208
+ case "ratio":
209
+ result = a / b;
210
+ break;
211
+ case "delta":
212
+ result = a - b;
213
+ break;
214
+ case "pctChange":
215
+ // A fraction, not a percent. A change of one half gives 0.5, thus an author asserts 0.5 and a
216
+ // `unit` of `%` only tells a renderer how to show it.
217
+ result = (a - b) / b;
218
+ break;
219
+ }
220
+ if (!Number.isFinite(result)) {
221
+ return fail(reference, "locator-out-of-range", `operation ${reference.op} does not yield a finite value, because the divisor is zero`);
222
+ }
223
+ const valueFailure = checkValueAssertion(reference, reference.assert?.value, reference.assert?.tolerance, result);
224
+ return valueFailure !== undefined ? err(valueFailure) : ok({ type: "scalar", value: result });
225
+ }
226
+ function resolveCitation(reference, snapshot) {
227
+ const key = `${reference.idKind}:${reference.id}`;
228
+ if (snapshot.citations === undefined || !snapshot.citations.includes(key)) {
229
+ return fail(reference, "artifact-missing", `the citation ${key} is not in the pinned evidence`);
230
+ }
231
+ const value = { type: "citation", id: key };
232
+ const citationFailure = checkCitationAssertion(reference, reference.assert?.value, key);
233
+ return citationFailure !== undefined ? err(citationFailure) : ok(value);
234
+ }
235
+ async function resolve(reference, snapshot) {
236
+ switch (reference.kind) {
237
+ case "artifact-value":
238
+ return resolveArtifactValue(reference, snapshot);
239
+ case "artifact-table":
240
+ return resolveArtifactTable(reference, snapshot);
241
+ case "artifact-file":
242
+ return resolveArtifactFile(reference, snapshot);
243
+ case "derivation":
244
+ return resolveDerivation(reference, snapshot);
245
+ case "citation":
246
+ return resolveCitation(reference, snapshot);
247
+ }
248
+ }
249
+ /** Make a resolver that reads only the snapshot. It holds no state, thus one instance serves every call. */
250
+ export function createFixtureResolver() {
251
+ return { resolve };
252
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The mint of a report snapshot, which is the membership boundary of one report session.
3
+ *
4
+ * A report session freezes at one moment, and the analysis continues after that anchor. The snapshot
5
+ * states which artifacts existed at the anchor, and that is a question that no other record answers.
6
+ * Thus a session never cites an artifact that a later run produced.
7
+ *
8
+ * The artifact ledger holds one row for each path, and it keeps no history. Thus the set at a past
9
+ * moment is not recoverable later, and the mint must run at the anchor itself.
10
+ */
11
+ import { type Result } from "neverthrow";
12
+ import type { Querier } from "../state/db.js";
13
+ import type { ReportSnapshot } from "./reference-resolver.js";
14
+ /**
15
+ * The reason that the mint gave no snapshot. The read of the ledger is the one operation that can
16
+ * fail. An analysis with no registered artifact is a normal answer, thus absence is not a member of
17
+ * this set. `cause` carries the underlying fault for a log.
18
+ */
19
+ export type MintSnapshotError = {
20
+ kind: "ledger-read-failed";
21
+ cause: unknown;
22
+ };
23
+ /**
24
+ * Mint the snapshot of one analysis from the artifact ledger.
25
+ *
26
+ * The mint copies no cell and no byte. An entry pins identity alone, thus the snapshot grows with the
27
+ * count of artifacts and never with the size of the data. A read of an artifact belongs to the value
28
+ * tier, which runs one time for each report version.
29
+ */
30
+ export declare function mintReportSnapshot(pool: Querier, analysisId: string): Promise<Result<ReportSnapshot, MintSnapshotError>>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The mint of a report snapshot, which is the membership boundary of one report session.
3
+ *
4
+ * A report session freezes at one moment, and the analysis continues after that anchor. The snapshot
5
+ * states which artifacts existed at the anchor, and that is a question that no other record answers.
6
+ * Thus a session never cites an artifact that a later run produced.
7
+ *
8
+ * The artifact ledger holds one row for each path, and it keeps no history. Thus the set at a past
9
+ * moment is not recoverable later, and the mint must run at the anchor itself.
10
+ */
11
+ import { err, ok } from "neverthrow";
12
+ import { queryAnalysisArtifacts } from "../state/artifacts.js";
13
+ /**
14
+ * Mint the snapshot of one analysis from the artifact ledger.
15
+ *
16
+ * The mint copies no cell and no byte. An entry pins identity alone, thus the snapshot grows with the
17
+ * count of artifacts and never with the size of the data. A read of an artifact belongs to the value
18
+ * tier, which runs one time for each report version.
19
+ */
20
+ export async function mintReportSnapshot(pool, analysisId) {
21
+ let ledgerRows;
22
+ try {
23
+ ledgerRows = await queryAnalysisArtifacts(pool, analysisId);
24
+ }
25
+ catch (cause) {
26
+ // The query speaks the throw protocol of the `pg` driver. This is the thin wrapper that turns
27
+ // that throw into a value, thus each caller above reads a failure as data.
28
+ return err({ kind: "ledger-read-failed", cause });
29
+ }
30
+ // The ledger accepts any path. A null-prototype map keeps a key such as `__proto__` an ordinary
31
+ // entry, thus no path collides with a prototype slot.
32
+ const artifacts = Object.create(null);
33
+ for (const row of ledgerRows) {
34
+ artifacts[row.path] = { hash: row.hash, fileType: row.fileType };
35
+ }
36
+ // The mint fills no citation. No store holds the citation ids of an analysis, and validation
37
+ // matches a citation against the external authorities and never against a minted list. Thus such
38
+ // a list would state nothing that a later read consumes.
39
+ return ok({ artifacts });
40
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The seam that turns a report reference into a concrete value, and the value model that it gives back.
3
+ *
4
+ * A reference is authored belief. A resolver reads the pinned evidence and gives the value that the
5
+ * belief points at. The harness declares the seam here, thus a production realization can read storage,
6
+ * and a fixture realization can read an in-memory snapshot. The two obey the same contract.
7
+ */
8
+ import type { Result } from "neverthrow";
9
+ import type { Reference, UnresolvedReference } from "../contracts/report-reference.js";
10
+ /**
11
+ * One pinned artifact: its content hash, and its rows as plain cells.
12
+ *
13
+ * A cell is a string or a number, because a realization gives back whatever its store holds. A
14
+ * text-backed artifact such as a CSV gives a numeric column as a string. Thus a realization is free to
15
+ * skip a type inference pass, and resolution compares a numeral by its numeric value and never by its
16
+ * JavaScript type.
17
+ *
18
+ * `rows` is optional because an image is pinned by its hash alone and holds no cells. An empty array in
19
+ * that position would say that the artifact holds zero rows, which is a different and false claim. The
20
+ * mint never populates `rows`. `rows` serves the fixture realization only, and that realization resolves
21
+ * from the snapshot itself. A production snapshot pins identity, and a value comes from a read of the
22
+ * artifact.
23
+ *
24
+ * `fileType` states a role, and it does not state a data format. `inferArtifactType` in
25
+ * `src/schemas/artifact-manifest.ts` gives each value that the ledger holds, and the values are
26
+ * `figure`, `script`, `log`, `notebook`, and `output`. Thus the field can refuse a kind of reference, and
27
+ * it can never confirm one. An `output` covers a table and an image alike.
28
+ */
29
+ export interface ArtifactSnapshot {
30
+ hash: string;
31
+ fileType?: string | null;
32
+ rows?: Array<Record<string, string | number>>;
33
+ }
34
+ /**
35
+ * The pinned evidence that a resolver reads. The `artifacts` map is keyed by the analysis-relative path,
36
+ * the same path that an artifact reference names and the same key as `cortex_artifacts.path`. That path
37
+ * is unique across the analysis, thus one map holds the artifacts of every run without a collision.
38
+ * `citations` holds each known external id as an `idKind:id` string, for example `pmid:12345`.
39
+ */
40
+ export interface ReportSnapshot {
41
+ artifacts: Record<string, ArtifactSnapshot>;
42
+ citations?: string[];
43
+ }
44
+ /**
45
+ * The concrete value that a reference resolves to. A scalar comes from one artifact cell or one
46
+ * derivation. A table comes from a whole-table artifact. A file echoes the pin of a whole-file artifact,
47
+ * for example an image. A citation echo confirms that an external id is in the pinned evidence.
48
+ */
49
+ export type ResolvedValue = {
50
+ type: "scalar";
51
+ value: string | number;
52
+ } | {
53
+ type: "table";
54
+ rows: Array<Record<string, string | number>>;
55
+ columns?: string[];
56
+ } | {
57
+ type: "file";
58
+ path: string;
59
+ hash: string;
60
+ } | {
61
+ type: "citation";
62
+ id: string;
63
+ };
64
+ /**
65
+ * The capability seam that resolves one reference against a snapshot.
66
+ *
67
+ * The method is async because a production realization reads storage. A synchronous signature would
68
+ * force a local realization onto every host, thus the seam stays async even when a realization is
69
+ * in-memory.
70
+ *
71
+ * The `Ok` channel carries the resolved value. The `Err` channel carries the `UnresolvedReference`,
72
+ * which names the reason and the detail that a reference did not bind. The validator collects each such
73
+ * `Err` into its report, thus a caller reads the reason as data and resolution never throws.
74
+ */
75
+ export interface ReferenceResolver {
76
+ resolve(reference: Reference, snapshot: ReportSnapshot): Promise<Result<ResolvedValue, UnresolvedReference>>;
77
+ }
78
+ /**
79
+ * Give back each name in `columns` that no row holds, in the order of `columns`.
80
+ *
81
+ * A table tolerates a ragged row, thus a column that only some rows hold is present. A column that no
82
+ * row holds is a name that addresses nothing, and it is the difference between a sparse table and a
83
+ * column that does not exist.
84
+ *
85
+ * A table with no rows gives back nothing. There is no evidence to contradict any name, and an empty
86
+ * result table is a real scientific outcome that must not fail for the shape of its emptiness.
87
+ */
88
+ export declare function columnsHeldByNoRow(rows: Array<Record<string, string | number>>, columns: readonly string[]): string[];
89
+ /**
90
+ * Give back `true` for a file type that holds no cell for a reference to address.
91
+ *
92
+ * The file type states a role, and it does not state a data format. Thus the rule runs one way only. It
93
+ * refuses a kind, and it never confirms one. An `output` gives back `false`, because it covers a table and
94
+ * an image alike. Only a read of the artifact settles which one it is. An absent file type gives back
95
+ * `false` for the same reason.
96
+ *
97
+ * The structural tier and a resolver both read this one predicate. Thus the two tiers can never disagree
98
+ * about one reference.
99
+ */
100
+ export declare function fileTypeHoldsNoCell(fileType: string | null | undefined): boolean;
101
+ /**
102
+ * Give back the snapshot entry at `path`, or `undefined` when the map holds no such own key.
103
+ *
104
+ * An agent authors a report reference, thus the path is untrusted text. The `artifacts` map can be a
105
+ * plain object: a fixture literal, or a `JSON.parse` of a stored snapshot. A bracket lookup on a plain
106
+ * object with a path such as `constructor` finds an inherited member. Then the refusal reads
107
+ * `hash-mismatch`, but the true answer is `artifact-missing`. The `Object.hasOwn` guard admits an own
108
+ * key only, thus an inherited member reads as absent.
109
+ *
110
+ * The structural tier and the fixture resolver both read this one lookup. Thus the two tiers can never
111
+ * disagree about membership.
112
+ */
113
+ export declare function snapshotEntry(snapshot: ReportSnapshot, path: string): ArtifactSnapshot | undefined;