@inflexa-ai/harness 0.31.0 → 0.32.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 (62) hide show
  1. package/dist/agents/conversation-agent.d.ts +7 -0
  2. package/dist/agents/conversation-agent.js +1 -0
  3. package/dist/agents/report-session-agent.d.ts +9 -0
  4. package/dist/agents/report-session-agent.js +10 -2
  5. package/dist/app/chat-turn.d.ts +11 -0
  6. package/dist/app/chat-turn.js +17 -1
  7. package/dist/app/spawn-report-session.d.ts +12 -0
  8. package/dist/app/spawn-report-session.js +21 -1
  9. package/dist/contracts/report-blocks.d.ts +8 -8
  10. package/dist/contracts/schemas/chat-parts.d.ts +6 -6
  11. package/dist/index.d.ts +4 -1
  12. package/dist/index.js +8 -0
  13. package/dist/prompts/briefing.d.ts +12 -1
  14. package/dist/prompts/briefing.js +27 -1
  15. package/dist/prompts/sandbox-standards.d.ts +1 -1
  16. package/dist/prompts/sandbox-standards.js +27 -0
  17. package/dist/provenance/seam.d.ts +203 -0
  18. package/dist/provenance/seam.js +71 -0
  19. package/dist/report-model/authoring-grammar.d.ts +2 -2
  20. package/dist/report-render/assets.d.ts +10 -0
  21. package/dist/report-render/assets.js +14 -0
  22. package/dist/report-render/design.js +256 -0
  23. package/dist/report-render/page.d.ts +75 -0
  24. package/dist/report-render/page.js +793 -1
  25. package/dist/report-render/provenance-data.d.ts +42 -0
  26. package/dist/report-render/provenance-data.js +62 -0
  27. package/dist/report-render/render.d.ts +20 -1
  28. package/dist/report-render/render.js +23 -10
  29. package/dist/report-render/types.d.ts +3 -0
  30. package/dist/report-render/views/chart-view.d.ts +5 -1
  31. package/dist/report-render/views/chart-view.js +6 -2
  32. package/dist/report-render/views/lineage.d.ts +93 -0
  33. package/dist/report-render/views/lineage.js +86 -0
  34. package/dist/report-render/views/page-view.d.ts +22 -1
  35. package/dist/report-render/views/page-view.js +12 -3
  36. package/dist/report-render/views/prose.d.ts +7 -1
  37. package/dist/report-render/views/prose.js +10 -6
  38. package/dist/report-render/views/references-view.d.ts +17 -1
  39. package/dist/report-render/views/references-view.js +12 -4
  40. package/dist/report-render/views/values.d.ts +17 -4
  41. package/dist/report-render/views/values.js +21 -8
  42. package/dist/runtime/assemble.d.ts +22 -0
  43. package/dist/runtime/assemble.js +4 -0
  44. package/dist/sandbox/k8s-client.d.ts +0 -5
  45. package/dist/sandbox/k8s-client.js +9 -115
  46. package/dist/sandbox/thread-env.d.ts +18 -12
  47. package/dist/sandbox/thread-env.js +36 -25
  48. package/dist/schemas/artifact-manifest.d.ts +4 -4
  49. package/dist/tools/display/show-user.d.ts +1 -1
  50. package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
  51. package/dist/tools/report-authoring/authoring-tools.js +58 -16
  52. package/dist/tools/report-session/derive-table.d.ts +3 -0
  53. package/dist/tools/report-session/derive-table.js +15 -0
  54. package/dist/tools/report-session/preview-report.d.ts +13 -4
  55. package/dist/tools/report-session/preview-report.js +23 -6
  56. package/dist/tools/report-session/record-version.d.ts +3 -0
  57. package/dist/tools/report-session/record-version.js +7 -3
  58. package/dist/tools/start-report-session.d.ts +7 -0
  59. package/dist/tools/start-report-session.js +1 -0
  60. package/dist/workflows/execute-analysis.d.ts +15 -69
  61. package/dist/workflows/execute-analysis.js +5 -4
  62. package/package.json +2 -1
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The data-script carrier of the provenance of one analysis.
3
+ *
4
+ * A `fetch` of a local file is refused on a `file://` page, thus the document cannot load the way an image
5
+ * does. A classic script asset loads on any page, and it registers itself under one global when the browser
6
+ * runs it. The carrier is the same one that a table payload uses.
7
+ *
8
+ * The document and the attestation are opaque text. This module reads no field of them, and it parses
9
+ * nothing. Thus the format belongs to the writer of the document, and a change of that format costs no
10
+ * change here.
11
+ *
12
+ * The document and the attestation ride separate assets, thus a new attestation over an unchanged document
13
+ * rewrites the attestation alone. Each name carries the hash of its own bytes, thus two renders of one
14
+ * document give one name and the sweep of the stage removes a name that the page does not reference.
15
+ *
16
+ * The module is pure, and it reads no file. The renderer derives an asset, and the caller writes it.
17
+ */
18
+ import type { DataAsset } from "./table-data.js";
19
+ /**
20
+ * The one global that both assets register under.
21
+ *
22
+ * The page script reads the same name, thus the carrier and the reader cannot disagree over a rename.
23
+ */
24
+ export declare const REPORT_PROVENANCE_GLOBAL = "__REPORT_PROV";
25
+ /**
26
+ * The provenance that the page carries: the document text, and the attestation text where the source holds
27
+ * one.
28
+ *
29
+ * Both are opaque strings. The renderer moves them into the page and never into a parse, thus a document of
30
+ * any format rides unchanged and the page-side reader owns the format.
31
+ */
32
+ export interface ProvenanceExport {
33
+ readonly document: string;
34
+ readonly attestation?: string;
35
+ }
36
+ /**
37
+ * The data assets of one provenance export, in load order.
38
+ *
39
+ * A source that holds no attestation gives the document asset alone. Thus an unsigned document still rides
40
+ * the page, and the reader finds no attestation member on the global.
41
+ */
42
+ export declare function provenanceDataAssets(provenance: ProvenanceExport): DataAsset[];
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The data-script carrier of the provenance of one analysis.
3
+ *
4
+ * A `fetch` of a local file is refused on a `file://` page, thus the document cannot load the way an image
5
+ * does. A classic script asset loads on any page, and it registers itself under one global when the browser
6
+ * runs it. The carrier is the same one that a table payload uses.
7
+ *
8
+ * The document and the attestation are opaque text. This module reads no field of them, and it parses
9
+ * nothing. Thus the format belongs to the writer of the document, and a change of that format costs no
10
+ * change here.
11
+ *
12
+ * The document and the attestation ride separate assets, thus a new attestation over an unchanged document
13
+ * rewrites the attestation alone. Each name carries the hash of its own bytes, thus two renders of one
14
+ * document give one name and the sweep of the stage removes a name that the page does not reference.
15
+ *
16
+ * The module is pure, and it reads no file. The renderer derives an asset, and the caller writes it.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import { scriptJson } from "./script-json.js";
20
+ /**
21
+ * The one global that both assets register under.
22
+ *
23
+ * The page script reads the same name, thus the carrier and the reader cannot disagree over a rename.
24
+ */
25
+ export const REPORT_PROVENANCE_GLOBAL = "__REPORT_PROV";
26
+ /** The member of the global that holds the document text. */
27
+ const DOCUMENT_MEMBER = "document";
28
+ /** The member of the global that holds the attestation text. */
29
+ const ATTESTATION_MEMBER = "attestation";
30
+ /** The count of hash characters in an asset name. It matches a table payload, thus one form reads across the directory. */
31
+ const HASH_CHARS = 12;
32
+ /**
33
+ * The source text of one member of the global.
34
+ *
35
+ * The global takes the null-prototype form, the same as the table registry, thus a member sits as an
36
+ * ordinary entry. The text rides as a JSON string literal, thus hostile bytes are data and never source. A
37
+ * `</script` sequence inside the text cannot close the element, because `scriptJson` writes each `<` as its
38
+ * escape.
39
+ */
40
+ function memberSource(member, text) {
41
+ const registry = `window.${REPORT_PROVENANCE_GLOBAL}`;
42
+ return `${registry}=${registry}||Object.create(null);${registry}.${member}=${scriptJson(text)};\n`;
43
+ }
44
+ /** One member asset: the content-addressed file name under `suffix`, and the source text. */
45
+ function memberAsset(member, suffix, text) {
46
+ const bytes = memberSource(member, text);
47
+ const hash = createHash("sha256").update(bytes).digest("hex").slice(0, HASH_CHARS);
48
+ return { name: `prov-${hash}${suffix}`, bytes };
49
+ }
50
+ /**
51
+ * The data assets of one provenance export, in load order.
52
+ *
53
+ * A source that holds no attestation gives the document asset alone. Thus an unsigned document still rides
54
+ * the page, and the reader finds no attestation member on the global.
55
+ */
56
+ export function provenanceDataAssets(provenance) {
57
+ const assets = [memberAsset(DOCUMENT_MEMBER, ".data.js", provenance.document)];
58
+ if (provenance.attestation !== undefined) {
59
+ assets.push(memberAsset(ATTESTATION_MEMBER, ".sig.data.js", provenance.attestation));
60
+ }
61
+ return assets;
62
+ }
@@ -18,7 +18,20 @@ import { type Result } from "neverthrow";
18
18
  import type { ReportDocument } from "../contracts/report-blocks.js";
19
19
  import { type CitationRecords } from "../report-model/reference-resolver.js";
20
20
  import { type DerivationChain } from "./references.js";
21
+ import { type ProvenanceExport } from "./provenance-data.js";
21
22
  import type { RenderedPage, RenderProblem, RenderValues } from "./types.js";
23
+ /**
24
+ * The optional inputs of one render: the bibliography of the pin, the chains of the session, and the frozen
25
+ * provenance of the analysis.
26
+ *
27
+ * Each member is absent on its own, and a caller gives the members that it holds. A positional tail of
28
+ * three optional inputs made a caller of the last one pad the two before it, thus the three ride one bag.
29
+ */
30
+ export interface RenderOptions {
31
+ readonly records?: CitationRecords;
32
+ readonly derivations?: readonly DerivationChain[];
33
+ readonly provenance?: ProvenanceExport;
34
+ }
22
35
  /**
23
36
  * Render a report document, its value map, and the pinned citation records to one page and its data assets.
24
37
  *
@@ -34,10 +47,16 @@ import type { RenderedPage, RenderProblem, RenderValues } from "./types.js";
34
47
  * byte-identically with the chains and without them. A chain carries the relative source of its staged
35
48
  * script and of its derived file as data, thus the entry links both and this module stages nothing.
36
49
  *
50
+ * The provenance is the frozen document of the analysis, and it is optional too. The renderer moves the
51
+ * opaque text into two data assets that register one page global, and it parses no byte of it. Each
52
+ * grounded block then stamps its keys, and its marker carries the control that opens the chain. A caller
53
+ * that passes none renders the page byte for byte without the provenance script, without a stamp, and
54
+ * without a control.
55
+ *
37
56
  * The renderer writes no file. The data assets ride the result, and the caller stages them beside the page.
38
57
  * Thus the render stays pure, and two renders of one document give byte-identical assets.
39
58
  *
40
59
  * One artifact gives one payload. A table block and a chart block that bind it read that one asset, and a
41
60
  * chart under the inline bound carries its rows in its own option and registers none.
42
61
  */
43
- export declare function renderReportPage(document: ReportDocument, values: RenderValues, records?: CitationRecords, derivations?: readonly DerivationChain[]): Result<RenderedPage, RenderProblem[]>;
62
+ export declare function renderReportPage(document: ReportDocument, values: RenderValues, { records, derivations, provenance }?: RenderOptions): Result<RenderedPage, RenderProblem[]>;
@@ -22,6 +22,7 @@ import { deriveChartRender } from "./chart.js";
22
22
  import { assemblePage, renderBand, renderReferenceSection } from "./views/page-view.js";
23
23
  import { renderClaim, renderNav, renderSection, renderText } from "./views/prose.js";
24
24
  import { citationKeyOf, derivationChains, ReferenceLedger } from "./references.js";
25
+ import { provenanceDataAssets } from "./provenance-data.js";
25
26
  import { encodeTablePayload, tableDataAsset } from "./table-data.js";
26
27
  import { renderCitation, renderFigure, renderMetric, renderMetricGrid, renderTable, tableColumns, tableDisplay } from "./views/values.js";
27
28
  /**
@@ -39,16 +40,22 @@ import { renderCitation, renderFigure, renderMetric, renderMetricGrid, renderTab
39
40
  * byte-identically with the chains and without them. A chain carries the relative source of its staged
40
41
  * script and of its derived file as data, thus the entry links both and this module stages nothing.
41
42
  *
43
+ * The provenance is the frozen document of the analysis, and it is optional too. The renderer moves the
44
+ * opaque text into two data assets that register one page global, and it parses no byte of it. Each
45
+ * grounded block then stamps its keys, and its marker carries the control that opens the chain. A caller
46
+ * that passes none renders the page byte for byte without the provenance script, without a stamp, and
47
+ * without a control.
48
+ *
42
49
  * The renderer writes no file. The data assets ride the result, and the caller stages them beside the page.
43
50
  * Thus the render stays pure, and two renders of one document give byte-identical assets.
44
51
  *
45
52
  * One artifact gives one payload. A table block and a chart block that bind it read that one asset, and a
46
53
  * chart under the inline bound carries its rows in its own option and registers none.
47
54
  */
48
- export function renderReportPage(document, values, records, derivations) {
55
+ export function renderReportPage(document, values, { records, derivations, provenance } = {}) {
49
56
  const problems = [];
50
57
  const ledger = new ReferenceLedger();
51
- const data = { payloads: new Map(), mounts: 0 };
58
+ const data = { payloads: new Map(), mounts: 0, view: { lineage: provenance !== undefined } };
52
59
  const content = [];
53
60
  for (const [index, section] of document.sections.entries()) {
54
61
  content.push(renderBand(index, renderBlock(section, values, ledger, records, data, 0, problems)));
@@ -56,10 +63,16 @@ export function renderReportPage(document, values, records, derivations) {
56
63
  if (problems.length > 0) {
57
64
  return err(problems);
58
65
  }
59
- const dataAssets = [...data.payloads.values()].map((entry) => tableDataAsset(entry.ids, entry.payload));
66
+ const tableAssets = [...data.payloads.values()].map((entry) => tableDataAsset(entry.ids, entry.payload));
67
+ // The two asset groups stay apart in the skeleton, because the table decoder answers for a table payload
68
+ // alone. Thus a page that carries the provenance and no table loads no decoder.
69
+ const provenanceAssets = provenance === undefined ? [] : provenanceDataAssets(provenance);
60
70
  const nav = renderNav(document.sections);
61
71
  const references = renderReferenceSection(ledger, document.sections.length, records, derivationChains(derivations));
62
- return ok({ html: assemblePage(document.title, nav, content.join(""), references, dataAssets, data.mounts > 0), dataAssets });
72
+ return ok({
73
+ html: assemblePage(document.title, nav, content.join(""), references, { dataAssets: tableAssets, grids: data.mounts > 0, provenanceAssets }),
74
+ dataAssets: [...provenanceAssets, ...tableAssets],
75
+ });
63
76
  }
64
77
  /**
65
78
  * Register the payload of one bound artifact, and name this block as a reader of it.
@@ -86,9 +99,9 @@ function renderBlock(block, values, ledger, records, data, depth, problems) {
86
99
  case "text":
87
100
  return renderText(block);
88
101
  case "claim":
89
- return renderClaim(block, ledger);
102
+ return renderClaim(block, ledger, data.view);
90
103
  case "citation":
91
- return renderCitation(block, ledger, citationRecordOf(records, citationKeyOf(block.binding)));
104
+ return renderCitation(block, ledger, citationRecordOf(records, citationKeyOf(block.binding)), data.view);
92
105
  case "metric": {
93
106
  const entry = values[block.id];
94
107
  if (entry === undefined) {
@@ -99,7 +112,7 @@ function renderBlock(block, values, ledger, records, data, depth, problems) {
99
112
  problems.push(wrongShape(block.id, entry.type, "scalar"));
100
113
  return "";
101
114
  }
102
- return renderMetric(block, ledger, entry);
115
+ return renderMetric(block, ledger, entry, data.view);
103
116
  }
104
117
  case "table": {
105
118
  const entry = values[block.id];
@@ -117,7 +130,7 @@ function renderBlock(block, values, ledger, records, data, depth, problems) {
117
130
  const columns = tableColumns(entry);
118
131
  data.mounts += 1;
119
132
  registerPayload(data, block.binding, block.id, () => payloadOf(block.binding, entry, columns));
120
- return renderTable(block, ledger, entry.rows.length, entry.total);
133
+ return renderTable(block, ledger, entry.rows.length, entry.total, data.view);
121
134
  }
122
135
  case "figure": {
123
136
  const entry = values[block.id];
@@ -129,7 +142,7 @@ function renderBlock(block, values, ledger, records, data, depth, problems) {
129
142
  problems.push(wrongShape(block.id, entry.type, "figure"));
130
143
  return "";
131
144
  }
132
- return renderFigure(block, ledger, entry);
145
+ return renderFigure(block, ledger, entry, data.view);
133
146
  }
134
147
  case "chart": {
135
148
  const entry = values[block.id];
@@ -154,7 +167,7 @@ function renderBlock(block, values, ledger, records, data, depth, problems) {
154
167
  if (derived.value.readsPayload) {
155
168
  registerPayload(data, block.binding, block.id, () => payloadOf(block.binding, entry, columns));
156
169
  }
157
- return renderChart(block, ledger, derived.value.option);
170
+ return renderChart(block, ledger, derived.value.option, data.view);
158
171
  }
159
172
  case "section":
160
173
  return renderSection(block, depth, renderChildren(block.blocks, values, ledger, records, data, depth + 1, problems));
@@ -47,6 +47,9 @@ export type RenderValues = Record<string, RenderValue>;
47
47
  *
48
48
  * The renderer writes no file. Thus it gives the asset bytes back to the caller, and the caller stages
49
49
  * them beside the page. A document with no table gives an empty list, and its page references none.
50
+ *
51
+ * The list holds the provenance assets of the render beside the table payloads, thus one stage writes each
52
+ * file that the page loads and the caller sorts nothing.
50
53
  */
51
54
  export interface RenderedPage {
52
55
  html: string;
@@ -11,11 +11,15 @@
11
11
  import type { ChartBlock } from "../../contracts/report-blocks.js";
12
12
  import type { EchartOption } from "../chart.js";
13
13
  import type { ReferenceLedger } from "../references.js";
14
+ import { type ViewOptions } from "./lineage.js";
14
15
  /**
15
16
  * Render the card markup for a chart.
16
17
  *
17
18
  * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
18
19
  * appendix names the artifact that the chart plots. A chart with no title still shows its marker on the
19
20
  * same line.
21
+ *
22
+ * `view.lineage` states that the page carries a provenance document. The card then stamps its keys, and the
23
+ * marker carries the control that opens the chain.
20
24
  */
21
- export declare function renderChart(block: ChartBlock, ledger: ReferenceLedger, option: EchartOption): string;
25
+ export declare function renderChart(block: ChartBlock, ledger: ReferenceLedger, option: EchartOption, view?: ViewOptions): string;
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
10
10
  * height, because the chart runtime measures the container and a container with no height shows no chart.
11
11
  */
12
12
  import { raw } from "hono/html";
13
+ import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
13
14
  import { Marker } from "./references-view.js";
14
15
  import { scriptJson } from "../script-json.js";
15
16
  /**
@@ -18,15 +19,18 @@ import { scriptJson } from "../script-json.js";
18
19
  * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
19
20
  * appendix names the artifact that the chart plots. A chart with no title still shows its marker on the
20
21
  * same line.
22
+ *
23
+ * `view.lineage` states that the page carries a provenance document. The card then stamps its keys, and the
24
+ * marker carries the control that opens the chain.
21
25
  */
22
- export function renderChart(block, ledger, option) {
26
+ export function renderChart(block, ledger, option, view = DEFAULT_VIEW_OPTIONS) {
23
27
  const n = ledger.mark(block.binding);
24
28
  const containerId = chartContainerId(block.id);
25
29
  // The JSON goes to the page through `raw()`, thus `scriptJson` is the sole guard of this sink. It
26
30
  // replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
27
31
  // element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
28
32
  const json = scriptJson(option);
29
- return String(_jsxs("div", { class: "report-chart", children: [_jsxs("div", { class: "report-chart-title", children: [block.title, _jsx(Marker, { n: n })] }), _jsxs("div", { class: "report-chart-card corner-accents", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
33
+ return String(_jsxs("div", { class: "report-chart", ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsxs("div", { class: "report-chart-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] }), _jsxs("div", { class: "report-chart-card corner-accents", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
30
34
  }
31
35
  /** The container id derives from the block id, thus the id stays stable across renders. */
32
36
  function chartContainerId(blockId) {
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The lineage keys of a grounded block, and the control that opens the chain of one key.
3
+ *
4
+ * A grounded block stamps its identity into the markup: the block id, and one key for each reference that
5
+ * it binds, in marker order. The page script reads the stamp when a reader clicks a control. Thus the
6
+ * markup is the one contract between the renderer and the script, and neither side holds a copy of the
7
+ * other.
8
+ *
9
+ * A key is a pin or an external record. A pin names one immutable file by `path` and `hash`, and the walk
10
+ * resolves the node of those bytes. A citation names a paper, which no data chain reaches, thus its key
11
+ * carries the identity of the record instead.
12
+ *
13
+ * A reference that pins no single file gives no key. A derivation computes over two inputs, thus it names
14
+ * no file of its own and the marker of such a reference carries no control.
15
+ *
16
+ * The stamp is empty while the page carries no provenance document. Thus such a page holds no lineage
17
+ * attribute, no control, and the markup that it held before the stamp existed.
18
+ *
19
+ * The module also declares `ViewOptions`. Each view of the page reads that bag, and the lineage condition
20
+ * is the one truth that it holds today.
21
+ */
22
+ import type { Reference } from "../../contracts/report-reference.js";
23
+ /**
24
+ * The page-wide truths of one render.
25
+ *
26
+ * A truth of this kind is constant across the whole page, thus each view of one page decides it alike. The
27
+ * bag threads one time through the view layer. Thus the next truth joins this interface, and no view
28
+ * signature grows one more parameter for it.
29
+ *
30
+ * `lineage` states that the page carries a provenance document. Each grounded block then stamps its keys
31
+ * and shows its control, and a page with no document carries neither.
32
+ */
33
+ export interface ViewOptions {
34
+ readonly lineage: boolean;
35
+ }
36
+ /**
37
+ * The options that a view takes when the caller gives none. Each truth is off, thus the view renders its
38
+ * plain form.
39
+ */
40
+ export declare const DEFAULT_VIEW_OPTIONS: ViewOptions;
41
+ /** The attribute that names the block of one stamp. The page script reads the same name. */
42
+ export declare const LINEAGE_BLOCK_ATTRIBUTE = "data-lineage-block";
43
+ /** The attribute that carries the keys of one stamp, as a JSON array in marker order. */
44
+ export declare const LINEAGE_KEYS_ATTRIBUTE = "data-lineage-keys";
45
+ /** The attribute of one control: the place of its key in the key list of the block. */
46
+ export declare const LINEAGE_KEY_ATTRIBUTE = "data-lineage-key";
47
+ /** The class of the control that opens the chain of one reference. */
48
+ export declare const LINEAGE_CONTROL_CLASS = "report-lineage";
49
+ /** The pin of one artifact: the analysis-relative path, and the content hash of the bytes. */
50
+ export interface LineagePin {
51
+ readonly path: string;
52
+ readonly hash: string;
53
+ }
54
+ /** The identity of one external record: the identifier space, and the identifier inside it. */
55
+ export interface LineageRecord {
56
+ readonly idKind: string;
57
+ readonly id: string;
58
+ }
59
+ /** One key of a stamp. A reference that pins no single file gives `null` in place of a key. */
60
+ export type LineageKey = LineagePin | LineageRecord;
61
+ /**
62
+ * The key of one reference, or `null` where the reference pins no single file.
63
+ *
64
+ * A derivation is the one artifact-side kind that gives `null`. It computes over two inputs, thus the
65
+ * document holds no node of its own bytes and one pin cannot address it.
66
+ */
67
+ export declare function lineageKeyOf(reference: Reference): LineageKey | null;
68
+ /**
69
+ * The stamp attributes of one block, or an empty object while the page carries no provenance.
70
+ *
71
+ * The caller spreads the result onto the container element of the block. Thus a page with no provenance
72
+ * emits no attribute at all, and the spread is the one place that decides it.
73
+ *
74
+ * The key list keeps one entry for each binding, in marker order. Thus the place of a control indexes the
75
+ * bindings directly, and a binding that gives no key keeps its place as `null`.
76
+ */
77
+ export declare function lineageStamp(on: boolean, blockId: string, references: readonly Reference[]): Record<string, string>;
78
+ /**
79
+ * The place of the control of one reference, or `undefined` where the page carries no provenance or the
80
+ * reference pins nothing.
81
+ *
82
+ * A block that binds one reference passes no index, because its one key sits at the first place.
83
+ */
84
+ export declare function lineagePlace(on: boolean, reference: Reference, index?: number): number | undefined;
85
+ /**
86
+ * The control beside one marker. A click opens the chain of the key that sits at `place`.
87
+ *
88
+ * The control names its place and never its key. The stamp of the block holds the keys, thus one page
89
+ * carries one copy of each key and a claim of several bindings repeats none of them.
90
+ */
91
+ export declare function LineageControl({ place }: {
92
+ place: number;
93
+ }): import("hono/jsx/jsx-dev-runtime").JSX.Element;
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * The options that a view takes when the caller gives none. Each truth is off, thus the view renders its
4
+ * plain form.
5
+ */
6
+ export const DEFAULT_VIEW_OPTIONS = { lineage: false };
7
+ /** The attribute that names the block of one stamp. The page script reads the same name. */
8
+ export const LINEAGE_BLOCK_ATTRIBUTE = "data-lineage-block";
9
+ /** The attribute that carries the keys of one stamp, as a JSON array in marker order. */
10
+ export const LINEAGE_KEYS_ATTRIBUTE = "data-lineage-keys";
11
+ /** The attribute of one control: the place of its key in the key list of the block. */
12
+ export const LINEAGE_KEY_ATTRIBUTE = "data-lineage-key";
13
+ /** The class of the control that opens the chain of one reference. */
14
+ export const LINEAGE_CONTROL_CLASS = "report-lineage";
15
+ /** The name of the control for a reader who hears the page instead of seeing it. */
16
+ const LINEAGE_CONTROL_LABEL = "Show the lineage of this reference";
17
+ /** The class of the glyph inside the control. The design sheet holds the matching rule. */
18
+ const LINEAGE_GLYPH_CLASS = "report-lineage-glyph";
19
+ /**
20
+ * The glyph of the control: a branch of three nodes on the 16px grid.
21
+ *
22
+ * The drawing reads as a chain that divides, which is what the panel behind it shows. A text glyph reads
23
+ * as a disclosure of more prose, and a bracket form would give the page two notations that look alike and
24
+ * mean different things.
25
+ *
26
+ * The stroke takes the current color, thus the muted color of the button and its primary color on hover
27
+ * both reach the drawing and the sheet holds one color for the two states. The glyph is decoration, thus
28
+ * it hides from a reader who hears the page and the label of the button answers instead.
29
+ */
30
+ function LineageGlyph() {
31
+ return (_jsxs("svg", { class: LINEAGE_GLYPH_CLASS, width: "13", height: "13", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "aria-hidden": "true", children: [_jsx("circle", { cx: "4", cy: "4", r: "2" }), _jsx("circle", { cx: "4", cy: "12", r: "2" }), _jsx("circle", { cx: "12", cy: "12", r: "2" }), _jsx("path", { d: "M4 6v4" }), _jsx("path", { d: "M6 12h4" }), _jsx("path", { d: "M12 10V8a4 4 0 0 0-4-4H6" })] }));
32
+ }
33
+ /**
34
+ * The key of one reference, or `null` where the reference pins no single file.
35
+ *
36
+ * A derivation is the one artifact-side kind that gives `null`. It computes over two inputs, thus the
37
+ * document holds no node of its own bytes and one pin cannot address it.
38
+ */
39
+ export function lineageKeyOf(reference) {
40
+ switch (reference.kind) {
41
+ case "citation":
42
+ return { idKind: reference.idKind, id: reference.id };
43
+ case "derivation":
44
+ return null;
45
+ default:
46
+ return { path: reference.path, hash: reference.hash };
47
+ }
48
+ }
49
+ /**
50
+ * The stamp attributes of one block, or an empty object while the page carries no provenance.
51
+ *
52
+ * The caller spreads the result onto the container element of the block. Thus a page with no provenance
53
+ * emits no attribute at all, and the spread is the one place that decides it.
54
+ *
55
+ * The key list keeps one entry for each binding, in marker order. Thus the place of a control indexes the
56
+ * bindings directly, and a binding that gives no key keeps its place as `null`.
57
+ */
58
+ export function lineageStamp(on, blockId, references) {
59
+ if (!on) {
60
+ return {};
61
+ }
62
+ // The JSON rides an attribute value, and the markup runtime escapes each attribute value. Thus a
63
+ // hostile path reaches the page as text and it cannot leave its slot.
64
+ return {
65
+ [LINEAGE_BLOCK_ATTRIBUTE]: blockId,
66
+ [LINEAGE_KEYS_ATTRIBUTE]: JSON.stringify(references.map(lineageKeyOf)),
67
+ };
68
+ }
69
+ /**
70
+ * The place of the control of one reference, or `undefined` where the page carries no provenance or the
71
+ * reference pins nothing.
72
+ *
73
+ * A block that binds one reference passes no index, because its one key sits at the first place.
74
+ */
75
+ export function lineagePlace(on, reference, index = 0) {
76
+ return on && lineageKeyOf(reference) !== null ? index : undefined;
77
+ }
78
+ /**
79
+ * The control beside one marker. A click opens the chain of the key that sits at `place`.
80
+ *
81
+ * The control names its place and never its key. The stamp of the block holds the keys, thus one page
82
+ * carries one copy of each key and a claim of several bindings repeats none of them.
83
+ */
84
+ export function LineageControl({ place }) {
85
+ return (_jsx("button", { type: "button", class: LINEAGE_CONTROL_CLASS, "aria-label": LINEAGE_CONTROL_LABEL, "aria-expanded": "false", [LINEAGE_KEY_ATTRIBUTE]: String(place), children: _jsx(LineageGlyph, {}) }));
86
+ }
@@ -37,6 +37,18 @@ export declare function renderBand(index: number, inner: string): string;
37
37
  * such a path states it.
38
38
  */
39
39
  export declare function renderReferenceSection(ledger: ReferenceLedger, index: number, records?: CitationRecords, chains?: DerivationChains): string;
40
+ /**
41
+ * What one page loads: the table payloads, the grid condition, and the provenance assets.
42
+ *
43
+ * The two asset groups carry one type, and they land in different places of the skeleton. A positional pair
44
+ * of one type takes no fault at the build when a caller swaps the two. Thus the group rides one bag, and
45
+ * each member of it carries its own name.
46
+ */
47
+ export interface PageAssets {
48
+ readonly dataAssets: readonly DataAsset[];
49
+ readonly grids: boolean;
50
+ readonly provenanceAssets: readonly DataAsset[];
51
+ }
40
52
  /**
41
53
  * Assemble the page from the title, the navigation, the main content, the reference frame, and the data
42
54
  * assets. The title passes through the runtime as text, and the runtime escapes it. The reference frame is
@@ -46,7 +58,16 @@ export declare function renderReferenceSection(ledger: ReferenceLedger, index: n
46
58
  * of them. Thus every payload is registered before any reader looks. A page with no asset carries no tag
47
59
  * and no decoder.
48
60
  *
61
+ * The provenance assets lead the group, and they stand before each bootstrap. Thus a reader of the global
62
+ * finds the document whichever script asks for it first. A page with no provenance carries no such tag, and
63
+ * the rest of the skeleton is what it was.
64
+ *
65
+ * The provenance also gates the library reference and the lineage popover. The library stands in the head,
66
+ * beside the other two runtimes, and the popover script reads it when a reader clicks a control. Thus the
67
+ * place of the script in the order is free and it stands last. A page with no provenance carries no stamp
68
+ * and no control, thus neither the library nor the script would find anything to open.
69
+ *
49
70
  * `grids` states that the content holds a grid mount. The grid runtime weighs about two megabytes, thus a
50
71
  * page whose only payload feeds a chart references neither the runtime nor the grid boot.
51
72
  */
52
- export declare function assemblePage(title: string, nav: string, content: string, references: string, dataAssets: readonly DataAsset[], grids: boolean): string;
73
+ export declare function assemblePage(title: string, nav: string, content: string, references: string, { dataAssets, grids, provenanceAssets }: PageAssets): string;
@@ -18,7 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
18
18
  * one width, and the title over them sits on the same left edge.
19
19
  */
20
20
  import { raw } from "hono/html";
21
- import { ASSET_HEAD, CHART_BOOTSTRAP, FADE_IN_OBSERVER, GRID_ASSET_HEAD, GRID_BOOTSTRAP, SECTION_SPY, TABLE_DATA_DECODER } from "../page.js";
21
+ import { ASSET_HEAD, CHART_BOOTSTRAP, FADE_IN_OBSERVER, GRID_ASSET_HEAD, GRID_BOOTSTRAP, LINEAGE_ASSET_HEAD, LINEAGE_POPOVER, SECTION_SPY, TABLE_DATA_DECODER, } from "../page.js";
22
22
  import { stagedSource } from "../assets.js";
23
23
  import { DESIGN_CSS, ECHARTS_THEME, ECHARTS_THEME_NAME } from "../design.js";
24
24
  import { renderReferences } from "./references-view.js";
@@ -82,10 +82,19 @@ export function renderReferenceSection(ledger, index, records, chains) {
82
82
  * of them. Thus every payload is registered before any reader looks. A page with no asset carries no tag
83
83
  * and no decoder.
84
84
  *
85
+ * The provenance assets lead the group, and they stand before each bootstrap. Thus a reader of the global
86
+ * finds the document whichever script asks for it first. A page with no provenance carries no such tag, and
87
+ * the rest of the skeleton is what it was.
88
+ *
89
+ * The provenance also gates the library reference and the lineage popover. The library stands in the head,
90
+ * beside the other two runtimes, and the popover script reads it when a reader clicks a control. Thus the
91
+ * place of the script in the order is free and it stands last. A page with no provenance carries no stamp
92
+ * and no control, thus neither the library nor the script would find anything to open.
93
+ *
85
94
  * `grids` states that the content holds a grid mount. The grid runtime weighs about two megabytes, thus a
86
95
  * page whose only payload feeds a chart references neither the runtime nor the grid boot.
87
96
  */
88
- export function assemblePage(title, nav, content, references, dataAssets, grids) {
97
+ export function assemblePage(title, nav, content, references, { dataAssets, grids, provenanceAssets }) {
89
98
  return ("<!doctype html>" +
90
- String(_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), raw(ASSET_HEAD), grids ? raw(GRID_ASSET_HEAD) : null, _jsx("style", { children: raw(DESIGN_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsx("header", { class: "report-hero texture-noise", children: _jsx("div", { class: "report-container", children: _jsxs("div", { class: "report-content", children: [_jsx("p", { class: "report-eyebrow", children: HERO_EYEBROW }), _jsx("h1", { class: "report-display", children: title })] }) }) }), _jsxs("main", { children: [raw(content), references.length > 0 ? raw(references) : null] }), _jsx("footer", { class: "report-footer", children: _jsx("div", { class: "report-container", children: _jsxs("div", { class: "report-content report-footer-row", children: [_jsx("span", { class: "report-footer-title", children: title }), _jsx("span", { class: "report-footer-note", children: FOOTER_NOTE })] }) }) }), dataAssets.map((asset) => (_jsx("script", { src: stagedSource(asset.name) }))), dataAssets.length > 0 ? _jsx("script", { children: raw(TABLE_DATA_DECODER) }) : null, _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(FADE_IN_OBSERVER) }), grids ? _jsx("script", { children: raw(GRID_BOOTSTRAP) }) : null, _jsx("script", { children: raw(CHART_BOOTSTRAP) }), _jsx("script", { children: raw(SECTION_SPY) })] })] })));
99
+ String(_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), raw(ASSET_HEAD), grids ? raw(GRID_ASSET_HEAD) : null, provenanceAssets.length > 0 ? raw(LINEAGE_ASSET_HEAD) : null, _jsx("style", { children: raw(DESIGN_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsx("header", { class: "report-hero texture-noise", children: _jsx("div", { class: "report-container", children: _jsxs("div", { class: "report-content", children: [_jsx("p", { class: "report-eyebrow", children: HERO_EYEBROW }), _jsx("h1", { class: "report-display", children: title })] }) }) }), _jsxs("main", { children: [raw(content), references.length > 0 ? raw(references) : null] }), _jsx("footer", { class: "report-footer", children: _jsx("div", { class: "report-container", children: _jsxs("div", { class: "report-content report-footer-row", children: [_jsx("span", { class: "report-footer-title", children: title }), _jsx("span", { class: "report-footer-note", children: FOOTER_NOTE })] }) }) }), provenanceAssets.map((asset) => (_jsx("script", { src: stagedSource(asset.name) }))), dataAssets.map((asset) => (_jsx("script", { src: stagedSource(asset.name) }))), dataAssets.length > 0 ? _jsx("script", { children: raw(TABLE_DATA_DECODER) }) : null, _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(FADE_IN_OBSERVER) }), grids ? _jsx("script", { children: raw(GRID_BOOTSTRAP) }) : null, _jsx("script", { children: raw(CHART_BOOTSTRAP) }), _jsx("script", { children: raw(SECTION_SPY) }), provenanceAssets.length > 0 ? _jsx("script", { children: raw(LINEAGE_POPOVER) }) : null] })] })));
91
100
  }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { ClaimBlock, SectionBlock, TextBlock } from "../../contracts/report-blocks.js";
12
12
  import type { ReferenceLedger } from "../references.js";
13
+ import { type ViewOptions } from "./lineage.js";
13
14
  /**
14
15
  * Render a text block as escaped paragraphs, and then its list.
15
16
  *
@@ -23,8 +24,13 @@ export declare function renderText(block: TextBlock): string;
23
24
  * paragraph, thus they sit at the end of the claim text. The ledger marks each binding, thus a shared
24
25
  * reference keeps one number across the page. An artifact binding and a citation binding read the same
25
26
  * bracket, because one ladder counts them both.
27
+ *
28
+ * `view.lineage` states that the page carries a provenance document. The claim renders as paragraphs and it
29
+ * has no container of its own, thus one wrapper takes the stamp of the whole block. The wrapper carries no
30
+ * class, thus it adds no rule to the design sheet and each paragraph keeps its own spacing. A page with no
31
+ * document renders the paragraphs alone.
26
32
  */
27
- export declare function renderClaim(block: ClaimBlock, ledger: ReferenceLedger): string;
33
+ export declare function renderClaim(block: ClaimBlock, ledger: ReferenceLedger, view?: ViewOptions): string;
28
34
  /**
29
35
  * Render a section wrapper around its rendered children. The heading level is `h2` at depth 0, `h3` at
30
36
  * depth 1, and `h4` at depth 2 or deeper. The walk renders the children first, and it passes the markup
@@ -10,6 +10,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
10
10
  * item takes the same escape as a paragraph, thus no item reaches the page as markup.
11
11
  */
12
12
  import { raw } from "hono/html";
13
+ import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
13
14
  import { Marker } from "./references-view.js";
14
15
  /** The paragraph class of a text block and a claim block. The paragraph fills the content column. */
15
16
  const PARAGRAPH_CLASS = "report-prose";
@@ -57,15 +58,18 @@ export function renderText(block) {
57
58
  * paragraph, thus they sit at the end of the claim text. The ledger marks each binding, thus a shared
58
59
  * reference keeps one number across the page. An artifact binding and a citation binding read the same
59
60
  * bracket, because one ladder counts them both.
61
+ *
62
+ * `view.lineage` states that the page carries a provenance document. The claim renders as paragraphs and it
63
+ * has no container of its own, thus one wrapper takes the stamp of the whole block. The wrapper carries no
64
+ * class, thus it adds no rule to the design sheet and each paragraph keeps its own spacing. A page with no
65
+ * document renders the paragraphs alone.
60
66
  */
61
- export function renderClaim(block, ledger) {
62
- const markers = block.bindings.map((reference) => _jsx(Marker, { n: ledger.mark(reference) }));
67
+ export function renderClaim(block, ledger, view = DEFAULT_VIEW_OPTIONS) {
68
+ const markers = block.bindings.map((reference, index) => _jsx(Marker, { n: ledger.mark(reference), lineage: lineagePlace(view.lineage, reference, index) }));
63
69
  const paragraphs = splitParagraphs(block.content.prose);
64
- if (paragraphs.length === 0) {
65
- return String(_jsx("p", { class: PARAGRAPH_CLASS, children: markers }));
66
- }
67
70
  const last = paragraphs.length - 1;
68
- return String(_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
71
+ const body = paragraphs.length === 0 ? (_jsx("p", { class: PARAGRAPH_CLASS, children: markers })) : (_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
72
+ return String(view.lineage ? _jsx("div", { ...lineageStamp(view.lineage, block.id, block.bindings), children: body }) : body);
69
73
  }
70
74
  /** The heading class of a section, by heading level. A heading uses the sans family, never the mono family. */
71
75
  function headingClass(level) {