@inflexa-ai/harness 0.19.1 → 0.19.2

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 (50) hide show
  1. package/dist/agents/report-session-agent.d.ts +7 -0
  2. package/dist/agents/report-session-agent.js +2 -1
  3. package/dist/auth/local-run-authorizer.d.ts +3 -2
  4. package/dist/auth/local-run-authorizer.js +4 -2
  5. package/dist/execution/run-authorizer.d.ts +18 -0
  6. package/dist/execution/run-canceler.d.ts +67 -0
  7. package/dist/execution/run-canceler.js +130 -0
  8. package/dist/index.d.ts +5 -2
  9. package/dist/index.js +6 -1
  10. package/dist/report-render/assets.d.ts +42 -0
  11. package/dist/report-render/assets.js +61 -0
  12. package/dist/report-render/chart.js +31 -10
  13. package/dist/report-render/design.d.ts +120 -0
  14. package/dist/report-render/design.js +808 -0
  15. package/dist/report-render/page.d.ts +32 -115
  16. package/dist/report-render/page.js +130 -273
  17. package/dist/report-render/render.js +34 -11
  18. package/dist/report-render/views/chart-view.d.ts +6 -3
  19. package/dist/report-render/views/chart-view.js +10 -5
  20. package/dist/report-render/views/page-view.d.ts +19 -8
  21. package/dist/report-render/views/page-view.js +31 -11
  22. package/dist/report-render/views/prose.d.ts +1 -0
  23. package/dist/report-render/views/prose.js +7 -13
  24. package/dist/report-render/views/references-view.js +5 -5
  25. package/dist/report-render/views/values.d.ts +15 -6
  26. package/dist/report-render/views/values.js +29 -10
  27. package/dist/runtime/assemble.d.ts +10 -0
  28. package/dist/runtime/assemble.js +1 -0
  29. package/dist/sandbox/k8s-client.js +18 -0
  30. package/dist/sandbox/types.d.ts +6 -0
  31. package/dist/state/analyses.d.ts +3 -26
  32. package/dist/state/analyses.js +6 -64
  33. package/dist/state/index.d.ts +2 -2
  34. package/dist/state/index.js +2 -2
  35. package/dist/state/init.js +3 -3
  36. package/dist/state/runs.d.ts +10 -0
  37. package/dist/state/runs.js +21 -0
  38. package/dist/tasks/data-profile.d.ts +4 -0
  39. package/dist/tasks/data-profile.js +14 -0
  40. package/dist/tools/approval/gateway.d.ts +8 -1
  41. package/dist/tools/approval/gateway.js +9 -2
  42. package/dist/tools/approval/queries.d.ts +9 -2
  43. package/dist/tools/approval/queries.js +12 -4
  44. package/dist/tools/report-session/index.d.ts +1 -1
  45. package/dist/tools/report-session/index.js +1 -1
  46. package/dist/tools/report-session/preview-report.d.ts +11 -1
  47. package/dist/tools/report-session/preview-report.js +35 -12
  48. package/dist/workflows/sandbox-step.d.ts +4 -0
  49. package/dist/workflows/sandbox-step.js +21 -2
  50. package/package.json +5 -1
@@ -16,10 +16,10 @@
16
16
  import { err, ok } from "neverthrow";
17
17
  import { renderChart } from "./views/chart-view.js";
18
18
  import { deriveChartOption } from "./chart.js";
19
- import { assemblePage, renderReferenceSection } from "./views/page-view.js";
19
+ import { assemblePage, renderBand, renderReferenceSection } from "./views/page-view.js";
20
20
  import { renderClaim, renderNav, renderSection, renderText } from "./views/prose.js";
21
21
  import { ReferenceLedger } from "./references.js";
22
- import { renderCitation, renderFigure, renderMetric, renderTable } from "./views/values.js";
22
+ import { renderCitation, renderFigure, renderMetric, renderMetricGrid, renderTable } from "./views/values.js";
23
23
  /**
24
24
  * Render a report document and its value map to one HTML string.
25
25
  *
@@ -30,14 +30,14 @@ export function renderReportPage(document, values) {
30
30
  const problems = [];
31
31
  const ledger = new ReferenceLedger();
32
32
  const content = [];
33
- for (const section of document.sections) {
34
- content.push(renderBlock(section, values, ledger, 0, problems));
33
+ for (const [index, section] of document.sections.entries()) {
34
+ content.push(renderBand(index, renderBlock(section, values, ledger, 0, problems)));
35
35
  }
36
36
  if (problems.length > 0) {
37
37
  return err(problems);
38
38
  }
39
39
  const nav = renderNav(document.sections);
40
- const references = renderReferenceSection(ledger);
40
+ const references = renderReferenceSection(ledger, document.sections.length);
41
41
  return ok(assemblePage(document.title, nav, content.join(""), references));
42
42
  }
43
43
  /**
@@ -106,14 +106,37 @@ function renderBlock(block, values, ledger, depth, problems) {
106
106
  }
107
107
  return renderChart(block, option.value);
108
108
  }
109
- case "section": {
110
- const parts = [];
111
- for (const child of block.blocks) {
112
- parts.push(renderBlock(child, values, ledger, depth + 1, problems));
113
- }
114
- return renderSection(block, depth, parts.join(""));
109
+ case "section":
110
+ return renderSection(block, depth, renderChildren(block.blocks, values, ledger, depth + 1, problems));
111
+ }
112
+ }
113
+ /**
114
+ * Render the children of one section, and group a consecutive run of metric siblings into one grid.
115
+ *
116
+ * A run of two or more metric blocks reads as one row of statistics, thus the grid holds the whole run. A
117
+ * lone metric stays one card, and no grid wraps it. A block of a different kind ends the run.
118
+ */
119
+ function renderChildren(blocks, values, ledger, depth, problems) {
120
+ const parts = [];
121
+ let index = 0;
122
+ while (index < blocks.length) {
123
+ const run = metricRunLength(blocks, index);
124
+ const rendered = [];
125
+ for (let offset = 0; offset < Math.max(run, 1); offset += 1) {
126
+ rendered.push(renderBlock(blocks[index + offset], values, ledger, depth, problems));
115
127
  }
128
+ parts.push(run > 1 ? renderMetricGrid(rendered.join("")) : rendered.join(""));
129
+ index += Math.max(run, 1);
130
+ }
131
+ return parts.join("");
132
+ }
133
+ /** The count of the metric blocks that start at `start`. A block of a different kind gives zero. */
134
+ function metricRunLength(blocks, start) {
135
+ let length = 0;
136
+ while (start + length < blocks.length && blocks[start + length].kind === "metric") {
137
+ length += 1;
116
138
  }
139
+ return length;
117
140
  }
118
141
  /** A `missing-value` problem that names the block and the shape that it needs. */
119
142
  function missingValue(blockId, expected) {
@@ -1,11 +1,14 @@
1
1
  /**
2
- * The chart container markup.
2
+ * The chart panel markup.
3
3
  *
4
4
  * The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
5
5
  * `<script type="application/json">` element MUST follow the container div with no element between them.
6
- * The title renders above the container, and the caption renders below.
6
+ *
7
+ * The panel is the window-chrome form: the three dots on the left, the title in the center, and the
8
+ * `CORTEX` badge on the right. It is the one component with rounded corners. The chart body carries a fixed
9
+ * height, because the chart runtime measures the container and a container with no height shows no chart.
7
10
  */
8
11
  import type { ChartBlock } from "../../contracts/report-blocks.js";
9
12
  import type { EchartOption } from "../chart.js";
10
- /** Render the container markup for a chart. */
13
+ /** Render the panel markup for a chart. */
11
14
  export declare function renderChart(block: ChartBlock, option: EchartOption): string;
@@ -1,21 +1,26 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
2
  /**
3
- * The chart container markup.
3
+ * The chart panel markup.
4
4
  *
5
5
  * The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
6
6
  * `<script type="application/json">` element MUST follow the container div with no element between them.
7
- * The title renders above the container, and the caption renders below.
7
+ *
8
+ * The panel is the window-chrome form: the three dots on the left, the title in the center, and the
9
+ * `CORTEX` badge on the right. It is the one component with rounded corners. The chart body carries a fixed
10
+ * height, because the chart runtime measures the container and a container with no height shows no chart.
8
11
  */
9
12
  import { raw } from "hono/html";
10
13
  import { scriptJson } from "../script-json.js";
11
- /** Render the container markup for a chart. */
14
+ /** The product badge of a chart panel. */
15
+ const PANEL_BADGE = "CORTEX";
16
+ /** Render the panel markup for a chart. */
12
17
  export function renderChart(block, option) {
13
18
  const containerId = chartContainerId(block.id);
14
19
  // The JSON goes to the page through `raw()`, thus `scriptJson` is the sole guard of this sink. It
15
20
  // replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
16
21
  // element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
17
22
  const json = scriptJson(option);
18
- return String(_jsxs(_Fragment, { children: [block.title !== undefined ? _jsx("p", { class: "chart-title", children: block.title }) : null, _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: "chart-caption", children: block.caption }) : null] }));
23
+ return String(_jsxs("div", { class: "report-chart", children: [_jsxs("div", { class: "window-chrome", children: [_jsxs("div", { class: "window-chrome-bar", children: [_jsxs("div", { class: "chrome-dots", children: [_jsx("span", { class: "chrome-dot dot-1" }), _jsx("span", { class: "chrome-dot dot-2" }), _jsx("span", { class: "chrome-dot dot-3" })] }), block.title !== undefined ? _jsx("span", { class: "window-chrome-title", children: block.title }) : null, _jsx("span", { class: "window-chrome-badge", children: PANEL_BADGE })] }), _jsxs("div", { class: "window-chrome-body", 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] }));
19
24
  }
20
25
  /** The container id derives from the block id, thus the id stays stable across renders. */
21
26
  function chartContainerId(blockId) {
@@ -1,17 +1,28 @@
1
1
  /**
2
- * The page assembly.
2
+ * The page assembly: the hero, the bands, the reference band, and the footer.
3
3
  *
4
- * The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
5
- * The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
6
- * navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
7
- * `raw()` inserts them byte for byte.
4
+ * The skeleton inlines the style rules in the head, and it puts the three scripts at the end of the body.
5
+ * The order of the three scripts is a contract. The theme registration runs before the chart bootstrap,
6
+ * thus each chart finds its theme. The fade-in observer also runs before the chart bootstrap, thus the
7
+ * bootstrap finds the reveal gate and it signals readiness after the first reveal pass. The navigation, the
8
+ * main content, and the reference frame arrive as already-escaped markup strings, thus `raw()` inserts them
9
+ * byte for byte.
10
+ *
11
+ * One band holds one top-level section. The band index drives the alternation of the background and of the
12
+ * texture, thus a caller that adds a band passes the next index.
8
13
  */
9
14
  import type { ReferenceLedger } from "../references.js";
10
15
  /**
11
- * Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
12
- * no empty reference frame.
16
+ * Wrap already-escaped markup in one full-bleed band. An even index gives the white background and the dot
17
+ * texture, and an odd index gives the slate background and the grid texture. The noise texture lies over
18
+ * both.
19
+ */
20
+ export declare function renderBand(index: number, inner: string): string;
21
+ /**
22
+ * Wrap the reference list in the final band. An empty ledger gives an empty string, thus the page shows no
23
+ * empty reference band. The index continues the band alternation of the sections.
13
24
  */
14
- export declare function renderReferenceSection(ledger: ReferenceLedger): string;
25
+ export declare function renderReferenceSection(ledger: ReferenceLedger, index: number): string;
15
26
  /**
16
27
  * Assemble the page from the title, the navigation, the main content, and the reference frame. The title
17
28
  * passes through the runtime as text, and the runtime escapes it. The reference frame is optional, thus
@@ -1,16 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
2
  /**
3
- * The page assembly.
3
+ * The page assembly: the hero, the bands, the reference band, and the footer.
4
4
  *
5
- * The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
6
- * The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
7
- * navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
8
- * `raw()` inserts them byte for byte.
5
+ * The skeleton inlines the style rules in the head, and it puts the three scripts at the end of the body.
6
+ * The order of the three scripts is a contract. The theme registration runs before the chart bootstrap,
7
+ * thus each chart finds its theme. The fade-in observer also runs before the chart bootstrap, thus the
8
+ * bootstrap finds the reveal gate and it signals readiness after the first reveal pass. The navigation, the
9
+ * main content, and the reference frame arrive as already-escaped markup strings, thus `raw()` inserts them
10
+ * byte for byte.
11
+ *
12
+ * One band holds one top-level section. The band index drives the alternation of the background and of the
13
+ * texture, thus a caller that adds a band passes the next index.
9
14
  */
10
15
  import { raw } from "hono/html";
11
- import { CDN_HEAD, CHART_BOOTSTRAP, ECHARTS_THEME, ECHARTS_THEME_NAME, PAGE_CSS } from "../page.js";
16
+ import { ASSET_HEAD, CHART_BOOTSTRAP, FADE_IN_OBSERVER } from "../page.js";
17
+ import { DESIGN_CSS, ECHARTS_THEME, ECHARTS_THEME_NAME } from "../design.js";
12
18
  import { renderReferenceList } from "./references-view.js";
13
19
  import { scriptJson } from "../script-json.js";
20
+ /** The constant eyebrow of the hero. The render reads no clock, thus the hero carries no date. */
21
+ const HERO_EYEBROW = "INFLEXA · ANALYSIS REPORT";
22
+ /** The constant note of the footer. */
23
+ const FOOTER_NOTE = "Powered by Inflexa Cortex";
14
24
  /**
15
25
  * The theme object as script-safe JSON. `scriptJson` replaces every `<` with `\u003c`, thus a later edit
16
26
  * that adds a `<` to the theme stays safe inside the script sink. The JSON parser reads `\u003c` as `<`, thus
@@ -28,15 +38,25 @@ const THEME_REGISTRATION = `(function () {
28
38
  echarts.registerTheme(${JSON.stringify(ECHARTS_THEME_NAME)}, ${THEME_JSON});
29
39
  })();`;
30
40
  /**
31
- * Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
32
- * no empty reference frame.
41
+ * Wrap already-escaped markup in one full-bleed band. An even index gives the white background and the dot
42
+ * texture, and an odd index gives the slate background and the grid texture. The noise texture lies over
43
+ * both.
44
+ */
45
+ export function renderBand(index, inner) {
46
+ const surface = index % 2 === 0 ? "report-band-white texture-dots" : "report-band-slate texture-grid";
47
+ return String(_jsx("section", { class: `report-band ${surface} texture-noise`, children: _jsx("div", { class: "report-container fade-in", children: raw(inner) }) }));
48
+ }
49
+ /**
50
+ * Wrap the reference list in the final band. An empty ledger gives an empty string, thus the page shows no
51
+ * empty reference band. The index continues the band alternation of the sections.
33
52
  */
34
- export function renderReferenceSection(ledger) {
53
+ export function renderReferenceSection(ledger, index) {
35
54
  const list = renderReferenceList(ledger);
36
55
  if (list === "") {
37
56
  return "";
38
57
  }
39
- return String(_jsxs("section", { class: "report-references-section mt-16 pt-8 border-t border-slate-200", children: [_jsx("h2", { class: "text-xl font-semibold tracking-tight text-slate-900 mb-4", children: "References" }), raw(list)] }));
58
+ const heading = String(_jsx("h2", { class: "report-heading report-heading-2", children: "References" }));
59
+ return renderBand(index, heading + list);
40
60
  }
41
61
  /**
42
62
  * Assemble the page from the title, the navigation, the main content, and the reference frame. The title
@@ -45,5 +65,5 @@ export function renderReferenceSection(ledger) {
45
65
  */
46
66
  export function assemblePage(title, nav, content, references) {
47
67
  return ("<!doctype html>" +
48
- 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(CDN_HEAD), _jsx("style", { children: raw(PAGE_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsxs("main", { class: "report-main mx-auto max-w-4xl px-6 py-10", children: [_jsx("h1", { class: "report-title text-4xl font-bold tracking-tight text-slate-900 mb-8", children: title }), raw(content), references.length > 0 ? raw(references) : null] }), _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(CHART_BOOTSTRAP) })] })] })));
68
+ 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), _jsx("style", { children: raw(DESIGN_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsx("header", { class: "report-hero texture-noise", children: _jsxs("div", { class: "report-container", 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: _jsxs("div", { class: "report-container report-footer-row", children: [_jsx("span", { class: "report-footer-title", children: title }), _jsx("span", { class: "report-footer-note", children: FOOTER_NOTE })] }) }), _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(FADE_IN_OBSERVER) }), _jsx("script", { children: raw(CHART_BOOTSTRAP) })] })] })));
49
69
  }
@@ -24,5 +24,6 @@ export declare function renderSection(section: SectionBlock, depth: number, chil
24
24
  /**
25
25
  * Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
26
26
  * numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
27
+ * The `report-sidebar` id is the hook of the layout shift on a large viewport.
27
28
  */
28
29
  export declare function renderNav(topSections: SectionBlock[]): string;
@@ -8,8 +8,8 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
8
8
  */
9
9
  import { raw } from "hono/html";
10
10
  import { Marker } from "./references-view.js";
11
- /** The paragraph class of a text block and a claim block. */
12
- const PARAGRAPH_CLASS = "report-paragraph mb-4 leading-relaxed text-slate-600";
11
+ /** The paragraph class of a text block and a claim block. The measure caps the line inside the wide band. */
12
+ const PARAGRAPH_CLASS = "report-prose";
13
13
  /**
14
14
  * Split prose into paragraphs on a blank line. A run of blank lines makes one split. An empty part drops
15
15
  * out, thus a leading or a trailing blank line adds no empty paragraph.
@@ -45,16 +45,9 @@ export function renderClaim(block, ledger) {
45
45
  const last = paragraphs.length - 1;
46
46
  return String(_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
47
47
  }
48
- /** The heading class of a section, by heading level. */
48
+ /** The heading class of a section, by heading level. A heading uses the sans family, never the mono family. */
49
49
  function headingClass(level) {
50
- switch (level) {
51
- case 2:
52
- return "text-3xl font-semibold tracking-tight text-slate-900 mb-4";
53
- case 3:
54
- return "text-2xl font-semibold tracking-tight text-slate-900 mb-3";
55
- default:
56
- return "text-xl font-semibold tracking-tight text-slate-900 mb-2";
57
- }
50
+ return `report-heading report-heading-${level}`;
58
51
  }
59
52
  /**
60
53
  * Render a section wrapper around its rendered children. The heading level is `h2` at depth 0, `h3` at
@@ -64,12 +57,13 @@ function headingClass(level) {
64
57
  export function renderSection(section, depth, childrenHtml) {
65
58
  const level = Math.min(depth + 2, 4);
66
59
  const Heading = `h${level}`;
67
- return String(_jsxs("section", { id: section.id, class: "report-section mb-10", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
60
+ return String(_jsxs("section", { id: section.id, class: "report-section", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
68
61
  }
69
62
  /**
70
63
  * Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
71
64
  * numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
65
+ * The `report-sidebar` id is the hook of the layout shift on a large viewport.
72
66
  */
73
67
  export function renderNav(topSections) {
74
- return String(_jsxs("aside", { id: "report-sidebar", class: "fixed top-0 left-0 z-40 h-screen w-60 flex flex-col border-r border-slate-200 bg-white/95 backdrop-blur", "aria-label": "Report navigation", children: [_jsx("div", { class: "flex items-center gap-2 px-5 py-5 border-b border-slate-200", children: _jsx("span", { class: "font-mono text-sm font-semibold tracking-wider text-primary-500", children: "inflexa" }) }), _jsx("nav", { class: "flex-1 overflow-y-auto py-3", "aria-label": "Report sections", children: topSections.map((section, index) => (_jsxs("a", { href: `#${section.id}`, "data-sidebar-link": "", class: "sidebar-link flex items-center gap-3 px-5 py-2 text-sm text-slate-500 hover:text-slate-900 hover:bg-slate-50 transition-colors", children: [_jsx("span", { class: "font-mono text-[10px] font-semibold tracking-wider text-slate-300 shrink-0 w-4 text-right", children: index + 1 }), _jsx("span", { class: "truncate", children: section.title })] }))) })] }));
68
+ return String(_jsxs("aside", { id: "report-sidebar", class: "report-nav", "aria-label": "Report navigation", children: [_jsx("div", { class: "report-nav-brand", children: _jsx("span", { class: "report-nav-brand-name", children: "inflexa" }) }), _jsx("nav", { class: "report-nav-list", "aria-label": "Report sections", children: topSections.map((section, index) => (_jsxs("a", { href: `#${section.id}`, class: "report-nav-link", children: [_jsx("span", { class: "report-nav-index", children: index + 1 }), _jsx("span", { class: "report-nav-label", children: section.title })] }))) })] }));
75
69
  }
@@ -4,19 +4,19 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
4
4
  * an integer.
5
5
  */
6
6
  export function Marker({ n }) {
7
- return (_jsx("sup", { class: "report-marker font-mono text-[10px] text-primary-500", children: _jsx("a", { href: `#ref-${n}`, children: n }) }));
7
+ return (_jsx("sup", { class: "report-marker", children: _jsx("a", { href: `#ref-${n}`, children: n }) }));
8
8
  }
9
9
  /** The kind label of one reference entry. */
10
10
  function RefKind({ children }) {
11
- return _jsx("span", { class: "report-ref-kind font-mono text-[11px] uppercase tracking-wider text-slate-400", children: children });
11
+ return _jsx("span", { class: "report-ref-kind", children: children });
12
12
  }
13
13
  /** A path in a monospace code span. */
14
14
  function PathCode({ children }) {
15
- return _jsx("code", { class: "font-mono text-primary-700", children: children });
15
+ return _jsx("code", { class: "report-ref-path", children: children });
16
16
  }
17
17
  /** A plain detail such as a locator or an external id. */
18
18
  function Detail({ children }) {
19
- return _jsx("span", { class: "text-slate-500", children: children });
19
+ return _jsx("span", { class: "report-ref-detail", children: children });
20
20
  }
21
21
  /**
22
22
  * Describe a locator as plain text: the column and the one row selector. The refine of the schema admits
@@ -60,5 +60,5 @@ export function renderReferenceList(ledger) {
60
60
  if (entries.length === 0) {
61
61
  return "";
62
62
  }
63
- return String(_jsx("ol", { class: "report-references list-decimal pl-6", children: entries.map((reference, index) => (_jsx("li", { id: `ref-${index + 1}`, class: "report-ref-item mb-2 text-sm text-slate-600", children: _jsx(ReferenceEntry, { reference: reference }) }))) }));
63
+ return String(_jsx("ol", { class: "report-references", children: entries.map((reference, index) => (_jsx("li", { id: `ref-${index + 1}`, class: "report-ref-item", children: _jsx(ReferenceEntry, { reference: reference }) }))) }));
64
64
  }
@@ -4,6 +4,9 @@
4
4
  * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
5
5
  * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
6
6
  * hostile source reaches the page as text.
7
+ *
8
+ * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
9
+ * accents, which is the geometric identity of the report.
7
10
  */
8
11
  import type { CitationBlock, FigureBlock, MetricBlock, TableBlock } from "../../contracts/report-blocks.js";
9
12
  import type { ReferenceLedger } from "../references.js";
@@ -20,8 +23,14 @@ type TableValue = Extract<RenderValue, {
20
23
  type FigureValue = Extract<RenderValue, {
21
24
  type: "figure";
22
25
  }>;
23
- /** Render a metric block as a stat card with the label and the scalar value. */
26
+ /** Render a metric block as a stat card with the mono value and the mono label. */
24
27
  export declare function renderMetric(block: MetricBlock, value: ScalarValue): string;
28
+ /**
29
+ * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
30
+ * markup as one already-escaped string, thus `raw()` inserts it byte for byte. The grid carries its own
31
+ * stagger, thus the run reveals after the band around it.
32
+ */
33
+ export declare function renderMetricGrid(cardsHtml: string): string;
25
34
  /**
26
35
  * Render a table block. The header holds one cell for each column, and the body holds one row for each
27
36
  * resolved row. A zero-row table renders the header alone. The title and the caption render only when the
@@ -29,14 +38,14 @@ export declare function renderMetric(block: MetricBlock, value: ScalarValue): st
29
38
  */
30
39
  export declare function renderTable(block: TableBlock, value: TableValue): string;
31
40
  /**
32
- * Render a figure block. The source rides the `src` attribute, and the caption renders below the image.
33
- * The caption also fills the `alt` attribute, thus the escape keeps a hostile source and a hostile caption
34
- * inside their slots.
41
+ * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
42
+ * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
43
+ * source and a hostile caption inside their slots.
35
44
  */
36
45
  export declare function renderFigure(block: FigureBlock, value: FigureValue): string;
37
46
  /**
38
- * Render a citation block as a short line. The binding joins the ledger like a claim binding, thus one
39
- * shared source keeps one number. The optional note renders after the marker.
47
+ * Render a citation block as a card in the reference form. The binding joins the ledger like a claim
48
+ * binding, thus one shared source keeps one number. The optional note renders after the marker.
40
49
  */
41
50
  export declare function renderCitation(block: CitationBlock, ledger: ReferenceLedger): string;
42
51
  export {};
@@ -1,8 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * The value markup: the metric card, the data table, the figure, and the inline citation.
4
+ *
5
+ * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
6
+ * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
7
+ * hostile source reaches the page as text.
8
+ *
9
+ * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
10
+ * accents, which is the geometric identity of the report.
11
+ */
12
+ import { raw } from "hono/html";
2
13
  import { Marker } from "./references-view.js";
3
- /** Render a metric block as a stat card with the label and the scalar value. */
14
+ /** Render a metric block as a stat card with the mono value and the mono label. */
4
15
  export function renderMetric(block, value) {
5
- return String(_jsxs("div", { class: "corner-accents border border-slate-200 bg-white p-6", children: [_jsx("div", { class: "font-mono text-3xl font-bold", style: "color: #576dea;", children: value.value }), _jsx("div", { class: "font-mono text-xs font-medium text-slate-500 mt-2 uppercase tracking-wider", children: block.label })] }));
16
+ return String(_jsxs("div", { class: "stat-card corner-accents", children: [_jsx("div", { class: "stat-card-value", children: value.value }), _jsx("div", { class: "stat-card-label", children: block.label })] }));
17
+ }
18
+ /**
19
+ * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
20
+ * markup as one already-escaped string, thus `raw()` inserts it byte for byte. The grid carries its own
21
+ * stagger, thus the run reveals after the band around it.
22
+ */
23
+ export function renderMetricGrid(cardsHtml) {
24
+ return String(_jsx("div", { class: "report-metric-grid fade-in", "data-delay": "100", children: raw(cardsHtml) }));
6
25
  }
7
26
  /**
8
27
  * The column order of a table. The value columns win when they are present. The keys of the first row
@@ -22,22 +41,22 @@ function tableColumns(value) {
22
41
  */
23
42
  export function renderTable(block, value) {
24
43
  const columns = tableColumns(value);
25
- return String(_jsxs("div", { class: "report-table mb-8", children: [block.title !== undefined ? (_jsx("div", { class: "font-mono text-xs font-semibold uppercase tracking-widest text-primary-500 mb-3", children: block.title })) : null, _jsx("div", { class: "corner-accents border border-slate-200 bg-white overflow-hidden", children: _jsx("div", { class: "overflow-x-auto", children: _jsxs("table", { class: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { class: "border-b border-slate-200 bg-slate-50", children: columns.map((column) => (_jsx("th", { class: "px-4 py-3 text-left font-mono text-[11px] font-semibold text-slate-500 uppercase tracking-wider", children: column }))) }) }), _jsx("tbody", { children: value.rows.map((row) => (_jsx("tr", { class: "report-row border-b border-slate-100 last:border-b-0", children: columns.map((column) => (_jsx("td", { class: "px-4 py-2.5 text-slate-700", children: row[column] }))) }))) })] }) }) }), block.caption !== undefined ? _jsx("p", { class: "text-sm text-slate-400 mt-2", children: block.caption }) : null] }));
44
+ return String(_jsxs("div", { class: "report-table", children: [block.title !== undefined ? _jsx("div", { class: "report-table-title", children: block.title }) : null, _jsx("div", { class: "corner-accents", children: _jsx("div", { class: "data-table-scroll", children: _jsxs("table", { class: "data-table", children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column) => (_jsx("th", { children: column }))) }) }), _jsx("tbody", { children: value.rows.map((row) => (_jsx("tr", { class: "report-row", children: columns.map((column) => (_jsx("td", { children: row[column] }))) }))) })] }) }) }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
26
45
  }
27
46
  /**
28
- * Render a figure block. The source rides the `src` attribute, and the caption renders below the image.
29
- * The caption also fills the `alt` attribute, thus the escape keeps a hostile source and a hostile caption
30
- * inside their slots.
47
+ * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
48
+ * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
49
+ * source and a hostile caption inside their slots.
31
50
  */
32
51
  export function renderFigure(block, value) {
33
52
  const caption = block.caption;
34
- return String(_jsxs("figure", { class: "report-figure mb-8", children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "max-w-full h-auto border border-slate-200" }), caption !== undefined ? _jsx("figcaption", { class: "text-sm text-slate-400 mt-2", children: caption }) : null] }));
53
+ return String(_jsxs("figure", { class: "report-figure corner-accents", children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "report-figure-image" }), caption !== undefined ? _jsx("figcaption", { class: "report-caption", children: caption }) : null] }));
35
54
  }
36
55
  /**
37
- * Render a citation block as a short line. The binding joins the ledger like a claim binding, thus one
38
- * shared source keeps one number. The optional note renders after the marker.
56
+ * Render a citation block as a card in the reference form. The binding joins the ledger like a claim
57
+ * binding, thus one shared source keeps one number. The optional note renders after the marker.
39
58
  */
40
59
  export function renderCitation(block, ledger) {
41
60
  const n = ledger.mark(block.binding);
42
- return String(_jsxs("p", { class: "report-citation text-sm text-slate-500", children: [_jsx(Marker, { n: n }), block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "text-slate-600", children: block.note })] })) : null] }));
61
+ return String(_jsxs("div", { class: "report-citation corner-accents", children: [_jsx(Marker, { n: n }), block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "report-citation-note", children: block.note })] })) : null] }));
43
62
  }
@@ -30,6 +30,7 @@ import { type CitationResolverConfig } from "../citations/resolve.js";
30
30
  import type { CitationResolver } from "../citations/types.js";
31
31
  import type { AuthContext } from "../auth/types.js";
32
32
  import type { ReferenceResolver } from "../report-model/reference-resolver.js";
33
+ import type { ResolvePageAsset } from "../tools/report-session/index.js";
33
34
  /** Registered child sandbox-step callable the parent's child dispatch closes over. */
34
35
  export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
35
36
  /**
@@ -110,6 +111,15 @@ export interface CoreRuntimeDeps {
110
111
  * factory replaces that wiring, thus `reportHostReadCapBytes` then governs nothing.
111
112
  */
112
113
  readonly makeReportReferenceResolver?: MakeReportReferenceResolver;
114
+ /**
115
+ * The page-asset lookup of the report preview. The preview tool stages the chart
116
+ * runtime and the fonts beside the page, and it names each source by its module
117
+ * specifier. Absent, the tool resolves each specifier against the installation of the
118
+ * harness, which is the npm path and the whole OSS path. An embedder that ships the
119
+ * bytes packed, for example a compiled single-file binary with no `node_modules` tree,
120
+ * materializes them to disk and binds its own lookup here.
121
+ */
122
+ readonly resolveReportPageAsset?: ResolvePageAsset;
113
123
  }
114
124
  /**
115
125
  * The refusal a thread type carries no agent yet. `report` is a valid
@@ -122,6 +122,7 @@ export function assembleCoreRuntime(deps) {
122
122
  threads: reportThreads,
123
123
  chrome: conversation.chrome,
124
124
  makeResolver: makeReportResolver,
125
+ ...(deps.resolveReportPageAsset ? { resolvePageAsset: deps.resolveReportPageAsset } : {}),
125
126
  ...(conversation.logger ? { logger: conversation.logger } : {}),
126
127
  });
127
128
  // The single registration point for every typed agent. A future thread type's
@@ -32,6 +32,11 @@ const MANAGED_BY_VALUE = "cortex";
32
32
  const OWNER_WORKFLOW_LABEL = "cortex/owner-workflow-id";
33
33
  const RUN_ID_LABEL = "cortex/run-id";
34
34
  const STEP_ID_LABEL = "cortex/step-id";
35
+ // OpenCost sanitizes `/` and `-` to `_`, so these arrive at the metering
36
+ // reconciler as cortex_billing_context / cortex_user_id / cortex_analysis_id.
37
+ const BILLING_CONTEXT_LABEL = "cortex/billing-context";
38
+ const USER_ID_LABEL = "cortex/user-id";
39
+ const ANALYSIS_ID_LABEL = "cortex/analysis-id";
35
40
  /**
36
41
  * Coerce an arbitrary identifier into a valid K8s label value:
37
42
  * ≤63 chars, `[A-Za-z0-9._-]` only, starting and ending alphanumeric.
@@ -214,6 +219,17 @@ function buildJobSpec(meta, config, identity) {
214
219
  podSpec.tolerations = config.tolerations;
215
220
  if (config.runtimeClassName)
216
221
  podSpec.runtimeClassName = config.runtimeClassName;
222
+ // Pod-template placement is load-bearing: the OpenCost reconciler allocates
223
+ // by pod labels and filters on a non-empty cortex_billing_context — a
224
+ // Job-only label is invisible to compute metering.
225
+ const billingLabels = meta.billing
226
+ ? {
227
+ [BILLING_CONTEXT_LABEL]: sanitizeLabelValue(meta.billing.billingContextId),
228
+ [USER_ID_LABEL]: sanitizeLabelValue(meta.billing.userId),
229
+ [ANALYSIS_ID_LABEL]: sanitizeLabelValue(meta.analysisId),
230
+ [RUN_ID_LABEL]: sanitizeLabelValue(meta.runId),
231
+ }
232
+ : {};
217
233
  return {
218
234
  apiVersion: "batch/v1",
219
235
  kind: "Job",
@@ -227,6 +243,7 @@ function buildJobSpec(meta, config, identity) {
227
243
  [OWNER_WORKFLOW_LABEL]: sanitizeLabelValue(meta.childWorkflowId),
228
244
  [RUN_ID_LABEL]: sanitizeLabelValue(meta.runId),
229
245
  [STEP_ID_LABEL]: sanitizeLabelValue(meta.stepId),
246
+ ...billingLabels,
230
247
  },
231
248
  },
232
249
  spec: {
@@ -237,6 +254,7 @@ function buildJobSpec(meta, config, identity) {
237
254
  labels: {
238
255
  role: "sandbox",
239
256
  "cortex/sandbox-id": sandboxId,
257
+ ...billingLabels,
240
258
  },
241
259
  },
242
260
  spec: podSpec,
@@ -243,6 +243,12 @@ export interface CreateSandboxMeta {
243
243
  /** Enforced read-only: provision with no read-write step mount, only the
244
244
  * read-only analysis tree for generic read-only agents. */
245
245
  readOnly?: boolean;
246
+ /** Billing attribution stamped as pod labels for OpenCost compute metering.
247
+ * Absent ⇒ the pod is invisible to the metering reconciler (filter-level). */
248
+ billing?: {
249
+ billingContextId: string;
250
+ userId: string;
251
+ };
246
252
  }
247
253
  /**
248
254
  * The identity minted for a sandbox machine *before* it is spawned — the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `cortex_analysis_state` operations — analysis lifecycle and billing context.
2
+ * `cortex_analysis_state` operations — analysis lifecycle.
3
3
  */
4
4
  import type { ResultAsync } from "neverthrow";
5
5
  import { type DbError } from "../lib/db-result.js";
@@ -9,20 +9,12 @@ import type { Querier } from "./db.js";
9
9
  *
10
10
  * The seed endpoint is called repeatedly; this function handles both the
11
11
  * initial INSERT and subsequent UPDATEs by writing ALL mutable fields on
12
- * every call. Re-upserts replace `context` and `billing_context` wholesale.
12
+ * every call. Re-upserts replace `context` wholesale.
13
13
  *
14
14
  * User identity is derived from the ambient credential's JWT `sub` claim
15
15
  * at request time — not persisted in this table.
16
- *
17
- * INTENTIONAL: split-attribution across a single run.
18
- * If the seed re-upserts mid-workflow (e.g., the billing context's VK rotates),
19
- * in-flight steps that have already issued LLM calls keep the old attribution
20
- * on those calls, while subsequent steps in the same run pick up the new
21
- * identity on their next `billedAgent` resolution. This is by design —
22
- * usage rows contain full per-call attribution, so spend is
23
- * always assignable even when it crosses a rotation boundary.
24
16
  */
25
- export declare function upsertAnalysis(pool: Querier, resourceId: string, context: string | null, billingContext: Record<string, string> | null, inputFileIds?: string[]): ResultAsync<void, DbError>;
17
+ export declare function upsertAnalysis(pool: Querier, resourceId: string, context: string | null, inputFileIds?: string[]): ResultAsync<void, DbError>;
26
18
  export declare function loadAnalysisStatus(pool: Querier, resourceId: string): ResultAsync<{
27
19
  status: string;
28
20
  context: string | null;
@@ -41,18 +33,3 @@ export declare function suspendAnalysis(pool: Querier, analysisId: string): Resu
41
33
  * Only transitions from `suspended_insufficient_funds` → `active`.
42
34
  */
43
35
  export declare function resumeAnalysis(pool: Querier, analysisId: string): ResultAsync<void, DbError>;
44
- /**
45
- * Resolve the persisted billing context for an analysis.
46
- *
47
- * Single DB-read chokepoint used by `requireAnalysisBilling` middleware and
48
- * by any downstream code that needs to re-read billing identity. The driver
49
- * read is wrapped as a `DbError`; the missing-row / null-billing /
50
- * unparseable-JSON cases are control-flow throws (a misconfiguration, not a
51
- * driver failure) that short-circuit the chain and surface verbatim.
52
- *
53
- * User identity (`HEADERS.User`) is derived from the ambient credential's
54
- * JWT `sub` claim by `credential-middleware`, not from the DB.
55
- */
56
- export declare function resolveAnalysisBilling(pool: Querier, analysisId: string): ResultAsync<{
57
- billingContext: Record<string, string>;
58
- }, DbError>;