@inflexa-ai/harness 0.20.0 → 0.21.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 (133) hide show
  1. package/dist/agents/conversation-agent.d.ts +26 -14
  2. package/dist/agents/conversation-agent.js +12 -23
  3. package/dist/agents/report-session-agent.d.ts +31 -5
  4. package/dist/agents/report-session-agent.js +31 -6
  5. package/dist/app/chat-turn.js +3 -1
  6. package/dist/app/message-assembly.d.ts +15 -1
  7. package/dist/app/message-assembly.js +22 -8
  8. package/dist/app/report-session-runtime.d.ts +21 -4
  9. package/dist/app/report-session-runtime.js +53 -6
  10. package/dist/app/spawn-report-session.d.ts +174 -17
  11. package/dist/app/spawn-report-session.js +233 -21
  12. package/dist/contracts/chat-events.d.ts +7 -2
  13. package/dist/contracts/message.d.ts +10 -0
  14. package/dist/contracts/report-blocks.d.ts +743 -19
  15. package/dist/contracts/report-blocks.js +186 -11
  16. package/dist/contracts/report-reference.d.ts +88 -0
  17. package/dist/contracts/report-reference.js +48 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.js +20 -0
  20. package/dist/lib/chrome.d.ts +26 -6
  21. package/dist/lib/chrome.js +99 -32
  22. package/dist/lib/eyes.d.ts +57 -0
  23. package/dist/lib/eyes.js +45 -0
  24. package/dist/lib/page-capture.d.ts +14 -0
  25. package/dist/lib/page-capture.js +56 -2
  26. package/dist/loop/run-agent.js +58 -23
  27. package/dist/loop/tool-detail.d.ts +19 -2
  28. package/dist/loop/tool-detail.js +35 -2
  29. package/dist/memory/content-to-cortex.d.ts +3 -2
  30. package/dist/memory/content-to-cortex.js +21 -12
  31. package/dist/memory/conversation-display-replay.js +14 -7
  32. package/dist/memory/thread-history.d.ts +60 -3
  33. package/dist/memory/thread-history.js +55 -16
  34. package/dist/memory/thread-store.d.ts +11 -1
  35. package/dist/memory/thread-store.js +12 -2
  36. package/dist/prompts/conversation.d.ts +1 -1
  37. package/dist/prompts/conversation.js +23 -6
  38. package/dist/prompts/report-session.d.ts +1 -1
  39. package/dist/prompts/report-session.js +183 -9
  40. package/dist/report-model/authoring-grammar.d.ts +463 -0
  41. package/dist/report-model/authoring-grammar.js +77 -0
  42. package/dist/report-model/block-walk.d.ts +57 -14
  43. package/dist/report-model/block-walk.js +116 -11
  44. package/dist/report-model/draft-finish.d.ts +22 -4
  45. package/dist/report-model/draft-finish.js +39 -7
  46. package/dist/report-model/draft-operations.d.ts +3 -0
  47. package/dist/report-model/draft-operations.js +19 -5
  48. package/dist/report-model/draft-read.d.ts +8 -5
  49. package/dist/report-model/draft-read.js +21 -5
  50. package/dist/report-model/fixture-resolver.js +15 -4
  51. package/dist/report-model/pin-snapshot.d.ts +24 -6
  52. package/dist/report-model/pin-snapshot.js +204 -6
  53. package/dist/report-model/production-resolver.js +15 -4
  54. package/dist/report-model/reference-resolver.d.ts +50 -1
  55. package/dist/report-model/reference-resolver.js +94 -1
  56. package/dist/report-model/reference-stamp.d.ts +45 -0
  57. package/dist/report-model/reference-stamp.js +173 -0
  58. package/dist/report-model/structural-validation.d.ts +4 -1
  59. package/dist/report-model/structural-validation.js +48 -3
  60. package/dist/report-model/validate.d.ts +1 -1
  61. package/dist/report-model/validate.js +1 -1
  62. package/dist/report-render/asset-lookup.d.ts +19 -0
  63. package/dist/report-render/asset-lookup.js +37 -0
  64. package/dist/report-render/assets.d.ts +45 -1
  65. package/dist/report-render/assets.js +65 -6
  66. package/dist/report-render/chart-presets.d.ts +102 -0
  67. package/dist/report-render/chart-presets.js +205 -0
  68. package/dist/report-render/chart.d.ts +112 -10
  69. package/dist/report-render/chart.js +1164 -41
  70. package/dist/report-render/design.d.ts +99 -0
  71. package/dist/report-render/design.js +289 -107
  72. package/dist/report-render/number-format.d.ts +117 -0
  73. package/dist/report-render/number-format.js +479 -0
  74. package/dist/report-render/page.d.ts +121 -5
  75. package/dist/report-render/page.js +689 -7
  76. package/dist/report-render/references.d.ts +70 -16
  77. package/dist/report-render/references.js +54 -22
  78. package/dist/report-render/render.d.ts +23 -5
  79. package/dist/report-render/render.js +81 -22
  80. package/dist/report-render/table-data.d.ts +92 -0
  81. package/dist/report-render/table-data.js +129 -0
  82. package/dist/report-render/types.d.ts +15 -0
  83. package/dist/report-render/value-bridge.js +6 -2
  84. package/dist/report-render/views/chart-view.d.ts +12 -5
  85. package/dist/report-render/views/chart-view.js +14 -8
  86. package/dist/report-render/views/page-view.d.ts +36 -15
  87. package/dist/report-render/views/page-view.js +45 -23
  88. package/dist/report-render/views/prose.d.ts +12 -2
  89. package/dist/report-render/views/prose.js +29 -7
  90. package/dist/report-render/views/references-view.d.ts +22 -8
  91. package/dist/report-render/views/references-view.js +101 -13
  92. package/dist/report-render/views/values.d.ts +128 -13
  93. package/dist/report-render/views/values.js +199 -18
  94. package/dist/runtime/assemble.d.ts +39 -5
  95. package/dist/runtime/assemble.js +47 -6
  96. package/dist/sandbox/client.d.ts +10 -1
  97. package/dist/sandbox/create-sandbox.d.ts +9 -4
  98. package/dist/sandbox/create-sandbox.js +22 -16
  99. package/dist/sandbox/docker-client.js +10 -7
  100. package/dist/sandbox/mount-plan.d.ts +45 -7
  101. package/dist/sandbox/mount-plan.js +43 -5
  102. package/dist/sandbox/types.d.ts +6 -0
  103. package/dist/state/init.js +38 -9
  104. package/dist/state/report-session-state.d.ts +42 -1
  105. package/dist/state/report-session-state.js +70 -2
  106. package/dist/state/report-versions.d.ts +22 -17
  107. package/dist/state/report-versions.js +48 -35
  108. package/dist/state/snapshot-parse.js +16 -1
  109. package/dist/tools/define-tool.d.ts +32 -0
  110. package/dist/tools/define-tool.js +4 -0
  111. package/dist/tools/detail-resolver.d.ts +7 -2
  112. package/dist/tools/detail-resolver.js +7 -2
  113. package/dist/tools/iterate-report.d.ts +1 -1
  114. package/dist/tools/report/preview-snapshot.js +1 -0
  115. package/dist/tools/report-authoring/authoring-tools.d.ts +15 -6
  116. package/dist/tools/report-authoring/authoring-tools.js +99 -18
  117. package/dist/tools/report-session/derive-table.d.ts +162 -0
  118. package/dist/tools/report-session/derive-table.js +459 -0
  119. package/dist/tools/report-session/examine-page.d.ts +48 -18
  120. package/dist/tools/report-session/examine-page.js +204 -24
  121. package/dist/tools/report-session/index.d.ts +2 -0
  122. package/dist/tools/report-session/index.js +2 -0
  123. package/dist/tools/report-session/list-artifacts.d.ts +97 -0
  124. package/dist/tools/report-session/list-artifacts.js +197 -0
  125. package/dist/tools/report-session/preview-report.d.ts +6 -0
  126. package/dist/tools/report-session/preview-report.js +180 -28
  127. package/dist/tools/report-session/record-version.d.ts +22 -10
  128. package/dist/tools/report-session/record-version.js +99 -14
  129. package/dist/tools/start-report-session.d.ts +115 -0
  130. package/dist/tools/start-report-session.js +179 -0
  131. package/dist/workspace/paths.d.ts +50 -0
  132. package/dist/workspace/paths.js +63 -0
  133. package/package.json +2 -1
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * The one walk over a block tree.
3
3
  *
4
- * A block kind decides where a reference sits, which prose carries a free numeral, and whether the block
5
- * holds children. Three consumers need that same knowledge: the mechanical validator, the draft finish,
6
- * and the draft operations. A switch for each consumer is a switch that a ninth block kind can miss in
7
- * silence, because each arm returns and none of them fails to build. Thus the walk lives here one time.
4
+ * A block kind decides where a reference sits, which text carries a free numeral or a drifted exponent
5
+ * form, and whether the block holds children. Three consumers need that same knowledge: the mechanical
6
+ * validator, the draft finish, and the draft operations. A switch for each consumer is a switch that a
7
+ * ninth block kind can miss in silence, because each arm returns and none of them fails to build. Thus the
8
+ * walk lives here one time.
8
9
  *
9
10
  * The walk reads a finished `Block` tree and a relaxed `DraftBlock` tree alike. The two differ only in the
10
11
  * section rules, and the section arm reads the children of either one.
@@ -12,6 +13,8 @@
12
13
  * The walk resolves nothing and it opens no file. It reports what the tree holds, and a caller decides
13
14
  * which part it needs.
14
15
  */
16
+ import { channelColumn } from "../contracts/report-blocks.js";
17
+ import { holdsADriftedExponent } from "../report-render/number-format.js";
15
18
  /**
16
19
  * A free-standing token that looks like a number, with an optional decimal part and an optional percent
17
20
  * sign.
@@ -25,6 +28,13 @@
25
28
  * the report.
26
29
  */
27
30
  const NUMERAL_PATTERN = /(?<![A-Za-z0-9])-?\d+(?:\.\d+)?%?/g;
31
+ /**
32
+ * A free-standing token that states a number in an exponent form, for example `4.3e-05`.
33
+ *
34
+ * The left boundary is the one of the numeral scan, thus a token inside a name never matches. The exponent
35
+ * itself carries an optional sign, because a written form gives one and a shown form never does.
36
+ */
37
+ const EXPONENT_PATTERN = /(?<![A-Za-z0-9])-?\d+(?:\.\d+)?[eE][+-]?\d+/g;
28
38
  /** Warn for each free-standing numeral that the prose of one block carries. */
29
39
  export function numeralWarnings(blockId, prose) {
30
40
  const warnings = [];
@@ -33,6 +43,73 @@ export function numeralWarnings(blockId, prose) {
33
43
  }
34
44
  return warnings;
35
45
  }
46
+ /**
47
+ * Warn for each exponent form of one block that the page never prints.
48
+ *
49
+ * The page owns the notation of a value. Thus a sentence that writes a second notation of the same number
50
+ * disagrees with the card beside it. The renderer answers which forms it prints, thus this scan states no
51
+ * rule of its own.
52
+ *
53
+ * The check is advisory, exactly as the numeral scan is. A prose exponent can be a quoted threshold that no
54
+ * cell carries, thus a hit warns and it never fails the report.
55
+ */
56
+ export function exponentWarnings(blockId, prose) {
57
+ const warnings = [];
58
+ for (const match of prose.matchAll(EXPONENT_PATTERN)) {
59
+ if (holdsADriftedExponent(match[0])) {
60
+ warnings.push({ blockId, kind: "exponent-form", detail: match[0] });
61
+ }
62
+ }
63
+ return warnings;
64
+ }
65
+ /** Each advisory warning that the prose of one block carries. */
66
+ function proseWarnings(blockId, prose) {
67
+ return [...numeralWarnings(blockId, prose), ...exponentWarnings(blockId, prose)];
68
+ }
69
+ /**
70
+ * Each column that the grammar of one chart names.
71
+ *
72
+ * The quick path names its four channels and its label. A composition names the channels of each series,
73
+ * the lower bound of a band, the label of each series, and the column of each rank rule. A transform rides
74
+ * beside its column, thus a transformed channel names the same column as a plain one.
75
+ *
76
+ * A name comes back one time, in the order that the grammar states it. Thus a refusal names each absent
77
+ * column one time.
78
+ */
79
+ function chartColumns(block) {
80
+ const columns = [];
81
+ const add = (column) => {
82
+ if (column !== undefined && !columns.includes(column))
83
+ columns.push(column);
84
+ };
85
+ const addChannel = (channel) => {
86
+ if (channel !== undefined)
87
+ add(channelColumn(channel));
88
+ };
89
+ const encoding = block.encoding;
90
+ if (encoding !== undefined) {
91
+ addChannel(encoding.x);
92
+ addChannel(encoding.y);
93
+ addChannel(encoding.group);
94
+ addChannel(encoding.value);
95
+ add(encoding.label);
96
+ }
97
+ const composition = block.composition;
98
+ if (composition !== undefined) {
99
+ for (const series of composition.series) {
100
+ addChannel(series.encoding.x);
101
+ addChannel(series.encoding.y);
102
+ addChannel(series.encoding.y0);
103
+ addChannel(series.encoding.group);
104
+ add(series.encoding.label);
105
+ }
106
+ for (const annotation of composition.annotations ?? []) {
107
+ if (annotation.kind === "point-labels")
108
+ add(annotation.column);
109
+ }
110
+ }
111
+ return columns;
112
+ }
36
113
  /**
37
114
  * Walk a block tree, and report each reference, each repeated id, and each free numeral.
38
115
  *
@@ -58,10 +135,15 @@ export function walkBlocks(blocks) {
58
135
  }
59
136
  return;
60
137
  case "text":
61
- warnings.push(...numeralWarnings(block.id, block.content.prose));
138
+ warnings.push(...proseWarnings(block.id, block.content.prose));
139
+ // An item of a list states a point, the same as a sentence of the prose. Thus a free
140
+ // figure inside an item carries the same honesty concern, and the walk reads both.
141
+ for (const item of block.content.list?.items ?? []) {
142
+ warnings.push(...proseWarnings(block.id, item));
143
+ }
62
144
  return;
63
145
  case "claim":
64
- warnings.push(...numeralWarnings(block.id, block.content.prose));
146
+ warnings.push(...proseWarnings(block.id, block.content.prose));
65
147
  for (const binding of block.bindings) {
66
148
  references.push({ blockId: block.id, reference: binding });
67
149
  }
@@ -69,12 +151,9 @@ export function walkBlocks(blocks) {
69
151
  case "metric":
70
152
  references.push({ blockId: block.id, reference: block.value });
71
153
  return;
72
- case "chart": {
73
- const encoding = block.encoding;
74
- const encodingColumns = [encoding.x, encoding.y, encoding.group, encoding.value].filter((column) => column !== undefined);
75
- references.push({ blockId: block.id, reference: block.binding, encodingColumns });
154
+ case "chart":
155
+ references.push({ blockId: block.id, reference: block.binding, encodingColumns: chartColumns(block) });
76
156
  return;
77
- }
78
157
  case "table":
79
158
  case "figure":
80
159
  case "citation":
@@ -87,3 +166,29 @@ export function walkBlocks(blocks) {
87
166
  }
88
167
  return { references, repeatedIds: [...repeated].sort(), warnings };
89
168
  }
169
+ /**
170
+ * Each artifact path that a set of collected references names.
171
+ *
172
+ * An artifact binding names one path. A derivation reference computes over two inputs, thus it names the
173
+ * path of each input and a table that feeds a derived scalar counts as named. A citation names no path.
174
+ *
175
+ * The set answers one question: which files does this document use. The finish reads it against the
176
+ * derivations of the session, and the record reads it again over the recorded document.
177
+ */
178
+ export function referencedPaths(references) {
179
+ const paths = new Set();
180
+ for (const entry of references) {
181
+ const reference = entry.reference;
182
+ if (reference.kind === "citation") {
183
+ continue;
184
+ }
185
+ if (reference.kind === "derivation") {
186
+ for (const input of reference.inputs) {
187
+ paths.add(input.path);
188
+ }
189
+ continue;
190
+ }
191
+ paths.add(reference.path);
192
+ }
193
+ return paths;
194
+ }
@@ -5,14 +5,23 @@
5
5
  * draft one time: the full document schema, the unique ids, and the structural tier over every reference.
6
6
  *
7
7
  * The finish opens no file, and it reads the snapshot alone. Thus it runs the structural tier only, and
8
- * the value tier stays in the gate of the report pipeline. The chart encoding-column match needs the rows
9
- * behind a binding, thus it stays with the value tier.
8
+ * the value tier stays in the gate of the report pipeline. The structural tier matches the columns of a
9
+ * chart wherever the snapshot can answer, and the value tier matches them again over the rows that it
10
+ * reads.
10
11
  *
11
12
  * The free-numeral scan needs no file, thus the finish runs it and reports each hit as a warning. A
12
13
  * warning never makes a draft invalid, and it rides beside the outcome in either case. Without the
13
14
  * channel a caller could not read the one signal that catches a figure typed into prose with no metric
14
15
  * block behind it.
15
16
  *
17
+ * The exponent-form scan rides the same channel. A sentence can state a number in a notation that the page
18
+ * never prints, and such a sentence disagrees with the card beside it. Thus the prose notation and the page
19
+ * notation cannot drift in silence.
20
+ *
21
+ * The unused-derivation scan rides the same channel. The records and the draft both sit in memory, thus the
22
+ * scan opens no file either. A derivation whose output path no binding names warns, and the derived bytes
23
+ * stay until the record prunes them.
24
+ *
16
25
  * The result is plain data. A gap is an expected outcome, not an error, thus the finish never throws. On
17
26
  * a pass the finish gives the valid document, and it does not change the draft.
18
27
  */
@@ -53,8 +62,17 @@ export type FinishResult = {
53
62
  gaps: FinishGap[];
54
63
  warnings: ReportWarning[];
55
64
  };
65
+ /**
66
+ * One derivation of the session, as the finish reads it. The finish needs the output path alone, because
67
+ * the used set is the paths that the bindings name.
68
+ */
69
+ export interface SessionDerivation {
70
+ readonly outputPath: string;
71
+ }
56
72
  /**
57
73
  * Finish a draft. The finish validates the schema, the unique ids, and the structural resolution of each
58
- * reference, and it reports each gap as data. It warns about each free numeral in prose.
74
+ * reference, and it reports each gap as data. It warns about each free numeral in prose, and about each
75
+ * exponent form that the page never prints. It warns about each derivation of the session that no binding
76
+ * names.
59
77
  */
60
- export declare function finishDraft(draft: DraftDocument, snapshot: ReportSnapshot): FinishResult;
78
+ export declare function finishDraft(draft: DraftDocument, snapshot: ReportSnapshot, derivations?: readonly SessionDerivation[]): FinishResult;
@@ -5,25 +5,56 @@
5
5
  * draft one time: the full document schema, the unique ids, and the structural tier over every reference.
6
6
  *
7
7
  * The finish opens no file, and it reads the snapshot alone. Thus it runs the structural tier only, and
8
- * the value tier stays in the gate of the report pipeline. The chart encoding-column match needs the rows
9
- * behind a binding, thus it stays with the value tier.
8
+ * the value tier stays in the gate of the report pipeline. The structural tier matches the columns of a
9
+ * chart wherever the snapshot can answer, and the value tier matches them again over the rows that it
10
+ * reads.
10
11
  *
11
12
  * The free-numeral scan needs no file, thus the finish runs it and reports each hit as a warning. A
12
13
  * warning never makes a draft invalid, and it rides beside the outcome in either case. Without the
13
14
  * channel a caller could not read the one signal that catches a figure typed into prose with no metric
14
15
  * block behind it.
15
16
  *
17
+ * The exponent-form scan rides the same channel. A sentence can state a number in a notation that the page
18
+ * never prints, and such a sentence disagrees with the card beside it. Thus the prose notation and the page
19
+ * notation cannot drift in silence.
20
+ *
21
+ * The unused-derivation scan rides the same channel. The records and the draft both sit in memory, thus the
22
+ * scan opens no file either. A derivation whose output path no binding names warns, and the derived bytes
23
+ * stay until the record prunes them.
24
+ *
16
25
  * The result is plain data. A gap is an expected outcome, not an error, thus the finish never throws. On
17
26
  * a pass the finish gives the valid document, and it does not change the draft.
18
27
  */
19
28
  import { ReportDocumentSchema } from "../contracts/report-blocks.js";
20
- import { walkBlocks } from "./block-walk.js";
29
+ import { referencedPaths, walkBlocks } from "./block-walk.js";
21
30
  import { validateReferenceStructure } from "./structural-validation.js";
31
+ /**
32
+ * Warn for each derivation that no binding of the draft names.
33
+ *
34
+ * A derivation is used when a binding names its output path. Thus the scan is a set difference over the
35
+ * records and the collected references, and it reads no file. A warning names the output path, and it
36
+ * decides no outcome.
37
+ */
38
+ function unusedDerivationWarnings(derivations, references) {
39
+ if (derivations.length === 0) {
40
+ return [];
41
+ }
42
+ const named = referencedPaths(references);
43
+ const warnings = [];
44
+ for (const record of derivations) {
45
+ if (!named.has(record.outputPath)) {
46
+ warnings.push({ kind: "unused-derivation", detail: record.outputPath });
47
+ }
48
+ }
49
+ return warnings;
50
+ }
22
51
  /**
23
52
  * Finish a draft. The finish validates the schema, the unique ids, and the structural resolution of each
24
- * reference, and it reports each gap as data. It warns about each free numeral in prose.
53
+ * reference, and it reports each gap as data. It warns about each free numeral in prose, and about each
54
+ * exponent form that the page never prints. It warns about each derivation of the session that no binding
55
+ * names.
25
56
  */
26
- export function finishDraft(draft, snapshot) {
57
+ export function finishDraft(draft, snapshot, derivations = []) {
27
58
  const gaps = [];
28
59
  const parsed = ReportDocumentSchema.safeParse(draft);
29
60
  if (!parsed.success) {
@@ -37,12 +68,13 @@ export function finishDraft(draft, snapshot) {
37
68
  }
38
69
  // The walk reads the draft tree, not the parse result. The draft is already typed, thus the walk stays
39
70
  // well-defined even when the schema parse fails, and a caller still gets the id and reference gaps.
40
- const { references, repeatedIds, warnings } = walkBlocks(draft.sections);
71
+ const { references, repeatedIds, warnings: proseWarnings } = walkBlocks(draft.sections);
72
+ const warnings = [...proseWarnings, ...unusedDerivationWarnings(derivations, references)];
41
73
  for (const id of repeatedIds) {
42
74
  gaps.push({ kind: "duplicate-id", id });
43
75
  }
44
76
  for (const entry of references) {
45
- const result = validateReferenceStructure(entry.reference, snapshot);
77
+ const result = validateReferenceStructure(entry.reference, snapshot, entry.encodingColumns);
46
78
  if (result.isErr()) {
47
79
  gaps.push({ kind: "unresolved-reference", blockId: entry.blockId, failure: result.error });
48
80
  }
@@ -88,6 +88,9 @@ export declare function setTitle(draft: DraftDocument, title: string): DraftDocu
88
88
  *
89
89
  * The root admits a section only, thus an atom at the root refuses. The validation covers the payload and
90
90
  * each of its descendants.
91
+ *
92
+ * The stamp runs before the parse. Thus a reference that names a path of the snapshot and no hash carries
93
+ * the hash of that path when the grammar reads it.
91
94
  */
92
95
  export declare function addBlock(draft: DraftDocument, operation: AddOperation, snapshot: ReportSnapshot): Result<DraftDocument, DraftRefusal>;
93
96
  /**
@@ -18,6 +18,7 @@
18
18
  import { err, ok } from "neverthrow";
19
19
  import { walkBlocks } from "./block-walk.js";
20
20
  import { DraftBlockSchema } from "./draft.js";
21
+ import { stampReferenceHashes } from "./reference-stamp.js";
21
22
  import { validateReferenceStructure } from "./structural-validation.js";
22
23
  /**
23
24
  * Set the title of the document.
@@ -33,9 +34,16 @@ export function setTitle(draft, title) {
33
34
  *
34
35
  * The root admits a section only, thus an atom at the root refuses. The validation covers the payload and
35
36
  * each of its descendants.
37
+ *
38
+ * The stamp runs before the parse. Thus a reference that names a path of the snapshot and no hash carries
39
+ * the hash of that path when the grammar reads it.
36
40
  */
37
41
  export function addBlock(draft, operation, snapshot) {
38
- const parsed = DraftBlockSchema.safeParse(operation.block);
42
+ const stamped = stampReferenceHashes(operation.block, snapshot);
43
+ if (stamped.isErr()) {
44
+ return err(stamped.error);
45
+ }
46
+ const parsed = DraftBlockSchema.safeParse(stamped.value);
39
47
  if (!parsed.success) {
40
48
  return err(malformed(parsed.error));
41
49
  }
@@ -75,10 +83,16 @@ export function changeBlock(draft, operation, snapshot) {
75
83
  if (payload === null || typeof payload !== "object") {
76
84
  return err({ reason: "malformed-block", detail: "the payload is not an object" });
77
85
  }
78
- // The stamp sets the target id on the payload before the parse. Thus an id mismatch is
86
+ // The id stamp sets the target id on the payload before the parse. Thus an id mismatch is
79
87
  // unrepresentable, and a replacement always keeps the id of its target.
80
- const stamped = { ...payload, id: operation.targetId };
81
- const parsed = DraftBlockSchema.safeParse(stamped);
88
+ const withId = { ...payload, id: operation.targetId };
89
+ // The hash stamp then fills each absent reference hash from the snapshot, thus the grammar reads a
90
+ // complete pin. The two stamps are independent, because one reads the block and one reads a reference.
91
+ const hashed = stampReferenceHashes(withId, snapshot);
92
+ if (hashed.isErr()) {
93
+ return err(hashed.error);
94
+ }
95
+ const parsed = DraftBlockSchema.safeParse(hashed.value);
82
96
  if (!parsed.success) {
83
97
  return err(malformed(parsed.error));
84
98
  }
@@ -203,7 +217,7 @@ function commit(candidate, added, snapshot) {
203
217
  }
204
218
  const unresolved = [];
205
219
  for (const entry of walkBlocks([added]).references) {
206
- validateReferenceStructure(entry.reference, snapshot).match(() => { }, (failure) => {
220
+ validateReferenceStructure(entry.reference, snapshot, entry.encodingColumns).match(() => { }, (failure) => {
207
221
  unresolved.push(failure);
208
222
  });
209
223
  }
@@ -9,8 +9,11 @@
9
9
  * section gives its own fields and the ids of its children, and never the subtree. The outline already
10
10
  * names every descendant, thus a subtree here would return the whole tree that the outline exists to keep
11
11
  * out.
12
+ *
13
+ * A read gives each binding with no hash. The pinned snapshot owns that value, thus an echoed hash would
14
+ * give the agent a value to mistype.
12
15
  */
13
- import type { AtomBlock } from "../contracts/report-blocks.js";
16
+ import type { AuthoringAtomBlock } from "./authoring-grammar.js";
14
17
  import type { DraftBlock, DraftDocument } from "./draft.js";
15
18
  /**
16
19
  * One outline entry. It carries the id, the kind, the nesting depth, and a short label. A top-level
@@ -35,8 +38,8 @@ export interface ShallowSection {
35
38
  title: string;
36
39
  childIds: string[];
37
40
  }
38
- /** One block as the read surface gives it. An atom reads in full, and a section reads shallow. */
39
- export type ReadableBlock = AtomBlock | ShallowSection;
41
+ /** One block as the read surface gives it. An atom reads in full and hash-free, and a section reads shallow. */
42
+ export type ReadableBlock = AuthoringAtomBlock | ShallowSection;
40
43
  /**
41
44
  * Build the outline of a draft. The walk is pre-order, thus the entries follow the document order. A
42
45
  * section comes before its children, and a child sits one depth deeper than its parent.
@@ -53,7 +56,7 @@ export declare function buildOutline(draft: DraftDocument): OutlineEntry[];
53
56
  */
54
57
  export declare function childOutline(blocks: readonly DraftBlock[], depth: number): OutlineEntry[];
55
58
  /**
56
- * Read one block by its id. An atom gives the block as it is, with its bindings. A section gives its own
57
- * fields and the id of each child. A block that no id holds gives `undefined`.
59
+ * Read one block by its id. An atom gives the block with its bindings, and each binding gives no hash. A
60
+ * section gives its own fields and the id of each child. A block that no id holds gives `undefined`.
58
61
  */
59
62
  export declare function readBlock(draft: DraftDocument, id: string): ReadableBlock | undefined;
@@ -9,8 +9,12 @@
9
9
  * section gives its own fields and the ids of its children, and never the subtree. The outline already
10
10
  * names every descendant, thus a subtree here would return the whole tree that the outline exists to keep
11
11
  * out.
12
+ *
13
+ * A read gives each binding with no hash. The pinned snapshot owns that value, thus an echoed hash would
14
+ * give the agent a value to mistype.
12
15
  */
13
16
  import { locate } from "./draft-operations.js";
17
+ import { stripReferenceHashes } from "./reference-stamp.js";
14
18
  /** The label keeps a prose head of at most this many code points. A clipped label ends with the marker. */
15
19
  const PROSE_CLIP = 80;
16
20
  /** The marker that a clipped label ends with. It counts toward `PROSE_CLIP`. */
@@ -40,6 +44,9 @@ function clip(text) {
40
44
  * a note. A text and a claim carry prose. Every one of them clips: a caption, a note, and a title are free
41
45
  * prose too, thus an unclipped arm would put a paragraph into each outline that names the block.
42
46
  *
47
+ * A text block carries its enumeration in the list, and the lead sentences above it can be empty. Such a
48
+ * block gives the first item, thus the outline names it and the agent reads which block it is.
49
+ *
43
50
  * A block with an absent optional field gives an empty label.
44
51
  */
45
52
  function labelOf(block) {
@@ -56,8 +63,15 @@ function labelOf(block) {
56
63
  return clip(block.caption ?? "");
57
64
  case "citation":
58
65
  return clip(block.note ?? "");
59
- case "text":
60
- return clip(block.content.prose);
66
+ case "text": {
67
+ const prose = block.content.prose;
68
+ if (prose.trim().length > 0) {
69
+ return clip(prose);
70
+ }
71
+ // An item passes the grammar with one space in it, thus the first item can name nothing. The
72
+ // walk takes the first item that holds text, and a list of blank items gives an empty label.
73
+ return clip(block.content.list?.items.find((item) => item.trim().length > 0) ?? "");
74
+ }
61
75
  case "claim":
62
76
  return clip(block.content.prose);
63
77
  }
@@ -94,8 +108,8 @@ export function childOutline(blocks, depth) {
94
108
  return blocks.map((block) => ({ id: block.id, kind: block.kind, depth, label: labelOf(block) }));
95
109
  }
96
110
  /**
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`.
111
+ * Read one block by its id. An atom gives the block with its bindings, and each binding gives no hash. A
112
+ * section gives its own fields and the id of each child. A block that no id holds gives `undefined`.
99
113
  */
100
114
  export function readBlock(draft, id) {
101
115
  const found = locate(draft, id)?.block;
@@ -105,5 +119,7 @@ export function readBlock(draft, id) {
105
119
  if (found.kind === "section") {
106
120
  return { kind: "section", id: found.id, title: found.title, childIds: found.blocks.map((child) => child.id) };
107
121
  }
108
- return found;
122
+ // The stored atom and the read atom differ in the stamped hash alone, and the walk removes that field.
123
+ // Thus the assertion states what the walk did.
124
+ return stripReferenceHashes(found);
109
125
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { err, ok } from "neverthrow";
7
7
  import { asFiniteNumber, cellMatchesFilterValue, checkCitationAssertion, checkValueAssertion } from "./assert-rules.js";
8
- import { columnsHeldByNoRow, fileTypeHoldsNoCell, snapshotEntry, } from "./reference-resolver.js";
8
+ import { applyRowBound, columnsHeldByNoRow, fileTypeHoldsNoCell, snapshotEntry, } from "./reference-resolver.js";
9
9
  /** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
10
10
  function fail(reference, reason, detail) {
11
11
  const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
@@ -79,10 +79,21 @@ function resolveArtifactTable(reference, snapshot) {
79
79
  if (fileTypeHoldsNoCell(tableFileType)) {
80
80
  return fail(reference, "unreadable-artifact", `the ${tableFileType} at ${reference.path} holds no cell to read`);
81
81
  }
82
- const rows = artifact.rows ?? [];
82
+ // The bound ranks the rows as the artifact holds them, thus a bound over a column that the subset
83
+ // below leaves out still ranks them. An unknown bound column addresses nothing, exactly as an unknown
84
+ // subset column does.
85
+ const bound = reference.rowBound;
86
+ const allRows = artifact.rows ?? [];
87
+ if (bound !== undefined && columnsHeldByNoRow(allRows, [bound.column]).length > 0) {
88
+ return fail(reference, "locator-out-of-range", `the row bound names column ${bound.column}, which the table at ${reference.path} does not hold`);
89
+ }
90
+ const rows = bound === undefined ? allRows : applyRowBound(allRows, bound);
91
+ // The page states the shown count against the count of the artifact. This read is the one place that
92
+ // holds both, thus a bounded table carries the pre-bound total out with its rows.
93
+ const total = bound === undefined ? {} : { total: allRows.length };
83
94
  const columns = reference.columns;
84
95
  if (columns === undefined) {
85
- return ok({ type: "table", rows });
96
+ return ok({ type: "table", rows, ...total });
86
97
  }
87
98
  // A name that no row holds addresses nothing. Without this check a projection onto an invented column
88
99
  // gives an empty cell for each row and still resolves, which is the fabrication that grounding exists
@@ -102,7 +113,7 @@ function resolveArtifactTable(reference, snapshot) {
102
113
  }
103
114
  return projected;
104
115
  });
105
- return ok({ type: "table", rows: projectedRows, columns });
116
+ return ok({ type: "table", rows: projectedRows, columns, ...total });
106
117
  }
107
118
  function resolveArtifactFile(reference, snapshot) {
108
119
  const artifact = snapshotEntry(snapshot, reference.path);
@@ -7,24 +7,42 @@
7
7
  *
8
8
  * The artifact ledger holds one row for each path, and it keeps no history. Thus the set at a past
9
9
  * moment is not recoverable later, and the pin must run at the anchor itself.
10
+ *
11
+ * The snapshot carries the citation evidence beside the artifact map. The pinned set is the literature
12
+ * that the synthesis engaged, and two fields of a run synthesis carry it: the key references and the
13
+ * references of each finding. The records live in the run tree on disk. Thus the pin reads that tree
14
+ * through the workspace-root seam of the caller.
15
+ *
16
+ * A reference that carries a short citation also pins a bibliography record beside its key. The key list
17
+ * keeps the membership role, thus the record map is optional and it changes no resolution.
10
18
  */
11
19
  import { type Result } from "neverthrow";
12
20
  import type { Querier } from "../state/db.js";
21
+ import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
13
22
  import type { ReportSnapshot } from "./reference-resolver.js";
14
23
  /**
15
- * The reason that the pin 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.
24
+ * The reason that the pin gave no snapshot. The ledger read and the run listing are the two operations
25
+ * that can fail. An analysis with no registered artifact and an analysis with no synthesis on disk are
26
+ * normal answers, thus absence is not a member of this set. `cause` carries the underlying fault for a
27
+ * log.
18
28
  */
19
29
  export type PinSnapshotError = {
20
- kind: "ledger-read-failed";
30
+ kind: "ledger-read-failed" | "run-listing-failed";
21
31
  cause: unknown;
22
32
  };
33
+ /** The construction options of the pin. */
34
+ export interface PinReportSnapshotOptions {
35
+ /**
36
+ * The workspace-root seam of the caller. The citation evidence sits on disk, thus a composition
37
+ * that binds no seam pins the artifact map alone.
38
+ */
39
+ readonly resolveWorkspaceRoot?: ResolveWorkspaceRoot;
40
+ }
23
41
  /**
24
- * Pin the snapshot of one analysis from the artifact ledger.
42
+ * Pin the snapshot of one analysis from the artifact ledger and from the synthesis records of its runs.
25
43
  *
26
44
  * The pin copies no cell and no byte. An entry pins identity alone, thus the snapshot grows with the
27
45
  * count of artifacts and never with the size of the data. A read of an artifact belongs to the value
28
46
  * tier, which runs one time for each report version.
29
47
  */
30
- export declare function pinReportSnapshot(pool: Querier, analysisId: string): Promise<Result<ReportSnapshot, PinSnapshotError>>;
48
+ export declare function pinReportSnapshot(pool: Querier, analysisId: string, options?: PinReportSnapshotOptions): Promise<Result<ReportSnapshot, PinSnapshotError>>;