@malloy-publisher/server 0.0.250 → 0.2.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.
- package/dist/app/api-doc.yaml +230 -35
- package/dist/app/assets/{EnvironmentPage-CiuWaYTk.js → EnvironmentPage-DiUgLuCH.js} +1 -1
- package/dist/app/assets/{HomePage-ZXsmEAjw.js → HomePage-MhhjhnFj.js} +1 -1
- package/dist/app/assets/{LightMode-10iLdX5H.js → LightMode-BEyvNUzT.js} +1 -1
- package/dist/app/assets/{MainPage-u-IAaBdE.js → MainPage-D4Cfee9o.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-AcoL_Rnx.js → MaterializationsPage-nGgGqwSN.js} +1 -1
- package/dist/app/assets/{ModelPage-BjB-p4Qn.js → ModelPage-DLAcEw-F.js} +1 -1
- package/dist/app/assets/{PackagePage-le809jUO.js → PackagePage-Df0ikH7w.js} +1 -1
- package/dist/app/assets/{RouteError-DTlqmNQd.js → RouteError-BFQwtroH.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DrmSzweH.js → ThemeEditorPage-OoskoQYN.js} +1 -1
- package/dist/app/assets/{WorkbookPage-CIRFZXlP.js → WorkbookPage-CpFgvUDC.js} +1 -1
- package/dist/app/assets/{core-Cr-9Wkp3.es-YhIv13au.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
- package/dist/app/assets/{index-CM5d1afN.js → index-6ypuFH6g.js} +435 -436
- package/dist/app/assets/{index-DP4bilgp.js → index-B8pnU_V_.js} +4 -4
- package/dist/app/assets/{index-CSFSsus4.js → index-BImcHmVP.js} +1 -1
- package/dist/app/assets/{index-DzaYbhnD.js → index-DSoCUF-f.js} +438 -439
- package/dist/app/assets/{index-DdUOWgW6.js → index-DfqzSItA.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +237 -392
- package/dist/server.mjs +3992 -3120
- package/package.json +15 -14
|
@@ -2482,9 +2482,11 @@ var init_errors = __esm(() => {
|
|
|
2482
2482
|
}
|
|
2483
2483
|
};
|
|
2484
2484
|
MaterializationEligibilityError = class MaterializationEligibilityError extends Error {
|
|
2485
|
+
reason;
|
|
2485
2486
|
constructor(error) {
|
|
2486
2487
|
super(error.message);
|
|
2487
2488
|
this.name = "MaterializationEligibilityError";
|
|
2489
|
+
this.reason = error.reason;
|
|
2488
2490
|
}
|
|
2489
2491
|
};
|
|
2490
2492
|
FrozenConfigError = class FrozenConfigError extends Error {
|
|
@@ -2584,7 +2586,7 @@ function assertNoAuthorizeNearMisses(found) {
|
|
|
2584
2586
|
message: `These annotations are not \`authorize\` gates and nothing enforces ` + `them:
|
|
2585
2587
|
${unique.map((t) => ` - \`${t}\``).join(`
|
|
2586
2588
|
`)}
|
|
2587
|
-
` + `Malloy routes an annotation by its prefix, and only ` + `\`#(authorize)\` (or \`##(authorize)\`, or the block form ` + `\`#|(authorize)\`) reaches the authorize route — a space after the ` + `\`#\`, spaces inside the brackets, or anything trailing the closing ` + `bracket makes it a plain tag Malloy hands to something else. Write ` + `\`#(authorize)
|
|
2589
|
+
` + `Malloy routes an annotation by its prefix, and only ` + `\`#(authorize)\` (or \`##(authorize)\`, or the block form ` + `\`#|(authorize)\`) reaches the authorize route — a space after the ` + `\`#\`, spaces inside the brackets, or anything trailing the closing ` + `bracket makes it a plain tag Malloy hands to something else. Write ` + `\`#(authorize) <expression>\` on its own line directly above the ` + `\`source:\` statement you mean to protect (unquoted — the quoted ` + `string form is retired). This is refused rather than interpreted: ` + `guessing at the ` + `intent would let publisher start enforcing a filter on a package that ` + `has been serving every row.`
|
|
2588
2590
|
});
|
|
2589
2591
|
}
|
|
2590
2592
|
function describeMisplacedAuthorizeAnnotation(f) {
|
|
@@ -2605,20 +2607,6 @@ ${positions}
|
|
|
2605
2607
|
` + `A gate only applies where model load looks for one — a \`source:\`'s ` + `own annotation, or one it inherits from an \`extend\`/query-source ` + `base. File-level \`##(authorize)\` is deprecated and no longer ` + `enforced anywhere, so it always lands here: declare \`#(authorize)\` ` + `on each \`source:\` it was meant to protect instead. Every other ` + `position above should move to the \`source:\` statement it is meant ` + `to protect.`
|
|
2606
2608
|
});
|
|
2607
2609
|
}
|
|
2608
|
-
function buildAuthorizeProbe(exprs, givenDecls = []) {
|
|
2609
|
-
const selects = exprs.map((expr, i) => `__auth_${i} is (${expr})`).join(`
|
|
2610
|
-
`);
|
|
2611
|
-
const givenBlock = givenDecls.length > 0 ? `given:
|
|
2612
|
-
${givenDecls.map((g) => ` ${g.name} :: ${g.type}`).join(`
|
|
2613
|
-
`)}
|
|
2614
|
-
|
|
2615
|
-
` : "";
|
|
2616
|
-
return `${givenBlock}run: duckdb.sql("SELECT 1 AS __authorize_probe_row") -> {
|
|
2617
|
-
select:
|
|
2618
|
-
${selects}
|
|
2619
|
-
limit: 1
|
|
2620
|
-
}`;
|
|
2621
|
-
}
|
|
2622
2610
|
function referencedGivenNames(expr) {
|
|
2623
2611
|
const scanned = expr.replace(STRING_LITERAL_PATTERN, "''");
|
|
2624
2612
|
const names = [];
|
|
@@ -2632,249 +2620,6 @@ function referencedGivenNames(expr) {
|
|
|
2632
2620
|
}
|
|
2633
2621
|
return names;
|
|
2634
2622
|
}
|
|
2635
|
-
function classifyAuthorizeGate(condition, declaredTypes, declaredDefaults) {
|
|
2636
|
-
const fieldUsage = condition.refSummary?.fieldUsage;
|
|
2637
|
-
if (!Array.isArray(fieldUsage) || fieldUsage.length === 0) {
|
|
2638
|
-
return { shape: "given_only" };
|
|
2639
|
-
}
|
|
2640
|
-
const givenNames = [];
|
|
2641
|
-
const literalAtoms = [];
|
|
2642
|
-
let rejection;
|
|
2643
|
-
const reject = (cause, detail) => {
|
|
2644
|
-
rejection ??= { shape: "rejected", cause, detail };
|
|
2645
|
-
return false;
|
|
2646
|
-
};
|
|
2647
|
-
const givenOperand = (node) => {
|
|
2648
|
-
const n = asNode(node);
|
|
2649
|
-
return n && n.node === "given" && typeof n.refName === "string" ? n.refName : null;
|
|
2650
|
-
};
|
|
2651
|
-
const isLiteralOperand = (node) => {
|
|
2652
|
-
const n = asNode(node);
|
|
2653
|
-
return !!n && (n.node === "numberLiteral" || n.node === "stringLiteral" || n.node === "true" || n.node === "false");
|
|
2654
|
-
};
|
|
2655
|
-
const literalOperandText = (node) => {
|
|
2656
|
-
const n = asNode(node);
|
|
2657
|
-
if (!n)
|
|
2658
|
-
return null;
|
|
2659
|
-
if (n.node === "true" || n.node === "false")
|
|
2660
|
-
return n.node;
|
|
2661
|
-
if (n.node === "numberLiteral" && typeof n.literal === "string") {
|
|
2662
|
-
return n.literal;
|
|
2663
|
-
}
|
|
2664
|
-
if (n.node === "stringLiteral" && typeof n.literal === "string") {
|
|
2665
|
-
return `'${n.literal.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
2666
|
-
}
|
|
2667
|
-
return null;
|
|
2668
|
-
};
|
|
2669
|
-
const declaredTypeOf = (name) => {
|
|
2670
|
-
const declared = declaredTypes.get(name);
|
|
2671
|
-
if (declared === undefined) {
|
|
2672
|
-
reject("unreachable_given", `\`$${name}\` is not on this model's given surface, so the gate ` + `would bind its declaration default rather than the caller's ` + `value. Declare it here, importing it if it lives elsewhere ` + `(\`import { ${name} } from "…"\`)`);
|
|
2673
|
-
return null;
|
|
2674
|
-
}
|
|
2675
|
-
return declared;
|
|
2676
|
-
};
|
|
2677
|
-
const walk = (node, depth) => {
|
|
2678
|
-
if (depth > MAX_GATE_WALK_DEPTH) {
|
|
2679
|
-
return reject("unsupported_node", "the gate nests too deeply to read");
|
|
2680
|
-
}
|
|
2681
|
-
const n = asNode(node);
|
|
2682
|
-
if (!n || typeof n.node !== "string") {
|
|
2683
|
-
return reject("unsupported_node", "the gate has an unreadable shape");
|
|
2684
|
-
}
|
|
2685
|
-
const kind = n.node;
|
|
2686
|
-
if (BOOLEAN_NODES.has(kind)) {
|
|
2687
|
-
const kids = asNode(n.kids);
|
|
2688
|
-
if (!kids) {
|
|
2689
|
-
return reject("unsupported_node", `\`${kind}\` has no operands`);
|
|
2690
|
-
}
|
|
2691
|
-
return walk(kids.left, depth + 1) && walk(kids.right, depth + 1);
|
|
2692
|
-
}
|
|
2693
|
-
if (TRANSPARENT_NODES.has(kind)) {
|
|
2694
|
-
return walk(n.e, depth + 1);
|
|
2695
|
-
}
|
|
2696
|
-
if (kind === "inGiven") {
|
|
2697
|
-
if (n.not === true) {
|
|
2698
|
-
return reject("unsupported_node", "a negated membership test (`not in`) is not an access rule; " + "write the gate as the set of rows a caller MAY read");
|
|
2699
|
-
}
|
|
2700
|
-
const given = givenOperand(n.givenRef);
|
|
2701
|
-
if (given === null) {
|
|
2702
|
-
return reject("unsupported_node", "`in` must test membership of a declared given");
|
|
2703
|
-
}
|
|
2704
|
-
const declared = declaredTypeOf(given);
|
|
2705
|
-
if (declared === null)
|
|
2706
|
-
return false;
|
|
2707
|
-
if (!isArrayType(declared)) {
|
|
2708
|
-
return reject("scalar_given_rejects_in", `\`$${given}\` is declared \`${declared}\` (a scalar), so ` + `\`in $${given}\` is not a membership test. Compare it with ` + `\`=\` instead`);
|
|
2709
|
-
}
|
|
2710
|
-
givenNames.push(given);
|
|
2711
|
-
return walkFieldOperand(n.e);
|
|
2712
|
-
}
|
|
2713
|
-
if (SCALAR_COMPARISON_NODES.has(kind)) {
|
|
2714
|
-
const kids = asNode(n.kids);
|
|
2715
|
-
if (!kids) {
|
|
2716
|
-
return reject("unsupported_node", `\`${kind}\` has no operands`);
|
|
2717
|
-
}
|
|
2718
|
-
const left = givenOperand(kids.left);
|
|
2719
|
-
const right = givenOperand(kids.right);
|
|
2720
|
-
const given = left ?? right;
|
|
2721
|
-
if (given === null) {
|
|
2722
|
-
return reject("no_given_reference", `\`${kind}\` must compare a field against a given; a comparison ` + `against a constant is a fixed filter and belongs in the ` + `source's own \`where:\``);
|
|
2723
|
-
}
|
|
2724
|
-
const declared = declaredTypeOf(given);
|
|
2725
|
-
if (declared === null)
|
|
2726
|
-
return false;
|
|
2727
|
-
if (isArrayType(declared)) {
|
|
2728
|
-
return reject("array_given_needs_in", `\`$${given}\` is declared \`${declared}\` (an array), so ` + `comparing it with \`${kind}\` compiles and then fails in the ` + `warehouse. Write \`in $${given}\` — it is also the spelling ` + `that matches no rows when the array is empty`);
|
|
2729
|
-
}
|
|
2730
|
-
const otherSide = left === null ? kids.left : kids.right;
|
|
2731
|
-
if (isLiteralOperand(otherSide)) {
|
|
2732
|
-
givenNames.push(given);
|
|
2733
|
-
const literalText = literalOperandText(otherSide);
|
|
2734
|
-
if (literalText !== null) {
|
|
2735
|
-
literalAtoms.push(left !== null ? `$${given} ${kind} ${literalText}` : `${literalText} ${kind} $${given}`);
|
|
2736
|
-
}
|
|
2737
|
-
return true;
|
|
2738
|
-
}
|
|
2739
|
-
if (declaredDefaults.has(given)) {
|
|
2740
|
-
return reject("field_given_has_default", `\`${kind}\` compares row field data against \`$${given}\`, ` + `which is declared with a default (\`${declaredDefaults.get(given)}\`). ` + `A caller who supplies no value for \`$${given}\` gets that ` + `default, and the comparison then applies to every row — ` + `admitting rows it was meant to exclude. Declare \`$${given}\` ` + `with no default so a caller must supply one explicitly`);
|
|
2741
|
-
}
|
|
2742
|
-
givenNames.push(given);
|
|
2743
|
-
return walkFieldOperand(otherSide);
|
|
2744
|
-
}
|
|
2745
|
-
return reject("unsupported_node", `\`${kind}\` is not permitted in a gate; a row-level gate is a ` + `boolean combination of \`<field> <operator> $GIVEN\` comparisons`);
|
|
2746
|
-
};
|
|
2747
|
-
const walkFieldOperand = (node) => {
|
|
2748
|
-
const n = asNode(node);
|
|
2749
|
-
if (!n || n.node !== "field" || !Array.isArray(n.path)) {
|
|
2750
|
-
return reject("unsupported_node", `a gate compares a FIELD against a given; \`${asNode(node)?.node ?? "that operand"}\` is not a field reference`);
|
|
2751
|
-
}
|
|
2752
|
-
return true;
|
|
2753
|
-
};
|
|
2754
|
-
let ok;
|
|
2755
|
-
try {
|
|
2756
|
-
ok = walk(condition.e, 0);
|
|
2757
|
-
} catch {
|
|
2758
|
-
return {
|
|
2759
|
-
shape: "rejected",
|
|
2760
|
-
cause: "unsupported_node",
|
|
2761
|
-
detail: "the gate's compiled shape could not be read"
|
|
2762
|
-
};
|
|
2763
|
-
}
|
|
2764
|
-
if (!ok) {
|
|
2765
|
-
return rejection ?? {
|
|
2766
|
-
shape: "rejected",
|
|
2767
|
-
cause: "unsupported_node",
|
|
2768
|
-
detail: "the gate is not an allowed shape"
|
|
2769
|
-
};
|
|
2770
|
-
}
|
|
2771
|
-
if (givenNames.length === 0) {
|
|
2772
|
-
return {
|
|
2773
|
-
shape: "rejected",
|
|
2774
|
-
cause: "no_given_reference",
|
|
2775
|
-
detail: "a row-level gate must compare a field against a given; a gate " + "that references none is a fixed filter and belongs in the " + "source's own `where:`"
|
|
2776
|
-
};
|
|
2777
|
-
}
|
|
2778
|
-
return { shape: "row_level", givenNames, literalAtoms };
|
|
2779
|
-
}
|
|
2780
|
-
function isArrayType(declaredType) {
|
|
2781
|
-
const normalized = declaredType.trim().toLowerCase();
|
|
2782
|
-
return normalized === "array" || normalized.endsWith("[]");
|
|
2783
|
-
}
|
|
2784
|
-
function asNode(node) {
|
|
2785
|
-
return node !== null && typeof node === "object" ? node : null;
|
|
2786
|
-
}
|
|
2787
|
-
function inferGivenType(value) {
|
|
2788
|
-
if (typeof value === "string")
|
|
2789
|
-
return "string";
|
|
2790
|
-
if (typeof value === "number" || typeof value === "bigint")
|
|
2791
|
-
return "number";
|
|
2792
|
-
if (typeof value === "boolean")
|
|
2793
|
-
return "boolean";
|
|
2794
|
-
if (value instanceof Date)
|
|
2795
|
-
return "timestamp";
|
|
2796
|
-
if (Array.isArray(value)) {
|
|
2797
|
-
if (value.length === 0)
|
|
2798
|
-
return null;
|
|
2799
|
-
const elementType = inferGivenType(value[0]);
|
|
2800
|
-
return elementType ? `${elementType}[]` : null;
|
|
2801
|
-
}
|
|
2802
|
-
return null;
|
|
2803
|
-
}
|
|
2804
|
-
function bindProbeGivens(expr, givens, declaredTypes) {
|
|
2805
|
-
const decls = [];
|
|
2806
|
-
const bound = {};
|
|
2807
|
-
for (const name of referencedGivenNames(expr)) {
|
|
2808
|
-
if (!(name in givens))
|
|
2809
|
-
continue;
|
|
2810
|
-
const value = givens[name];
|
|
2811
|
-
const type = declaredTypes?.get(name) ?? inferGivenType(value);
|
|
2812
|
-
if (!type)
|
|
2813
|
-
continue;
|
|
2814
|
-
decls.push({ name, type });
|
|
2815
|
-
bound[name] = value;
|
|
2816
|
-
}
|
|
2817
|
-
return { decls, bound };
|
|
2818
|
-
}
|
|
2819
|
-
function isProbeTrue(cell) {
|
|
2820
|
-
return cell === true || cell === 1 || cell === "true";
|
|
2821
|
-
}
|
|
2822
|
-
async function runProbe(executor, probeText, givens) {
|
|
2823
|
-
const result = await executor.loadQuery(probeText).run({ rowLimit: 1, givens });
|
|
2824
|
-
const row = result?.data?.value?.[0];
|
|
2825
|
-
return !!(row && isProbeTrue(row.__auth_0));
|
|
2826
|
-
}
|
|
2827
|
-
async function evaluateAuthorize(executor, exprs, givens, declaredTypes, options) {
|
|
2828
|
-
const selfContainedFirst = options?.selfContainedFirst ?? false;
|
|
2829
|
-
for (const expr of exprs) {
|
|
2830
|
-
if (selfContainedFirst) {
|
|
2831
|
-
if (await evaluateSelfContainedFirst(executor, expr, givens, declaredTypes)) {
|
|
2832
|
-
return true;
|
|
2833
|
-
}
|
|
2834
|
-
continue;
|
|
2835
|
-
}
|
|
2836
|
-
try {
|
|
2837
|
-
if (await runProbe(executor, buildAuthorizeProbe([expr]), givens)) {
|
|
2838
|
-
return true;
|
|
2839
|
-
}
|
|
2840
|
-
continue;
|
|
2841
|
-
} catch {}
|
|
2842
|
-
try {
|
|
2843
|
-
const { decls, bound } = bindProbeGivens(expr, givens, declaredTypes);
|
|
2844
|
-
if (decls.length === 0)
|
|
2845
|
-
continue;
|
|
2846
|
-
if (await runProbe(executor, buildAuthorizeProbe([expr], decls), bound)) {
|
|
2847
|
-
return true;
|
|
2848
|
-
}
|
|
2849
|
-
} catch {
|
|
2850
|
-
continue;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
return false;
|
|
2854
|
-
}
|
|
2855
|
-
async function evaluateSelfContainedFirst(executor, expr, givens, declaredTypes) {
|
|
2856
|
-
const referenced = referencedGivenNames(expr);
|
|
2857
|
-
if (referenced.length === 0) {
|
|
2858
|
-
try {
|
|
2859
|
-
return await runProbe(executor, buildAuthorizeProbe([expr]), {});
|
|
2860
|
-
} catch {
|
|
2861
|
-
return false;
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
const { decls, bound } = bindProbeGivens(expr, givens, declaredTypes);
|
|
2865
|
-
if (decls.length !== referenced.length) {
|
|
2866
|
-
return false;
|
|
2867
|
-
}
|
|
2868
|
-
try {
|
|
2869
|
-
return await runProbe(executor, buildAuthorizeProbe([expr], decls), bound);
|
|
2870
|
-
} catch {
|
|
2871
|
-
try {
|
|
2872
|
-
return await runProbe(executor, buildAuthorizeProbe([expr]), givens);
|
|
2873
|
-
} catch {
|
|
2874
|
-
return false;
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
2623
|
function quoteMalloyIdentifier(name) {
|
|
2879
2624
|
return "`" + name.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + "`";
|
|
2880
2625
|
}
|
|
@@ -2903,42 +2648,8 @@ async function liftRowLevelCondition(compiler, sourceName, exprs) {
|
|
|
2903
2648
|
const prepared = await compiler.loadQuery(buildRowLevelProbe(sourceName, filterText)).getPreparedQuery();
|
|
2904
2649
|
return liftProbeFilterCondition(prepared, `row-level probe for "${sourceName}"`, filterText);
|
|
2905
2650
|
}
|
|
2906
|
-
async function runOneRowProbeOrThrow(compiler, sourceName, exprs) {
|
|
2907
|
-
try {
|
|
2908
|
-
await compiler.loadQuery(buildAuthorizeProbe(exprs)).getPreparedQuery();
|
|
2909
|
-
} catch (err) {
|
|
2910
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2911
|
-
throw new ModelCompilationError({
|
|
2912
|
-
message: `Invalid #(authorize) annotation on source "${sourceName}" [${exprs.join(" | ")}]: ${detail}`
|
|
2913
|
-
});
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
2916
|
-
async function assertNoVacuousDefaultAtom(executor, sourceName, literalAtoms) {
|
|
2917
|
-
for (const atom of literalAtoms) {
|
|
2918
|
-
let vacuous;
|
|
2919
|
-
try {
|
|
2920
|
-
vacuous = await runProbe(executor, buildAuthorizeProbe([atom]), {});
|
|
2921
|
-
} catch (err) {
|
|
2922
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2923
|
-
if (NO_DEFAULT_GIVEN_PATTERN.test(detail))
|
|
2924
|
-
continue;
|
|
2925
|
-
throw new ModelCompilationError({
|
|
2926
|
-
message: `Invalid #(authorize) annotation on source "${sourceName}": ` + `the atom \`${atom}\` could not be evaluated against its ` + `given's declared default (${detail}).`
|
|
2927
|
-
});
|
|
2928
|
-
}
|
|
2929
|
-
if (vacuous) {
|
|
2930
|
-
throw new ModelCompilationError({
|
|
2931
|
-
message: `Invalid #(authorize) annotation on source "${sourceName}": ` + `the atom \`${atom}\` evaluates to TRUE when a caller supplies ` + `no givens (its given's own declared default). An OR'd atom ` + `that is true by default makes the whole row filter admit ` + `every row for that caller. Give the given a default this ` + `atom evaluates false against, or declare it with no default ` + `so a caller must supply one explicitly.`
|
|
2932
|
-
});
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
2651
|
async function validateAuthorizeProbes(compiler, options) {
|
|
2937
|
-
const declaredTypes = options.declaredTypes ?? new Map;
|
|
2938
|
-
const declaredDefaults = options.declaredDefaults ?? new Map;
|
|
2939
|
-
const provenNoteObjects = new Set;
|
|
2940
2652
|
const ownNotesOf = options.authorizeOwnNotes ?? new Map;
|
|
2941
|
-
const pending = [];
|
|
2942
2653
|
for (const [sourceName, groups] of options.authorizeMap ?? []) {
|
|
2943
2654
|
for (const exprs of groups) {
|
|
2944
2655
|
if (exprs.length === 0)
|
|
@@ -2947,51 +2658,39 @@ async function validateAuthorizeProbes(compiler, options) {
|
|
|
2947
2658
|
try {
|
|
2948
2659
|
condition = await liftRowLevelCondition(compiler, sourceName, exprs);
|
|
2949
2660
|
} catch (err) {
|
|
2950
|
-
|
|
2951
|
-
continue;
|
|
2952
|
-
}
|
|
2953
|
-
const classification = classifyAuthorizeGate(condition, declaredTypes, declaredDefaults);
|
|
2954
|
-
if (classification.shape === "given_only") {
|
|
2955
|
-
await runOneRowProbeOrThrow(compiler, sourceName, exprs);
|
|
2956
|
-
for (const note of ownNotesOf.get(sourceName) ?? []) {
|
|
2957
|
-
provenNoteObjects.add(note);
|
|
2958
|
-
}
|
|
2959
|
-
continue;
|
|
2960
|
-
}
|
|
2961
|
-
if (classification.shape === "rejected") {
|
|
2962
|
-
options.onRowLevelGateRejected?.(classification.cause);
|
|
2661
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2963
2662
|
const ownNotes = ownNotesOf.get(sourceName) ?? [];
|
|
2964
2663
|
if (ownNotes.length === 0) {
|
|
2965
|
-
options.
|
|
2664
|
+
options.onRowLevelGateRejected?.("entry_point_unexpressible");
|
|
2665
|
+
options.onRowLevelGateUnexpressible?.(sourceName, detail);
|
|
2966
2666
|
continue;
|
|
2967
2667
|
}
|
|
2968
2668
|
throw new ModelCompilationError({
|
|
2969
|
-
message: `Invalid #(authorize) annotation on source "${sourceName}" ` + `[${exprs.join(" | ")}]: ${
|
|
2669
|
+
message: `Invalid #(authorize) annotation on source "${sourceName}" ` + `[${exprs.join(" | ")}]: ${detail}`
|
|
2970
2670
|
});
|
|
2971
2671
|
}
|
|
2972
|
-
|
|
2973
|
-
for (const note of ownNotesOf.get(sourceName) ?? []) {
|
|
2974
|
-
provenNoteObjects.add(note);
|
|
2975
|
-
}
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
for (const failure of pending) {
|
|
2979
|
-
const ownNotes = ownNotesOf.get(failure.sourceName) ?? [];
|
|
2980
|
-
const inherited = ownNotes.length === 0 || ownNotes.every((note) => provenNoteObjects.has(note));
|
|
2981
|
-
if (inherited) {
|
|
2982
|
-
const detail = failure.err instanceof Error ? failure.err.message : String(failure.err);
|
|
2983
|
-
options.onRowLevelGateRejected?.("entry_point_unexpressible");
|
|
2984
|
-
options.onRowLevelGateUnexpressible?.(failure.sourceName, detail);
|
|
2985
|
-
continue;
|
|
2672
|
+
options.onOwnRowLevelConditionCompiled?.(sourceName, condition);
|
|
2986
2673
|
}
|
|
2987
|
-
await runOneRowProbeOrThrow(compiler, failure.sourceName, failure.exprs);
|
|
2988
2674
|
}
|
|
2989
2675
|
}
|
|
2676
|
+
function isLegacyQuotedPayload(payload) {
|
|
2677
|
+
const trimmed = payload.trim();
|
|
2678
|
+
return WHOLE_BODY_QUOTED_STRING.test(trimmed) || WHOLE_BODY_SINGLE_QUOTED_STRING.test(trimmed);
|
|
2679
|
+
}
|
|
2680
|
+
function unquoteLegacyGatePayload(payload) {
|
|
2681
|
+
const trimmed = payload.trim();
|
|
2682
|
+
const inner = trimmed.slice(1, -1);
|
|
2683
|
+
return trimmed[0] === "'" ? inner.replace(SINGLE_QUOTE_ESCAPE, "$1") : inner.replace(DOUBLE_QUOTE_ESCAPE, "$1");
|
|
2684
|
+
}
|
|
2990
2685
|
function parseAuthorizeAnnotation(annotation) {
|
|
2991
2686
|
const content = authorizeNoteContent(annotation);
|
|
2992
2687
|
if (content === undefined)
|
|
2993
2688
|
return null;
|
|
2994
|
-
|
|
2689
|
+
const trimmed = content.trim();
|
|
2690
|
+
if (trimmed.length === 0) {
|
|
2691
|
+
throw new Error("authorize annotation has an empty expression body");
|
|
2692
|
+
}
|
|
2693
|
+
return trimmed;
|
|
2995
2694
|
}
|
|
2996
2695
|
function collectAuthorizeExprs(annotations) {
|
|
2997
2696
|
const exprs = [];
|
|
@@ -3003,43 +2702,57 @@ function collectAuthorizeExprs(annotations) {
|
|
|
3003
2702
|
}
|
|
3004
2703
|
return exprs;
|
|
3005
2704
|
}
|
|
3006
|
-
function
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
let i = 1;
|
|
3012
|
-
let closed = false;
|
|
3013
|
-
for (;i < body.length; i++) {
|
|
3014
|
-
const ch = body[i];
|
|
3015
|
-
if (ch === "\\" && i + 1 < body.length) {
|
|
3016
|
-
const next = body[i + 1];
|
|
3017
|
-
if (next === '"' || next === "\\") {
|
|
3018
|
-
expr += next;
|
|
3019
|
-
i++;
|
|
3020
|
-
continue;
|
|
3021
|
-
}
|
|
3022
|
-
}
|
|
3023
|
-
if (ch === '"') {
|
|
3024
|
-
closed = true;
|
|
3025
|
-
i++;
|
|
3026
|
-
break;
|
|
2705
|
+
function findMultipleAuthorizeGates(authorizeOwnNotes) {
|
|
2706
|
+
const found = [];
|
|
2707
|
+
for (const [sourceName, notes] of authorizeOwnNotes) {
|
|
2708
|
+
if (notes.length > 1) {
|
|
2709
|
+
found.push({ sourceName, texts: notes.map((note) => note.text) });
|
|
3027
2710
|
}
|
|
3028
|
-
expr += ch;
|
|
3029
|
-
}
|
|
3030
|
-
if (!closed) {
|
|
3031
|
-
throw new Error(`authorize annotation has mismatched quotes: ${body}`);
|
|
3032
2711
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
2712
|
+
return found;
|
|
2713
|
+
}
|
|
2714
|
+
function assertAtMostOneAuthorizeGate(found) {
|
|
2715
|
+
if (found.length === 0)
|
|
2716
|
+
return;
|
|
2717
|
+
const positions = found.map(({ sourceName, texts }) => ` - source "${sourceName}" declares ${texts.length}:
|
|
2718
|
+
` + texts.map((t) => ` \`${t.trim().split(/[\r\n]/, 1)[0]}\``).join(`
|
|
2719
|
+
`)).join(`
|
|
2720
|
+
`);
|
|
2721
|
+
throw new ModelCompilationError({
|
|
2722
|
+
message: `A source may declare at most one \`#(authorize)\` annotation:
|
|
2723
|
+
${positions}
|
|
2724
|
+
` + `Combine multiple conditions into one expression with \`or\` instead of ` + `repeating the annotation, e.g. ` + "`#(authorize) $ROLE = 'admin' or org_id in $GROUPS`."
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
function findLegacyStringGates(authorizeOwnNotes) {
|
|
2728
|
+
const found = [];
|
|
2729
|
+
for (const [sourceName, notes] of authorizeOwnNotes) {
|
|
2730
|
+
const legacyNotes = notes.filter((note) => {
|
|
2731
|
+
const content = authorizeNoteContent(note.text);
|
|
2732
|
+
return content !== undefined && isLegacyQuotedPayload(content);
|
|
2733
|
+
});
|
|
2734
|
+
if (legacyNotes.length === 0)
|
|
2735
|
+
continue;
|
|
2736
|
+
const exprs = collectAuthorizeExprs(legacyNotes.map((note) => note.text));
|
|
2737
|
+
if (exprs.length > 0) {
|
|
2738
|
+
found.push({ sourceName, exprs });
|
|
2739
|
+
}
|
|
3039
2740
|
}
|
|
3040
|
-
return
|
|
2741
|
+
return found;
|
|
2742
|
+
}
|
|
2743
|
+
function assertNoLegacyStringGate(found) {
|
|
2744
|
+
if (found.length === 0)
|
|
2745
|
+
return;
|
|
2746
|
+
const rewrites = found.flatMap(({ sourceName, exprs }) => exprs.map((expr) => ` - source "${sourceName}": replace \`#(authorize) ${expr}\`` + ` with
|
|
2747
|
+
#(authorize) ${unquoteLegacyGatePayload(expr)}`)).join(`
|
|
2748
|
+
`);
|
|
2749
|
+
throw new ModelCompilationError({
|
|
2750
|
+
message: `The string form of \`#(authorize)\` (a Malloy-quoted expression on ` + `the \`source:\` line) is no longer accepted. Replace it with the ` + `unquoted expression, carried by an \`#(authorize)\` annotation on ` + `its own line directly above the \`source:\` line:
|
|
2751
|
+
${rewrites}
|
|
2752
|
+
` + `Test that the rewrite gates the same rows and check the row count ` + `matches what the string form served.`
|
|
2753
|
+
});
|
|
3041
2754
|
}
|
|
3042
|
-
var AUTHORIZE_ROUTE = "authorize", AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, MOTLY_AUTHORIZE_PAYLOAD, MALFORMED_AUTHORIZE_ATTEMPT, GIVEN_REF_PATTERN, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES,
|
|
2755
|
+
var AUTHORIZE_ROUTE = "authorize", AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, MOTLY_AUTHORIZE_PAYLOAD, MALFORMED_AUTHORIZE_ATTEMPT, GIVEN_REF_PATTERN, SINGLE_QUOTE_ESCAPE, DOUBLE_QUOTE_ESCAPE, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES, WHOLE_BODY_QUOTED_STRING, WHOLE_BODY_SINGLE_QUOTED_STRING;
|
|
3043
2756
|
var init_authorize = __esm(() => {
|
|
3044
2757
|
init_errors();
|
|
3045
2758
|
AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*[([{<]?[ \t]*authorize(?=[)\]}>]|[ \t]|$)`;
|
|
@@ -3047,12 +2760,12 @@ var init_authorize = __esm(() => {
|
|
|
3047
2760
|
MOTLY_AUTHORIZE_PAYLOAD = /^[ \t]*[([{<][ \t]*authorize[ \t]*[)\]}>]/iu;
|
|
3048
2761
|
MALFORMED_AUTHORIZE_ATTEMPT = /^##?\|?[ \t]*[([{<]?[ \t]*authorize/iu;
|
|
3049
2762
|
GIVEN_REF_PATTERN = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
2763
|
+
SINGLE_QUOTE_ESCAPE = /\\(['\\])/g;
|
|
2764
|
+
DOUBLE_QUOTE_ESCAPE = /\\(["\\])/g;
|
|
2765
|
+
STRING_LITERAL_PATTERN = /'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/g;
|
|
2766
|
+
ROW_LEVEL_GATE_REJECTION_CAUSES = everyMemberOf()("unreachable_given", "entry_point_unexpressible", "source_line_gate_no_given_reference", "source_line_gate_negated_membership", "legacy_string_gate", "given_usage_unresolvable", "unclassifiable_condition");
|
|
2767
|
+
WHOLE_BODY_QUOTED_STRING = /^"(?:\\.|[^"\\])*"$/;
|
|
2768
|
+
WHOLE_BODY_SINGLE_QUOTED_STRING = /^'(?:\\.|[^'\\])*'$/;
|
|
3056
2769
|
});
|
|
3057
2770
|
|
|
3058
2771
|
// src/authorize_metrics.ts
|
|
@@ -3076,7 +2789,7 @@ function recordRowLevelGateDecision(decision) {
|
|
|
3076
2789
|
}
|
|
3077
2790
|
function recordRowLevelGateRejected(cause) {
|
|
3078
2791
|
rowLevelRejectionCounter ??= publisherMeter().createCounter("publisher_authorize_row_level_rejected_total", {
|
|
3079
|
-
description: "Row-level `#(authorize)` gates
|
|
2792
|
+
description: "Row-level `#(authorize)` gates that were refused, warned about, or could not be resolved. Label: cause (" + ROW_LEVEL_GATE_REJECTION_CAUSES.map((c) => `'${c}'`).join("|") + "). Only 'legacy_string_gate' fails the whole model load. 'entry_point_unexpressible', 'source_line_gate_no_given_reference' and 'source_line_gate_negated_membership' warn at load and leave the model servable. 'unreachable_given', 'given_usage_unresolvable' and 'unclassifiable_condition' are request-time only — that entry point denies every request. Alert on any nonzero value since the last publish; the request-time causes also make this a rate signal, so see the doc above before writing an alert."
|
|
3080
2793
|
});
|
|
3081
2794
|
rowLevelRejectionCounter.add(1, { cause });
|
|
3082
2795
|
}
|
|
@@ -13639,6 +13352,97 @@ var init_data_styles = __esm(() => {
|
|
|
13639
13352
|
init_logger();
|
|
13640
13353
|
});
|
|
13641
13354
|
|
|
13355
|
+
// src/service/gate_dimension.ts
|
|
13356
|
+
import {
|
|
13357
|
+
isJoined,
|
|
13358
|
+
isSourceDef
|
|
13359
|
+
} from "@malloydata/malloy";
|
|
13360
|
+
function resolveFieldUsagePath(struct, path) {
|
|
13361
|
+
let current = struct;
|
|
13362
|
+
for (let i = 0;i < path.length - 1; i++) {
|
|
13363
|
+
const seg = path[i];
|
|
13364
|
+
const joinField = (current.fields ?? []).find((f) => (f.as || f.name) === seg && isJoined(f) && (isSourceDef(f) || f.type === "record" || f.type === "array"));
|
|
13365
|
+
if (!joinField)
|
|
13366
|
+
return;
|
|
13367
|
+
current = joinField;
|
|
13368
|
+
}
|
|
13369
|
+
const leaf = path[path.length - 1];
|
|
13370
|
+
const field = (current.fields ?? []).find((f) => (f.as || f.name) === leaf);
|
|
13371
|
+
return field ? { struct: current, field } : undefined;
|
|
13372
|
+
}
|
|
13373
|
+
function expandRefSummaryGivenIds(struct, refSummary, seen = new Set) {
|
|
13374
|
+
const ids = new Set;
|
|
13375
|
+
for (const g of refSummary?.givenUsage ?? [])
|
|
13376
|
+
ids.add(g.id);
|
|
13377
|
+
for (const usage of refSummary?.fieldUsage ?? []) {
|
|
13378
|
+
if (usage.path.length === 0)
|
|
13379
|
+
continue;
|
|
13380
|
+
const resolved = resolveFieldUsagePath(struct, usage.path);
|
|
13381
|
+
if (!resolved) {
|
|
13382
|
+
return { ok: false, unresolvedPath: usage.path.join(".") };
|
|
13383
|
+
}
|
|
13384
|
+
if (seen.has(resolved.field))
|
|
13385
|
+
continue;
|
|
13386
|
+
seen.add(resolved.field);
|
|
13387
|
+
const nestedRefSummary = resolved.field.refSummary;
|
|
13388
|
+
const nested = expandRefSummaryGivenIds(resolved.struct, nestedRefSummary, seen);
|
|
13389
|
+
if (!nested.ok)
|
|
13390
|
+
return nested;
|
|
13391
|
+
for (const id of nested.givenIds)
|
|
13392
|
+
ids.add(id);
|
|
13393
|
+
}
|
|
13394
|
+
return { ok: true, givenIds: ids };
|
|
13395
|
+
}
|
|
13396
|
+
function containsNegatedMembership(node, depth = 0) {
|
|
13397
|
+
if (depth > 64 || node === null || typeof node !== "object")
|
|
13398
|
+
return false;
|
|
13399
|
+
const n = node;
|
|
13400
|
+
if (n.node === "inGiven" && n.not === true)
|
|
13401
|
+
return true;
|
|
13402
|
+
if (n.node === "not") {
|
|
13403
|
+
let inner = n.e;
|
|
13404
|
+
while (inner && inner.node === "()") {
|
|
13405
|
+
inner = inner.e;
|
|
13406
|
+
}
|
|
13407
|
+
if (inner && inner.node === "inGiven")
|
|
13408
|
+
return true;
|
|
13409
|
+
return containsNegatedMembership(n.e, depth + 1);
|
|
13410
|
+
}
|
|
13411
|
+
if (n.node === "and" || n.node === "or") {
|
|
13412
|
+
const kids = n.kids;
|
|
13413
|
+
return containsNegatedMembership(kids?.left, depth + 1) || containsNegatedMembership(kids?.right, depth + 1);
|
|
13414
|
+
}
|
|
13415
|
+
if (n.node === "()")
|
|
13416
|
+
return containsNegatedMembership(n.e, depth + 1);
|
|
13417
|
+
return false;
|
|
13418
|
+
}
|
|
13419
|
+
function validateSourceLineGateGivenUsage(sourceName, struct, refSummary, conditionExpr, modelDef, onWarning) {
|
|
13420
|
+
const expansion = expandRefSummaryGivenIds(struct, refSummary);
|
|
13421
|
+
if (!expansion.ok) {
|
|
13422
|
+
throw new ModelCompilationError({
|
|
13423
|
+
message: `#(authorize) on source "${sourceName}" references ` + `"${expansion.unresolvedPath}", which could not be resolved to a ` + `field this model can reach. An unresolvable reference is refused, ` + `not treated as referencing no given`
|
|
13424
|
+
});
|
|
13425
|
+
}
|
|
13426
|
+
const givenIds = expansion.givenIds;
|
|
13427
|
+
for (const id of givenIds) {
|
|
13428
|
+
const given = modelDef.givens?.[id];
|
|
13429
|
+
if (given && (given.default !== undefined || given.defaultText !== undefined)) {
|
|
13430
|
+
throw new ModelCompilationError({
|
|
13431
|
+
message: `#(authorize) on source "${sourceName}" references ` + `\`$${given.name}\`, which is declared with a default. A ` + `caller who supplies no value for \`$${given.name}\` gets ` + `that default, which can admit rows the gate was meant to ` + `exclude. Declare \`$${given.name}\` with no default`
|
|
13432
|
+
});
|
|
13433
|
+
}
|
|
13434
|
+
}
|
|
13435
|
+
if (givenIds.size === 0) {
|
|
13436
|
+
onWarning?.("source_line_gate_no_given_reference", `#(authorize) on source "${sourceName}" references no given; it is ` + `a fixed predicate, not an access rule keyed on the caller`);
|
|
13437
|
+
}
|
|
13438
|
+
if (containsNegatedMembership(conditionExpr)) {
|
|
13439
|
+
onWarning?.("source_line_gate_negated_membership", `#(authorize) on source "${sourceName}" negates a membership test; ` + `an empty given then matches every row instead of none`);
|
|
13440
|
+
}
|
|
13441
|
+
}
|
|
13442
|
+
var init_gate_dimension = __esm(() => {
|
|
13443
|
+
init_errors();
|
|
13444
|
+
});
|
|
13445
|
+
|
|
13642
13446
|
// src/service/annotations.ts
|
|
13643
13447
|
import { Annotations } from "@malloydata/malloy";
|
|
13644
13448
|
function isReservedRoute(route) {
|
|
@@ -13905,7 +13709,7 @@ var init_filter = __esm(() => {
|
|
|
13905
13709
|
});
|
|
13906
13710
|
|
|
13907
13711
|
// src/service/gate_registry_walk.ts
|
|
13908
|
-
import { isSourceDef } from "@malloydata/malloy";
|
|
13712
|
+
import { isSourceDef as isSourceDef2 } from "@malloydata/malloy";
|
|
13909
13713
|
function resolveDeclaredSource(struct, modelDef) {
|
|
13910
13714
|
if (!modelDef)
|
|
13911
13715
|
return { kind: "none" };
|
|
@@ -13917,7 +13721,7 @@ function resolveDeclaredSource(struct, modelDef) {
|
|
|
13917
13721
|
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
13918
13722
|
if (declared === struct)
|
|
13919
13723
|
continue;
|
|
13920
|
-
if (!declared || !
|
|
13724
|
+
if (!declared || !isSourceDef2(declared)) {
|
|
13921
13725
|
sawBrokenEntry = true;
|
|
13922
13726
|
continue;
|
|
13923
13727
|
}
|
|
@@ -13952,7 +13756,7 @@ function resolveQuerySourceBase(struct, modelDef) {
|
|
|
13952
13756
|
return;
|
|
13953
13757
|
const ref = duck.query?.structRef;
|
|
13954
13758
|
const base = typeof ref === "string" ? modelDef?.contents[ref] : ref;
|
|
13955
|
-
return base &&
|
|
13759
|
+
return base && isSourceDef2(base) ? base : undefined;
|
|
13956
13760
|
}
|
|
13957
13761
|
function resolveCompositeResolvedBase(struct) {
|
|
13958
13762
|
const duck = struct;
|
|
@@ -14011,8 +13815,8 @@ var init_gate_registry_walk = __esm(() => {
|
|
|
14011
13815
|
|
|
14012
13816
|
// src/service/source_extraction.ts
|
|
14013
13817
|
import {
|
|
14014
|
-
isJoined,
|
|
14015
|
-
isSourceDef as
|
|
13818
|
+
isJoined as isJoined2,
|
|
13819
|
+
isSourceDef as isSourceDef3
|
|
14016
13820
|
} from "@malloydata/malloy";
|
|
14017
13821
|
function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
14018
13822
|
const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
|
|
@@ -14023,26 +13827,40 @@ function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
|
|
|
14023
13827
|
if (!entry)
|
|
14024
13828
|
continue;
|
|
14025
13829
|
const declared = entry.type === "source_registry_reference" ? modelDef.contents[entry.name] : entry;
|
|
14026
|
-
if (declared &&
|
|
13830
|
+
if (declared && isSourceDef3(declared))
|
|
14027
13831
|
return false;
|
|
14028
13832
|
}
|
|
14029
13833
|
return true;
|
|
14030
13834
|
}
|
|
13835
|
+
function isEarlierPosition(a, b) {
|
|
13836
|
+
return a.line < b.line || a.line === b.line && a.character < b.character;
|
|
13837
|
+
}
|
|
13838
|
+
function considerAuthorizeNoteOwner(declaredBy, note, candidate) {
|
|
13839
|
+
const location = candidate.location;
|
|
13840
|
+
if (!location || location.url !== note.at.url)
|
|
13841
|
+
return;
|
|
13842
|
+
const currentLocation = declaredBy.get(note)?.location;
|
|
13843
|
+
if (!currentLocation || isEarlierPosition(location.range.start, currentLocation.range.start)) {
|
|
13844
|
+
declaredBy.set(note, candidate);
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
14031
13847
|
function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
14032
13848
|
const filterMap = new Map;
|
|
14033
13849
|
const authorizeMap = new Map;
|
|
14034
13850
|
const misplacedAuthorize = [];
|
|
14035
13851
|
const gatedSourceOwnAuthorizeNotes = new Set;
|
|
13852
|
+
const authorizeNoteDeclaredBy = new Map;
|
|
14036
13853
|
const nearMissAuthorize = [];
|
|
14037
13854
|
const sweptStructs = [];
|
|
14038
13855
|
for (const obj of Object.values(modelDef.contents)) {
|
|
14039
|
-
if (!
|
|
13856
|
+
if (!isSourceDef3(obj))
|
|
14040
13857
|
continue;
|
|
14041
13858
|
const struct = obj;
|
|
14042
13859
|
sweptStructs.push(obj);
|
|
14043
13860
|
for (const note of ownLevelNotes(struct.annotations)) {
|
|
14044
13861
|
if (containsAuthorizeAnnotationTag([note.text])) {
|
|
14045
13862
|
gatedSourceOwnAuthorizeNotes.add(note);
|
|
13863
|
+
considerAuthorizeNoteOwner(authorizeNoteDeclaredBy, note, struct);
|
|
14046
13864
|
}
|
|
14047
13865
|
}
|
|
14048
13866
|
nearMissAuthorize.push(...collectAuthorizeNearMisses([
|
|
@@ -14054,12 +13872,13 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
14054
13872
|
const entry = value.entry;
|
|
14055
13873
|
if (entry.type === "source_registry_reference")
|
|
14056
13874
|
continue;
|
|
14057
|
-
if (!
|
|
13875
|
+
if (!isSourceDef3(entry))
|
|
14058
13876
|
continue;
|
|
14059
13877
|
sweptStructs.push(entry);
|
|
14060
13878
|
for (const note of ownLevelNotes(entry.annotations)) {
|
|
14061
13879
|
if (containsAuthorizeAnnotationTag([note.text])) {
|
|
14062
13880
|
gatedSourceOwnAuthorizeNotes.add(note);
|
|
13881
|
+
considerAuthorizeNoteOwner(authorizeNoteDeclaredBy, note, entry);
|
|
14063
13882
|
}
|
|
14064
13883
|
}
|
|
14065
13884
|
nearMissAuthorize.push(...collectAuthorizeNearMisses(ownLevelNotes(entry.annotations).map((note) => note.text)));
|
|
@@ -14076,10 +13895,11 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
14076
13895
|
}
|
|
14077
13896
|
assertNoAuthorizeNearMisses(nearMissAuthorize);
|
|
14078
13897
|
const authorizeOwnNotes = new Map;
|
|
13898
|
+
const attributedAuthorizeOwnNotes = new Map;
|
|
14079
13899
|
if (containsAuthorizeAnnotationTag((modelAnnotations(modelDef).notes ?? []).map((note) => note.text))) {
|
|
14080
13900
|
misplacedAuthorize.push({ kind: "file" });
|
|
14081
13901
|
}
|
|
14082
|
-
const sources = Object.values(modelDef.contents).filter((obj) =>
|
|
13902
|
+
const sources = Object.values(modelDef.contents).filter((obj) => isSourceDef3(obj)).map((sourceObj) => {
|
|
14083
13903
|
const struct = sourceObj;
|
|
14084
13904
|
const sourceName = struct.as || struct.name;
|
|
14085
13905
|
const annotations = annotationTexts(struct.annotations);
|
|
@@ -14117,7 +13937,9 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
14117
13937
|
}
|
|
14118
13938
|
const ownNotes = ownLevelNoteTexts(struct.annotations);
|
|
14119
13939
|
const ownGates = collectAuthorizeExprs(ownNotes);
|
|
14120
|
-
|
|
13940
|
+
const ownAuthorizeNotes = ownLevelNotes(struct.annotations).filter((note) => containsAuthorizeAnnotationTag([note.text]));
|
|
13941
|
+
authorizeOwnNotes.set(sourceName, ownAuthorizeNotes);
|
|
13942
|
+
attributedAuthorizeOwnNotes.set(sourceName, ownAuthorizeNotes.filter((note) => authorizeNoteDeclaredBy.get(note) === struct));
|
|
14121
13943
|
const inheritedGroups = ownGates.length === 0 ? effectiveAncestorGateExprs(struct, modelDef) : [];
|
|
14122
13944
|
const effectiveGroups = ownGates.length > 0 ? [ownGates] : inheritedGroups;
|
|
14123
13945
|
let authorize;
|
|
@@ -14137,7 +13959,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
14137
13959
|
continue;
|
|
14138
13960
|
}
|
|
14139
13961
|
const fieldName = field.as || field.name;
|
|
14140
|
-
if (
|
|
13962
|
+
if (isJoined2(field) && isSourceDef3(field)) {
|
|
14141
13963
|
const joinedStruct = field;
|
|
14142
13964
|
if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
|
|
14143
13965
|
continue;
|
|
@@ -14169,7 +13991,8 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
|
|
|
14169
13991
|
filterMap,
|
|
14170
13992
|
authorizeMap,
|
|
14171
13993
|
misplacedAuthorize,
|
|
14172
|
-
authorizeOwnNotes
|
|
13994
|
+
authorizeOwnNotes,
|
|
13995
|
+
attributedAuthorizeOwnNotes
|
|
14173
13996
|
};
|
|
14174
13997
|
}
|
|
14175
13998
|
function extractQueriesFromModelDef(modelDef) {
|
|
@@ -14570,9 +14393,11 @@ init_authorize_metrics();
|
|
|
14570
14393
|
init_data_styles();
|
|
14571
14394
|
init_errors();
|
|
14572
14395
|
init_authorize();
|
|
14396
|
+
init_gate_dimension();
|
|
14573
14397
|
var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
|
|
14574
14398
|
import {
|
|
14575
14399
|
contextOverlay,
|
|
14400
|
+
isSourceDef as isSourceDef4,
|
|
14576
14401
|
MalloyConfig,
|
|
14577
14402
|
MalloyError as MalloyError2,
|
|
14578
14403
|
modelDefToModelInfo,
|
|
@@ -15012,14 +14837,16 @@ function extractSources(modelDef, givens) {
|
|
|
15012
14837
|
filterMap,
|
|
15013
14838
|
authorizeMap,
|
|
15014
14839
|
misplacedAuthorize,
|
|
15015
|
-
authorizeOwnNotes
|
|
14840
|
+
authorizeOwnNotes,
|
|
14841
|
+
attributedAuthorizeOwnNotes
|
|
15016
14842
|
} = extractSourcesFromModelDef(modelDef, givens);
|
|
15017
14843
|
return {
|
|
15018
14844
|
sources,
|
|
15019
14845
|
filterMap,
|
|
15020
14846
|
authorizeMap,
|
|
15021
14847
|
misplacedAuthorize,
|
|
15022
|
-
authorizeOwnNotes
|
|
14848
|
+
authorizeOwnNotes,
|
|
14849
|
+
attributedAuthorizeOwnNotes
|
|
15023
14850
|
};
|
|
15024
14851
|
}
|
|
15025
14852
|
function authorizeWarningCollector() {
|
|
@@ -15031,12 +14858,6 @@ function authorizeWarningCollector() {
|
|
|
15031
14858
|
}
|
|
15032
14859
|
};
|
|
15033
14860
|
}
|
|
15034
|
-
function givenDeclaredTypes(givens) {
|
|
15035
|
-
return new Map((givens ?? []).filter((g) => g.name != null && g.type != null).map((g) => [g.name, g.type]));
|
|
15036
|
-
}
|
|
15037
|
-
function givenDeclaredDefaults(givens) {
|
|
15038
|
-
return new Map((givens ?? []).filter((g) => g.name != null && g.default != null).map((g) => [g.name, g.default]));
|
|
15039
|
-
}
|
|
15040
14861
|
function extractQueries(modelDef) {
|
|
15041
14862
|
const { queries, misplacedAuthorize } = extractQueriesFromModelDef(modelDef);
|
|
15042
14863
|
return {
|
|
@@ -15074,7 +14895,8 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
15074
14895
|
filterMap,
|
|
15075
14896
|
authorizeMap,
|
|
15076
14897
|
misplacedAuthorize,
|
|
15077
|
-
authorizeOwnNotes
|
|
14898
|
+
authorizeOwnNotes,
|
|
14899
|
+
attributedAuthorizeOwnNotes
|
|
15078
14900
|
} = extractSources(modelDef, givens);
|
|
15079
14901
|
const queryResult = extractQueries(modelDef);
|
|
15080
14902
|
const queries = queryResult.queries;
|
|
@@ -15082,14 +14904,25 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
|
|
|
15082
14904
|
...misplacedAuthorize,
|
|
15083
14905
|
...queryResult.misplacedAuthorize
|
|
15084
14906
|
]);
|
|
14907
|
+
const legacyStringGates = findLegacyStringGates(authorizeOwnNotes);
|
|
14908
|
+
legacyStringGates.forEach(() => recordRowLevelGateRejected("legacy_string_gate"));
|
|
14909
|
+
assertNoLegacyStringGate(legacyStringGates);
|
|
14910
|
+
assertAtMostOneAuthorizeGate(findMultipleAuthorizeGates(authorizeOwnNotes));
|
|
15085
14911
|
const authorizeWarningCollection = authorizeWarningCollector();
|
|
15086
14912
|
await validateAuthorizeProbes(mm, {
|
|
15087
14913
|
authorizeMap,
|
|
15088
|
-
|
|
15089
|
-
declaredDefaults: givenDeclaredDefaults(givens),
|
|
15090
|
-
authorizeOwnNotes,
|
|
14914
|
+
authorizeOwnNotes: attributedAuthorizeOwnNotes,
|
|
15091
14915
|
onRowLevelGateRejected: recordRowLevelGateRejected,
|
|
15092
|
-
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible
|
|
14916
|
+
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
14917
|
+
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
14918
|
+
const struct = modelDef.contents[sourceName];
|
|
14919
|
+
if (!struct || !isSourceDef4(struct))
|
|
14920
|
+
return;
|
|
14921
|
+
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, modelDef, (cause, detail) => {
|
|
14922
|
+
recordRowLevelGateRejected(cause);
|
|
14923
|
+
authorizeWarningCollection.warnings.push(`Row-level #(authorize) gate warning on "${sourceName}" (${cause}): ${detail}`);
|
|
14924
|
+
});
|
|
14925
|
+
}
|
|
15093
14926
|
});
|
|
15094
14927
|
return {
|
|
15095
14928
|
modelPath,
|
|
@@ -15209,13 +15042,25 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
|
|
|
15209
15042
|
...extracted.misplacedAuthorize,
|
|
15210
15043
|
...finalQueryResult.misplacedAuthorize
|
|
15211
15044
|
]);
|
|
15045
|
+
const finalLegacyStringGates = findLegacyStringGates(extracted.authorizeOwnNotes);
|
|
15046
|
+
finalLegacyStringGates.forEach(() => recordRowLevelGateRejected("legacy_string_gate"));
|
|
15047
|
+
assertNoLegacyStringGate(finalLegacyStringGates);
|
|
15048
|
+
assertAtMostOneAuthorizeGate(findMultipleAuthorizeGates(extracted.authorizeOwnNotes));
|
|
15049
|
+
const finalCompiledModelDef = finalModelDef;
|
|
15212
15050
|
await validateAuthorizeProbes(mm, {
|
|
15213
15051
|
authorizeMap: extracted.authorizeMap,
|
|
15214
|
-
|
|
15215
|
-
declaredDefaults: givenDeclaredDefaults(finalGivens),
|
|
15216
|
-
authorizeOwnNotes: extracted.authorizeOwnNotes,
|
|
15052
|
+
authorizeOwnNotes: extracted.attributedAuthorizeOwnNotes,
|
|
15217
15053
|
onRowLevelGateRejected: recordRowLevelGateRejected,
|
|
15218
|
-
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible
|
|
15054
|
+
onRowLevelGateUnexpressible: authorizeWarningCollection.onRowLevelGateUnexpressible,
|
|
15055
|
+
onOwnRowLevelConditionCompiled: (sourceName, condition) => {
|
|
15056
|
+
const struct = finalCompiledModelDef.contents[sourceName];
|
|
15057
|
+
if (!struct || !isSourceDef4(struct))
|
|
15058
|
+
return;
|
|
15059
|
+
validateSourceLineGateGivenUsage(sourceName, struct, condition.refSummary, condition.e, finalCompiledModelDef, (cause, detail) => {
|
|
15060
|
+
recordRowLevelGateRejected(cause);
|
|
15061
|
+
authorizeWarningCollection.warnings.push(`Row-level #(authorize) gate warning on "${sourceName}" (${cause}): ${detail}`);
|
|
15062
|
+
});
|
|
15063
|
+
}
|
|
15219
15064
|
});
|
|
15220
15065
|
}
|
|
15221
15066
|
return {
|