@datarails-ai/dr-appspace-cli 0.10.13 → 0.10.14

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 (2) hide show
  1. package/dist/cli.js +100 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "@datarails-ai/dr-appspace-cli",
16
- version: "0.10.13",
16
+ version: "0.10.14",
17
17
  description: "Datarails App Space CLI \u2014 build, try, push and publish App Space apps",
18
18
  type: "module",
19
19
  engines: {
@@ -57,7 +57,7 @@ var package_default = {
57
57
  };
58
58
 
59
59
  // src/buildInfo.ts
60
- var BUILD_TIME = "2026-10-01T08:37:09.841Z";
60
+ var BUILD_TIME = "2026-10-01T09:25:02.651Z";
61
61
  function formatBuildTime(iso) {
62
62
  if (iso === "") return "";
63
63
  const d = new Date(iso);
@@ -14566,6 +14566,92 @@ function originOf(row) {
14566
14566
  function sourcesOf(contract) {
14567
14567
  return isPlainObject7(contract?.data?.sources) ? contract.data.sources : {};
14568
14568
  }
14569
+ var FINANCEOS_SCOPES = {
14570
+ commands: {
14571
+ "status:read": ["status", "whoami"],
14572
+ "tables:read": ["list_tables", "get_table"],
14573
+ "semantic:read": ["list_semantic_tables", "get_semantic_table", "metric_series"],
14574
+ "metrics:read": ["list_metrics", "get_metric", "metric_series"],
14575
+ "filebox:read": ["list_filebox"],
14576
+ "users:read": ["list_users"],
14577
+ "pipeline:read": ["data_readiness"],
14578
+ "raw:read": ["get_raw_table", "get_raw_table_data", "get_aliased_table_data"]
14579
+ },
14580
+ namespaces: {
14581
+ "status:read": ["status"],
14582
+ "tables:read": ["tables"],
14583
+ "semantic:read": ["semantic_tables"],
14584
+ "metrics:read": ["metrics", "data_models"],
14585
+ "filebox:read": ["filebox"]
14586
+ },
14587
+ gated: {
14588
+ get_raw_table: "raw:read",
14589
+ get_raw_table_data: "raw:read",
14590
+ get_aliased_table_data: "raw:read"
14591
+ }
14592
+ };
14593
+ function scopesGranting(kind, name) {
14594
+ return Object.keys(FINANCEOS_SCOPES[kind]).filter((scope) => FINANCEOS_SCOPES[kind][scope].includes(name)).sort();
14595
+ }
14596
+ function financeosNeeds(row, origin) {
14597
+ const needs = [];
14598
+ const need = (reads, anyOf, catalog = false) => {
14599
+ if (anyOf.length > 0 && !needs.some((one) => one.reads === reads && one.catalog === catalog)) needs.push({ reads, anyOf, catalog });
14600
+ };
14601
+ if (origin === "call") {
14602
+ const parts = typeof row.from.call === "string" ? row.from.call.split(".") : [];
14603
+ if (parts.length === 1) need(parts[0], scopesGranting("commands", parts[0]));
14604
+ if (parts.length === 2) {
14605
+ need(parts[0], scopesGranting("namespaces", parts[0]));
14606
+ const gated = FINANCEOS_SCOPES.gated[parts[1]];
14607
+ if (gated) need(`${parts[0]}.${parts[1]}`, [gated]);
14608
+ }
14609
+ return needs;
14610
+ }
14611
+ const read = isPlainObject7(row.from.financeos) ? row.from.financeos : {};
14612
+ const filterable = Array.isArray(row.filterable) && row.filterable.length > 0;
14613
+ if (read.metric_id !== void 0 && read.metric_id !== null) {
14614
+ if (read.drill_rows) need("data_models", scopesGranting("namespaces", "data_models"));
14615
+ else need("metric_series", scopesGranting("commands", "metric_series"));
14616
+ if (filterable) need("data_models", scopesGranting("namespaces", "data_models"), true);
14617
+ return needs;
14618
+ }
14619
+ const address = read.address === "id" || read.address === "alias" ? read.address : read.table_alias ? "alias" : read.table_id !== void 0 && read.table_id !== null ? "id" : null;
14620
+ if (address === null) return needs;
14621
+ const raw = read.rows === "raw" || row.shape === "ohlc";
14622
+ if (address === "id") need("get_raw_table", scopesGranting("commands", "get_raw_table"));
14623
+ if (raw) {
14624
+ const page = address === "id" ? "get_raw_table_data" : "get_aliased_table_data";
14625
+ need(page, scopesGranting("commands", page));
14626
+ } else {
14627
+ need("data_models", scopesGranting("namespaces", "data_models"));
14628
+ }
14629
+ if (filterable) need("data_models", scopesGranting("namespaces", "data_models"), true);
14630
+ return needs;
14631
+ }
14632
+ function financeosScopeProblems(name, row, origin, declared) {
14633
+ const missing = /* @__PURE__ */ new Map();
14634
+ for (const { reads, anyOf, catalog } of financeosNeeds(row, origin)) {
14635
+ if (anyOf.some((scope2) => declared.includes(scope2))) continue;
14636
+ const scope = anyOf.includes("metrics:read") ? "metrics:read" : anyOf[0];
14637
+ const entry = missing.get(scope) ?? { scope, anyOf, reads: [], catalog: true };
14638
+ if (!entry.reads.includes(reads)) entry.reads.push(reads);
14639
+ entry.catalog = entry.catalog && catalog;
14640
+ missing.set(scope, entry);
14641
+ }
14642
+ return [...missing.values()].map(
14643
+ ({ scope, anyOf, reads, catalog }) => catalog ? {
14644
+ code: "financeos_scope_catalog",
14645
+ pointer: `/data/sources/${escapePointer3(name)}/filterable`,
14646
+ message: `source "${name}" offers filterable values, which the filter bar reads from ${reads.join(" and ")} with financeos scope ${anyOf.join(" or ")}; the source loads without it, but its filter bar offers no values until you add ${JSON.stringify(scope)} to app.capabilities.financeos`,
14647
+ advisory: true
14648
+ } : {
14649
+ code: "financeos_scope",
14650
+ pointer: "/app/capabilities/financeos",
14651
+ message: `source "${name}" reads ${reads.join(" and ")}, which needs financeos scope ${anyOf.join(" or ")}; add ${JSON.stringify(scope)} to app.capabilities.financeos`
14652
+ }
14653
+ );
14654
+ }
14569
14655
  function servedSources(elements, sources) {
14570
14656
  const out = /* @__PURE__ */ new Set();
14571
14657
  for (const element of Object.values(elements)) {
@@ -14606,6 +14692,8 @@ function sourceProblems(sources, app, handlers, extra) {
14606
14692
  pointer: "/app/capabilities/financeos",
14607
14693
  message: `source "${name}" reads FinanceOS, and this app grants no financeos capability; grant at least one read scope (e.g. metrics:read, plus raw:read to read a table by id)`
14608
14694
  });
14695
+ } else if (origin === "financeos" || origin === "call") {
14696
+ problems.push(...financeosScopeProblems(name, row, origin, financeos));
14609
14697
  }
14610
14698
  if (origin === "depends") {
14611
14699
  for (const [index, dep] of row.from.entries()) {
@@ -15124,7 +15212,8 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
15124
15212
  const handlers = isPlainObject7(contract.data?.app?.handlers) ? contract.data.app.handlers : {};
15125
15213
  const names = Object.keys(handlers).sort(natural3);
15126
15214
  const stated2 = sourcesOf(contract);
15127
- problems.push(...sourceProblems(stated2, app, handlers, extra));
15215
+ const sourceFindings = sourceProblems(stated2, app, handlers, extra);
15216
+ problems.push(...sourceFindings.filter((one) => !one.advisory));
15128
15217
  problems.push(...compareProblems(stated2, model));
15129
15218
  problems.push(...paramProblems(contract, stated2));
15130
15219
  problems.push(...themeProblems(app));
@@ -15208,7 +15297,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
15208
15297
  problems.push(...numberPinProblems(declared, { "dr-app-runtime": runtime, "dr-app-widgets": appWidgets, "dr-components": appComponents, "dr-components-controls": appControls }));
15209
15298
  problems.push(...whenPinProblems(declared, { "dr-components": appComponents }));
15210
15299
  const widgetFindings = tree.problems.length === 0 ? widgetProblems(declared, tree, handlers, contract?.data?.sources, appWidgets) : [];
15211
- const warnings = widgetFindings.filter((one) => one.advisory).map(({ advisory: _advisory, ...one }) => one);
15300
+ const warnings = [...sourceFindings, ...widgetFindings].filter((one) => one.advisory).map(({ advisory: _advisory, ...one }) => one);
15212
15301
  problems.push(...widgetFindings.filter((one) => !one.advisory));
15213
15302
  if (tree.problems.length === 0) problems.push(...partProblems(declared, tree, handlers, sources, contract?.pages, model));
15214
15303
  if (problems.length > 0) return { files: {}, problems, contract, warnings };
@@ -24469,6 +24558,8 @@ var REFUSALS = /* @__PURE__ */ new Set([
24469
24558
  "source_unknown",
24470
24559
  "source_name_taken",
24471
24560
  "source_needs_financeos",
24561
+ // a source the declared financeos scopes do not reach (conversation 34b3d658)
24562
+ "financeos_scope",
24472
24563
  "transform_missing",
24473
24564
  // a dashboard dataset row's own transform (transforms.mjs)
24474
24565
  "transform_row",
@@ -24535,6 +24626,9 @@ var PARAM_REFUSALS = {
24535
24626
  param_kind_value: "a param's default, min/max/step, values or max_selected is one its kind cannot hold",
24536
24627
  param_conflict: "one param name declared twice with different rules (the server keeps the first)"
24537
24628
  };
24629
+ var SOURCE_REFUSALS = {
24630
+ financeos_scope: "a FinanceOS source reads a surface app.capabilities.financeos does not grant (a table aggregate or a metric needs metrics:read; a by-id table or raw rows need raw:read; a call needs the scope its command is under) \u2014 add the scope the message names. `filterable` values without metrics:read are only a warning (financeos_scope_catalog): the source loads, its filter bar offers nothing"
24631
+ };
24538
24632
  var KPI_REFUSALS = {
24539
24633
  source_compare_field: "a source's compare names a field its rows do not carry (where that is knowable: a FinanceOS aggregate's dimensions, measures and period column; an app table's columns), or is set on a source not shaped kpi \u2014 declare the measure beside the figure, or name one it carries"
24540
24634
  };
@@ -25588,6 +25682,8 @@ function registerCompileCommand(parent) {
25588
25682
  "",
25589
25683
  "An app's own header is refused with:",
25590
25684
  ...Object.entries(APP_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
25685
+ "A FinanceOS source the declared scopes do not reach is refused with:",
25686
+ ...Object.entries(SOURCE_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
25591
25687
  "A KPI source (shape kpi: its period comes from the rows; compare sets the figure against another measure of the same row) is refused with:",
25592
25688
  ...Object.entries(KPI_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
25593
25689
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datarails-ai/dr-appspace-cli",
3
- "version": "0.10.13",
3
+ "version": "0.10.14",
4
4
  "description": "Datarails App Space CLI — build, try, push and publish App Space apps",
5
5
  "type": "module",
6
6
  "engines": {