Imported from verticalbarHQ/verticalbar-agent (
packages/anthropic-hosted/skills/briefing/SKILL.md). Install upstream withnpx skills add verticalbarHQ/verticalbar-agent --skill briefing. Copyright stays with the author.
Briefing — author & publish an analysis surface
Turn a user's analysis goal into a self-contained interactive HTML Briefing, grounded in real data, and publish it to the dashboard's Briefing surface (/[org]/[ws]/briefings).
Domain-agnostic. A Briefing is the SURFACE + the free-authoring, NOT a fixed report type. The same skill produces a close diagnostic, an order-to-cash process map, an AR-aging view, a spend anomaly brief — whatever the goal is. Do not couple the skill to any one domain.
The agent owns judgment + visualization; the platform owns only the thin sandboxed render surface (
<iframe sandbox="allow-scripts">). Quality bar: bespoke visuals by composition, never a templated catalog. Separation: data acquisition/detection is deterministic (every figure traces to a query); authoring is free.
Method — any analysis, any data
- Inspect
runtime_info, then resolve authorized scope. On the hosted remote surface, the MCP request already carries verified identity: do not look forlogin, a local browser, a token cache, or a window. On an installed local surface, useloginonly whenruntime_inforeports that Cognito identity is absent and the reported runtime supports login. In every case, discover workspace scope withcc_workspaces; no environment value or remembered workspace is authority. - Understand the goal, then CHOOSE the data the question needs. Acquire it ONLY through CrossCheck's authenticated, read-only HTTP GET proxies — the platform runs the read server-side and returns JSON; the skill never touches a database or NetSuite directly, never holds DB credentials, never issues raw SQL against a database:
- the live read proxy (
/api/v1/live-read) — SuiteQL-as-a-GET over live NetSuite, for fresh reads; - the landed-data read proxy (
/api/v1/briefings-data) — the same shape over CrossCheck's landed close event-store (read-only, workspace-scoped; "psql-as-a-GET"); - CrossCheck read APIs (snapshots / customizations / dependencies / employees) and Vertical Bar process data via their read surfaces. All reads ride the existing CrossCheck / Vertical Bar Cognito session. The skill is handed a thin GET surface, never DB credentials. The Briefing surface owns authoring + publish, NOT data ownership.
- the live read proxy (
- Acquire read-only, coverage-first + ground. Pull what's actually present for the account; state gaps honestly (never silently drop a finding); every figure carries provenance (its source table/query).
- Assemble a grounded bundle =
{ goal, account, generated_at, coverage{available,missing}, facts: {<key>:{value, unit?, provenance}}, series, findings }. - Free-author a self-contained interactive HTML. Inject the grounded data inline and read every authoritative figure FROM it — never free-type a number. But this is an internal authoring discipline: NEVER surface the mechanism in the rendered output — no
window.__BRIEFING_DATA__, no "references the bundle", no data-block plumbing text. The only user-facing provenance is a per-figure ⓘ tooltip citing the source/detector (e.g. "Detector B4 — systemnote × employees"); the implementation stays invisible. Product chrome (headers, cards, layout, labels) uses CrossCheck--cc-*design tokens — no hex on chrome; data-encoding palettes (chart colors) are free. Charts via the vendored viz runtime, auto-inlined. Put the single marker<!--BRIEFING_VIZ-->in<head>and author charts against the globalwindow.d3(aliaswindow.BriefingViz) — a curated, eval-free d3 toolkit (scales, color, shape, axes, force, drag, zoom, hierarchy, sankey).briefing_publishsplices the ~200KB runtime in at the marker, so you NEVER paste the library yourself; without the marker no runtime is added. The sandbox blocks the network, so this inline path is the only one — do not reference a CDN or<script src>. Use it for flow diagrams (d3.sankey); hand-rolled SVG/CSS stays fine for simple KPI/bar. For clean, non-overlapping flowcharts / decision trees / dependency graphs, preferbriefing_layout(give it mermaid text or a generic{nodes,edges}model → embed the returned snippet) over hand-rollingd3.forceSimulation. Compose freely (KPI grid, charts, tables, process/flow diagrams, findings, honest caveats) — bespoke over a fixed catalog. - Self-review (output-blind), then publish — don't screenshot into the chat. Re-read the assembled HTML against the Acceptance checklist below (every figure cited, ≥1 bespoke visual, no duplicate findings/raw dumps, on-brand
--cc-*chrome, ≥1 process/flow diagram where the domain has process data) and fix what fails. Do NOT spin up headless Chrome / puppeteer to render the artifact to a PNG and surface it in the chat — that is not the deliverable; the publishedviewUrlis where the human sees the real render. A capable installed runtime may open that URL as a best-effort convenience; a hosted runtime returns it for the agent to surface. A pre-publish render is opt-in only — reserve it for a layout you have a specific reason to doubt, and even then keep it to yourself. - Publish — the DEFAULT terminal step (opt-out only). The end of every analysis is
briefing_publish({title, account, html, workspaceId})→{ id, viewUrl, rawUrl, workspace }; do this unless the user explicitly said not to. When re-publishing an iteration of an earlier analysis, pass a stabletopickey (an optional string) so the platform stacks the new artifact as the next version of that same topic instead of a fresh, unrelated briefing (RND-3056); an optionalnotecan describe what changed in this version. Pass the SAMEworkspaceIdyou chose in scope discovery; no ambient publish scope exists. The result echoes the resolvedworkspacewhen available — confirm it is the workspace the goal targeted before surfacing the link.viewUrlis the human dashboard page/[org]/[ws]/briefings/<id>; the installed runtime may auto-open it, while a hosted runtime returns it without attempting local UI.rawUrlis the auth-gated raw artifact. Surface theviewUrlto the user as the deliverable. The artifact carries its own theme (the sandbox can't read the parent's).
Data paths that enrich results (easy to miss)
Call briefing_schema FIRST — it is the runtime source of the specific data-path map (which landed
table, which column, which cross-product join key) for the account you are analyzing. It returns
{ tables, joinGraph, dataPaths, detectors }; wire the ones the goal needs (not a prescription). Keep
these path-shaped reminders in mind and let briefing_schema supply the exact table/column/key names:
- Actor ids are LANDED — don't default to a name-only join. Resolve a process actor to a
CrossCheck employee reference-first (the landed actor/creator keys are in
briefing_schema.dataPaths→actor-identity); integrations resolve by name viacc_employees. A livesystemnoteread (cc_live_read) is the narrow fallback ONLY for a not-yet-landed human status-changer. - There is a 2nd cross-product bridge beyond the transaction id — see
briefing_schema.joinGraph. - Landed close tables are environment-scoped — group by environment for multi-env work
(subsidiary ≠ environment);
briefing_schemagives the exact keying. $amounts are coverage-gated — posting txns carry GL; non-posting Sales Orders have none. State the gap; never invent a figure. (briefing_schema.dataPaths→amount-coverage.)- Live-read is budget 1/NetSuite-account, fail-closed — prefer landed data; never parallel-run the same account.
Tools (the current agent MCP)
The installed plugin and hosted remote MCP expose the same workspace-explicit analysis tools. Call
runtime_info instead of inferring which surface is active. Reads are HTTP proxies the platform
runs server-side; the skill never holds DB/NetSuite credentials and never issues SQL against a
database directly.
login({ email?, password? })— installed surfaces only. Ifruntime_inforeports a local surface with no Cognito identity, no args opens browser OAuth; explicit email+password is the Node SRP compatibility path. The hosted remote surface has verified request identity and deliberately exposes nologinorlogout.whoamishows current auth and workspace routing mode.briefing_data_query({ sql, limit?, environmentId? })— read-onlySELECT/WITHover the platform's landed analytical stores ("psql-as-a-GET"), and the ONLY data-query tool. The platform runs it server-side, workspace-scoped, and returns{ rows, rowCount, truncated, source }. WHICH store answers is decided server-side from the relations you name (names viabriefing_schema); a single statement cannot span two stores. PassenvironmentIdwhen the workspace has more than one and the statement reads process data. This is NOT a database connection — it is a guarded read proxy, and a refusal is never an empty result.cc_live_read({ sql, limit? })— read-only SuiteQLSELECTagainst live NetSuite, proxied through CrossCheck. Use for fresh reads the landed store does not have.briefing_layout({ mermaid? | model?, opts? })→{ snippet, summary }— compute a deterministic, non-overlapping graph/flow layout at publish time (dagre, in Node) and get back a ready-to-embed HTMLsnippet(the artifact ships no layout engine — only coordinates + the bundled renderer). Provide EXACTLY ONE of:mermaid(flowchart subset —graph/flowchart TD|LR; shapes[] () ([]) {} (()); edges--> --- -.-> ==>+-->|label|; chains) ORmodel(generic{ nodes:[{id,label?,shape?}], edges:[{from,to,label?,style?}] }). Data-agnostic — you map ANY analysis (dependencies, process steps, decision trees, …) into the generic model yourself. For a swimlane (lane-banded flow by actor / phase / type — e.g. SYS·AR·AP·ACCT), passopts:{layout:'swimlane', lanes:[{id,label?,color?}], laneAxis?:'row'|'col'}and alaneon every node (model-direct; mermaid stays flow-only). Embed the returnedsnippetverbatim in your<body>and keep<!--BRIEFING_VIZ-->in<head>. ELK is reserved and fails loud.briefing_publish({ title, account?, html, topic?, note? })→{ id, viewUrl }— publish the self-contained artifact to the in-product Briefing surface. Pass a stabletopicto stack this as the next version of the same topic (RND-3056).briefing_listlists published Briefings (metadata only).cc_*— governed CrossCheck tools. The analysis surface remains read-only:cc_workspaces,cc_list_snapshots,cc_get_snapshot,cc_list_customizations,cc_get_suitescript_source,cc_dependencies,cc_dependency_summary,cc_dependency_chain,cc_dependency_paths,cc_dependency_graph,cc_dependency_graph_status,cc_impact_analysis,cc_employees,cc_script_telemetry. The narrow deployment-mutation exception is described below.vb_*— Vertical Bar process mining, all FIXED-SHAPE (Cognito only):vb_workspaces,vb_projects,vb_process_overview,vb_variants,vb_cases,vb_episode_variants. There is novb_data_query— the flexible read-only SQL path over process data isbriefing_data_query, which reaches it through CrossCheck; see the data-path note below.vb_episode_variants({ workspaceId, primary_types, time_range, anchor?, max_hops?, max_variants?, max_edges?, max_exceptions?, max_primary_objects?, statement_timeout_seconds?, poll_interval_ms?, timeout_ms? })wraps the async PAclose.getVariantsjob API for transaction-type episode summaries. It requires an explicitworkspaceIdfromvb_workspaces; no ambient scope fallback exists. It sends a workspace-scoped deterministic idempotency key, polls only the returned relative/insights/close-analytics/jobs/{job_id}path, and returns boundedsummary,variants, aggregated topedges,exceptions,provenance, counts/caps/truncation metadata, workspace echo, and a limitation string. Treat it as episode-summary evidence only: it is not full process-map network parity, not a case timeline/list or edge drilldown, not a close attestation, and not a backend capacity fix (network parity is RND-2800; backend capacity hardening is RND-2801).
Deployment tool pack (RND-2907)
Nine workspace-scoped tools cover the governed deployment walkthrough. Reads:
cc_list_ci_workflows, cc_get_ci_workflow, cc_get_env_snapshot_git_source,
cc_list_release_packages, cc_get_release_package, cc_get_ci_workflow_run. Mutations:
cc_create_release_package, cc_add_release_package_items, cc_start_ci_workflow_run.
All nine use the same routing as the other CrossCheck tools: pass the workspaceId returned by
cc_workspaces. A Cognito user must have the server-authorized scopes for each operation.
Each mutation is a single direct server call — there is no client-side confirm/preflight; the
server enforces its own guards and its response (including any error) is surfaced verbatim. Start-run
requires an identified Cognito user. Demo order: establish identity only when runtime_info says the
installed runtime needs it → resolve the snapshot Git source → create package →
add items → get package/closure → start run → poll run. The full start→observe flow (strict
all-target Review, staged A→B) needs PR #833 in the target deployment; before that, start-run
returns RUN_ENGINE_NOT_READY (503). Approval is intentionally absent and stays in the web UI.
Scope discovery — do this FIRST, never hardcode a workspace. Call cc_workspaces (and
whoami) to enumerate the workspaces authorized by the Cognito identity. Use the sole result
automatically. If more than one is returned and the user's request does not already identify one,
ask the user to choose by safe name; never pick the first or guess. Thread that returned
workspaceId to BOTH every data tool AND briefing_publish — analyze and publish into the same
workspace. No ambient workspace scope exists. Confirm the workspace the publish result echoes back
matches the target. A WORKSPACE_SCOPE_REQUIRED refusal means perform this discovery and retry the
original tool once. Use cc_live_read (the governed proxy — rate-limited, audited, row-capped)
for live reads; it is the only live-NetSuite path.
CrossCheck × Vertical Bar (data path, not a prescribed analysis). The two products mine the same NetSuite transactions: CrossCheck carries state / structure / outcome (config, close-state, GL, lineage, what changed), Vertical Bar carries process (how a transaction flowed — activities, throughput, rework loops). They join on the NetSuite transaction internalId, transaction-scoped (a period-close event itself has no VB case). The exact join keys are table-specific and easy to get wrong — get them from briefing_schema.joinGraph, don't guess (there is also a 2nd bridge for audit-trail events). Reach the process side two ways: the fixed vb_* endpoints (workspace-scoped map / variants / cases — they take a backboneType, not a projectId), or briefing_data_query = open read-only SQL, which serves the whole process corpus through CrossCheck — the event log, the backbone transaction registry (so you can scope a question to one document type) and the transition relation with per-step durations (for bottlenecks, rework and throughput). It carries all transaction object types, not just Sales Order, and those three join to each other in a single statement. briefing_data_query is the single data-query tool: the platform picks which store answers from the relations you name, so a single statement can never span the two stores — run one per store and join the results yourself. Process data is published per environment: pass environmentId when the workspace has more than one, and treat an explicit refusal as "nothing is published here", never as "no process data exists". Coverage is bounded to VB's ingest window (∩ the CrossCheck window); when a join returns 0 rows, distinguish out-of-window from no-relationship. Survey BOTH products in scope discovery before concluding a goal is single-product.
Example analyses (illustrative — NOT the skill's scope)
The method above is identical for every domain; these are just example detector sets a goal might use:
- Close health — period-state lag/blitz, owner-identity-join (systemnote actor ×
employees), task rework, late-JE timing, GL trial-balance; + VB process where relevant. - Order-to-cash — VB process states/variants/durations/bottlenecks (the VB transaction-status field; see
briefing_schema) + CC GL trial-balance. - AR aging / spend / anomaly / config-impact / lineage / … — pick the data + detectors the question demands.
Domain-specific detector queries are reference content, never the skill's identity. Add new domains by adding data/detectors, not by forking the skill.
Acceptance — the human pixel gate (output-blind)
- every authoritative figure correct + cited — zero "evidence unavailable"; 2. ≥1 bespoke visual a fixed catalog could not express; 3. no duplicate findings / raw dumps; 4. on-brand
--cc-*chrome; 5. ≥1 data-grounded process/flow diagram where the domain has process data. A prior PoC is a yardstick, never a template (don't copy its composition).