@granular-software/sdk 0.4.36 → 0.4.37
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/README.md +82 -2
- package/dist/agent-evals.d.mts +64 -3
- package/dist/agent-evals.d.ts +64 -3
- package/dist/agent-evals.js +2486 -601
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +2486 -601
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +39 -4
- package/dist/agent-harness.d.ts +39 -4
- package/dist/agent-harness.js +1051 -456
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +1049 -457
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +2335 -289
- package/dist/{client-Cq8onk2D.d.mts → client-eE9nTfvp.d.mts} +109 -28
- package/dist/{client-Cq8onk2D.d.ts → client-eE9nTfvp.d.ts} +109 -28
- package/dist/index.d.mts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1930 -571
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1927 -572
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -1194,8 +1194,8 @@ var require_command = __commonJS({
|
|
|
1194
1194
|
"../../node_modules/commander/lib/command.js"(exports) {
|
|
1195
1195
|
var EventEmitter = __require("events").EventEmitter;
|
|
1196
1196
|
var childProcess = __require("child_process");
|
|
1197
|
-
var
|
|
1198
|
-
var
|
|
1197
|
+
var path7 = __require("path");
|
|
1198
|
+
var fs8 = __require("fs");
|
|
1199
1199
|
var process10 = __require("process");
|
|
1200
1200
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1201
1201
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2175,7 +2175,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2175
2175
|
* @param {string} subcommandName
|
|
2176
2176
|
*/
|
|
2177
2177
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2178
|
-
if (
|
|
2178
|
+
if (fs8.existsSync(executableFile)) return;
|
|
2179
2179
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2180
2180
|
const executableMissing = `'${executableFile}' does not exist
|
|
2181
2181
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2193,11 +2193,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2193
2193
|
let launchWithNode = false;
|
|
2194
2194
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2195
2195
|
function findFile(baseDir, baseName) {
|
|
2196
|
-
const localBin =
|
|
2197
|
-
if (
|
|
2198
|
-
if (sourceExt.includes(
|
|
2196
|
+
const localBin = path7.resolve(baseDir, baseName);
|
|
2197
|
+
if (fs8.existsSync(localBin)) return localBin;
|
|
2198
|
+
if (sourceExt.includes(path7.extname(baseName))) return void 0;
|
|
2199
2199
|
const foundExt = sourceExt.find(
|
|
2200
|
-
(ext) =>
|
|
2200
|
+
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
2201
2201
|
);
|
|
2202
2202
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2203
2203
|
return void 0;
|
|
@@ -2209,21 +2209,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2209
2209
|
if (this._scriptPath) {
|
|
2210
2210
|
let resolvedScriptPath;
|
|
2211
2211
|
try {
|
|
2212
|
-
resolvedScriptPath =
|
|
2212
|
+
resolvedScriptPath = fs8.realpathSync(this._scriptPath);
|
|
2213
2213
|
} catch {
|
|
2214
2214
|
resolvedScriptPath = this._scriptPath;
|
|
2215
2215
|
}
|
|
2216
|
-
executableDir =
|
|
2217
|
-
|
|
2216
|
+
executableDir = path7.resolve(
|
|
2217
|
+
path7.dirname(resolvedScriptPath),
|
|
2218
2218
|
executableDir
|
|
2219
2219
|
);
|
|
2220
2220
|
}
|
|
2221
2221
|
if (executableDir) {
|
|
2222
2222
|
let localFile = findFile(executableDir, executableFile);
|
|
2223
2223
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2224
|
-
const legacyName =
|
|
2224
|
+
const legacyName = path7.basename(
|
|
2225
2225
|
this._scriptPath,
|
|
2226
|
-
|
|
2226
|
+
path7.extname(this._scriptPath)
|
|
2227
2227
|
);
|
|
2228
2228
|
if (legacyName !== this._name) {
|
|
2229
2229
|
localFile = findFile(
|
|
@@ -2234,7 +2234,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2234
2234
|
}
|
|
2235
2235
|
executableFile = localFile || executableFile;
|
|
2236
2236
|
}
|
|
2237
|
-
launchWithNode = sourceExt.includes(
|
|
2237
|
+
launchWithNode = sourceExt.includes(path7.extname(executableFile));
|
|
2238
2238
|
let proc;
|
|
2239
2239
|
if (process10.platform !== "win32") {
|
|
2240
2240
|
if (launchWithNode) {
|
|
@@ -3081,7 +3081,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3081
3081
|
* @return {Command}
|
|
3082
3082
|
*/
|
|
3083
3083
|
nameFromFilename(filename) {
|
|
3084
|
-
this._name =
|
|
3084
|
+
this._name = path7.basename(filename, path7.extname(filename));
|
|
3085
3085
|
return this;
|
|
3086
3086
|
}
|
|
3087
3087
|
/**
|
|
@@ -3095,9 +3095,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3095
3095
|
* @param {string} [path]
|
|
3096
3096
|
* @return {(string|null|Command)}
|
|
3097
3097
|
*/
|
|
3098
|
-
executableDir(
|
|
3099
|
-
if (
|
|
3100
|
-
this._executableDir =
|
|
3098
|
+
executableDir(path8) {
|
|
3099
|
+
if (path8 === void 0) return this._executableDir;
|
|
3100
|
+
this._executableDir = path8;
|
|
3101
3101
|
return this;
|
|
3102
3102
|
}
|
|
3103
3103
|
/**
|
|
@@ -3440,8 +3440,8 @@ var require_package = __commonJS({
|
|
|
3440
3440
|
// ../../node_modules/dotenv/lib/main.js
|
|
3441
3441
|
var require_main = __commonJS({
|
|
3442
3442
|
"../../node_modules/dotenv/lib/main.js"(exports, module) {
|
|
3443
|
-
var
|
|
3444
|
-
var
|
|
3443
|
+
var fs8 = __require("fs");
|
|
3444
|
+
var path7 = __require("path");
|
|
3445
3445
|
var os2 = __require("os");
|
|
3446
3446
|
var crypto2 = __require("crypto");
|
|
3447
3447
|
var packageJson = require_package();
|
|
@@ -3549,7 +3549,7 @@ var require_main = __commonJS({
|
|
|
3549
3549
|
if (options && options.path && options.path.length > 0) {
|
|
3550
3550
|
if (Array.isArray(options.path)) {
|
|
3551
3551
|
for (const filepath of options.path) {
|
|
3552
|
-
if (
|
|
3552
|
+
if (fs8.existsSync(filepath)) {
|
|
3553
3553
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
3554
3554
|
}
|
|
3555
3555
|
}
|
|
@@ -3557,15 +3557,15 @@ var require_main = __commonJS({
|
|
|
3557
3557
|
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
3558
3558
|
}
|
|
3559
3559
|
} else {
|
|
3560
|
-
possibleVaultPath =
|
|
3560
|
+
possibleVaultPath = path7.resolve(process.cwd(), ".env.vault");
|
|
3561
3561
|
}
|
|
3562
|
-
if (
|
|
3562
|
+
if (fs8.existsSync(possibleVaultPath)) {
|
|
3563
3563
|
return possibleVaultPath;
|
|
3564
3564
|
}
|
|
3565
3565
|
return null;
|
|
3566
3566
|
}
|
|
3567
3567
|
function _resolveHome(envPath) {
|
|
3568
|
-
return envPath[0] === "~" ?
|
|
3568
|
+
return envPath[0] === "~" ? path7.join(os2.homedir(), envPath.slice(1)) : envPath;
|
|
3569
3569
|
}
|
|
3570
3570
|
function _configVault(options) {
|
|
3571
3571
|
const debug = Boolean(options && options.debug);
|
|
@@ -3582,7 +3582,7 @@ var require_main = __commonJS({
|
|
|
3582
3582
|
return { parsed };
|
|
3583
3583
|
}
|
|
3584
3584
|
function configDotenv(options) {
|
|
3585
|
-
const dotenvPath =
|
|
3585
|
+
const dotenvPath = path7.resolve(process.cwd(), ".env");
|
|
3586
3586
|
let encoding = "utf8";
|
|
3587
3587
|
const debug = Boolean(options && options.debug);
|
|
3588
3588
|
const quiet = options && "quiet" in options ? options.quiet : true;
|
|
@@ -3606,13 +3606,13 @@ var require_main = __commonJS({
|
|
|
3606
3606
|
}
|
|
3607
3607
|
let lastError;
|
|
3608
3608
|
const parsedAll = {};
|
|
3609
|
-
for (const
|
|
3609
|
+
for (const path8 of optionPaths) {
|
|
3610
3610
|
try {
|
|
3611
|
-
const parsed = DotenvModule.parse(
|
|
3611
|
+
const parsed = DotenvModule.parse(fs8.readFileSync(path8, { encoding }));
|
|
3612
3612
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
3613
3613
|
} catch (e) {
|
|
3614
3614
|
if (debug) {
|
|
3615
|
-
_debug(`Failed to load ${
|
|
3615
|
+
_debug(`Failed to load ${path8} ${e.message}`);
|
|
3616
3616
|
}
|
|
3617
3617
|
lastError = e;
|
|
3618
3618
|
}
|
|
@@ -3627,8 +3627,8 @@ var require_main = __commonJS({
|
|
|
3627
3627
|
const shortPaths = [];
|
|
3628
3628
|
for (const filePath of optionPaths) {
|
|
3629
3629
|
try {
|
|
3630
|
-
const
|
|
3631
|
-
shortPaths.push(
|
|
3630
|
+
const relative2 = path7.relative(process.cwd(), filePath);
|
|
3631
|
+
shortPaths.push(relative2);
|
|
3632
3632
|
} catch (e) {
|
|
3633
3633
|
if (debug) {
|
|
3634
3634
|
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
@@ -5561,7 +5561,7 @@ var LIBRARY_TEMPLATE = {
|
|
|
5561
5561
|
userId: "reader_demo",
|
|
5562
5562
|
name: "Mina Reader",
|
|
5563
5563
|
email: "mina@example.com",
|
|
5564
|
-
permissions: ["
|
|
5564
|
+
permissions: ["allow-all"]
|
|
5565
5565
|
},
|
|
5566
5566
|
seedRecords: [
|
|
5567
5567
|
{
|
|
@@ -5937,7 +5937,7 @@ var SUPPORT_TEMPLATE = {
|
|
|
5937
5937
|
userId: "support_demo",
|
|
5938
5938
|
name: "Taylor Support",
|
|
5939
5939
|
email: "taylor@example.com",
|
|
5940
|
-
permissions: ["
|
|
5940
|
+
permissions: ["allow-all"]
|
|
5941
5941
|
},
|
|
5942
5942
|
seedRecords: [
|
|
5943
5943
|
{
|
|
@@ -6253,7 +6253,7 @@ var DELIVERY_TEMPLATE = {
|
|
|
6253
6253
|
userId: "delivery_demo",
|
|
6254
6254
|
name: "Alex Delivery",
|
|
6255
6255
|
email: "alex@example.com",
|
|
6256
|
-
permissions: ["
|
|
6256
|
+
permissions: ["allow-all"]
|
|
6257
6257
|
},
|
|
6258
6258
|
seedRecords: [
|
|
6259
6259
|
{
|
|
@@ -6769,10 +6769,839 @@ return {
|
|
|
6769
6769
|
`;
|
|
6770
6770
|
}
|
|
6771
6771
|
|
|
6772
|
+
// ../policy-engine/src/index.ts
|
|
6773
|
+
var DECISIONS = /* @__PURE__ */ new Set(["allow", "confirm", "deny"]);
|
|
6774
|
+
var BUILTIN_PROFILE_NAMES = ["allow-all", "confirm-all", "deny-all"];
|
|
6775
|
+
function getBuiltinPermissionProfiles() {
|
|
6776
|
+
return [
|
|
6777
|
+
{
|
|
6778
|
+
schemaVersion: 1,
|
|
6779
|
+
name: "allow-all",
|
|
6780
|
+
label: "Allow all",
|
|
6781
|
+
description: "Every declared action is visible unless a manifest policy denies it.",
|
|
6782
|
+
source: "builtin",
|
|
6783
|
+
defaults: { actionPolicy: "allow" },
|
|
6784
|
+
policies: [],
|
|
6785
|
+
actions: []
|
|
6786
|
+
},
|
|
6787
|
+
{
|
|
6788
|
+
schemaVersion: 1,
|
|
6789
|
+
name: "confirm-all",
|
|
6790
|
+
label: "Confirm all",
|
|
6791
|
+
description: "Every declared action requires user confirmation before execution.",
|
|
6792
|
+
source: "builtin",
|
|
6793
|
+
defaults: { actionPolicy: "confirm" },
|
|
6794
|
+
policies: [],
|
|
6795
|
+
actions: []
|
|
6796
|
+
},
|
|
6797
|
+
{
|
|
6798
|
+
schemaVersion: 1,
|
|
6799
|
+
name: "deny-all",
|
|
6800
|
+
label: "Deny all",
|
|
6801
|
+
description: "No declared action is invocable.",
|
|
6802
|
+
source: "builtin",
|
|
6803
|
+
defaults: { actionPolicy: "deny" },
|
|
6804
|
+
policies: [],
|
|
6805
|
+
actions: []
|
|
6806
|
+
}
|
|
6807
|
+
];
|
|
6808
|
+
}
|
|
6809
|
+
function stableStringify(value) {
|
|
6810
|
+
return JSON.stringify(sortJson(value));
|
|
6811
|
+
}
|
|
6812
|
+
async function digestJson(value) {
|
|
6813
|
+
const payload = stableStringify(value);
|
|
6814
|
+
const cryptoLike = globalThis.crypto;
|
|
6815
|
+
if (cryptoLike?.subtle) {
|
|
6816
|
+
const data = new TextEncoder().encode(payload);
|
|
6817
|
+
const hash2 = await cryptoLike.subtle.digest("SHA-256", data);
|
|
6818
|
+
const hex = Array.from(new Uint8Array(hash2)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
6819
|
+
return `sha256:${hex}`;
|
|
6820
|
+
}
|
|
6821
|
+
let hash = 2166136261;
|
|
6822
|
+
for (let index = 0; index < payload.length; index += 1) {
|
|
6823
|
+
hash ^= payload.charCodeAt(index);
|
|
6824
|
+
hash = Math.imul(hash, 16777619);
|
|
6825
|
+
}
|
|
6826
|
+
return `fnv1a:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
6827
|
+
}
|
|
6828
|
+
function sortJson(value) {
|
|
6829
|
+
if (Array.isArray(value)) return value.map(sortJson);
|
|
6830
|
+
if (isRecord(value)) {
|
|
6831
|
+
return Object.fromEntries(
|
|
6832
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortJson(child)])
|
|
6833
|
+
);
|
|
6834
|
+
}
|
|
6835
|
+
return value;
|
|
6836
|
+
}
|
|
6837
|
+
function isRecord(value) {
|
|
6838
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6839
|
+
}
|
|
6840
|
+
function normalizeProfileName(name) {
|
|
6841
|
+
if (typeof name !== "string") return null;
|
|
6842
|
+
const trimmed = name.trim();
|
|
6843
|
+
if (!/^[a-z0-9][a-z0-9._-]{1,63}$/.test(trimmed)) return null;
|
|
6844
|
+
return trimmed;
|
|
6845
|
+
}
|
|
6846
|
+
function normalizeDecision(value) {
|
|
6847
|
+
return DECISIONS.has(value) ? value : null;
|
|
6848
|
+
}
|
|
6849
|
+
function normalizePath(value) {
|
|
6850
|
+
if (Array.isArray(value)) {
|
|
6851
|
+
return value.map((part) => String(part)).filter(Boolean);
|
|
6852
|
+
}
|
|
6853
|
+
if (typeof value === "string") {
|
|
6854
|
+
return value.includes(".") ? value.split(".").filter(Boolean) : [value];
|
|
6855
|
+
}
|
|
6856
|
+
return [];
|
|
6857
|
+
}
|
|
6858
|
+
function firstDefinedValue(spec) {
|
|
6859
|
+
if ("value" in spec) return spec.value;
|
|
6860
|
+
if ("stringValue" in spec) return spec.stringValue;
|
|
6861
|
+
if ("numberValue" in spec) return spec.numberValue;
|
|
6862
|
+
if ("booleanValue" in spec) return spec.booleanValue;
|
|
6863
|
+
if ("state" in spec) return spec.state;
|
|
6864
|
+
return void 0;
|
|
6865
|
+
}
|
|
6866
|
+
function normalizeCondition(input) {
|
|
6867
|
+
if (input === void 0 || input === null) return { kind: "always" };
|
|
6868
|
+
if (!isRecord(input)) {
|
|
6869
|
+
throw new Error("Policy condition must be an object");
|
|
6870
|
+
}
|
|
6871
|
+
if (Array.isArray(input.all)) {
|
|
6872
|
+
return {
|
|
6873
|
+
kind: "all",
|
|
6874
|
+
conditions: input.all.map((item) => normalizeCondition(item))
|
|
6875
|
+
};
|
|
6876
|
+
}
|
|
6877
|
+
if (Array.isArray(input.any)) {
|
|
6878
|
+
return {
|
|
6879
|
+
kind: "any",
|
|
6880
|
+
conditions: input.any.map((item) => normalizeCondition(item))
|
|
6881
|
+
};
|
|
6882
|
+
}
|
|
6883
|
+
if (input.not !== void 0) {
|
|
6884
|
+
return { kind: "not", condition: normalizeCondition(input.not) };
|
|
6885
|
+
}
|
|
6886
|
+
for (const source of ["input", "object", "stateMachine"]) {
|
|
6887
|
+
const raw = input[source];
|
|
6888
|
+
if (!isRecord(raw)) continue;
|
|
6889
|
+
const operator = raw.operator;
|
|
6890
|
+
if (operator !== "eq" && operator !== "neq" && operator !== "gt" && operator !== "gte" && operator !== "lt" && operator !== "lte" && operator !== "contains" && operator !== "not_contains" && operator !== "starts_with" && operator !== "ends_with" && operator !== "exists") {
|
|
6891
|
+
throw new Error(`Unsupported policy operator: ${String(operator)}`);
|
|
6892
|
+
}
|
|
6893
|
+
if (source === "stateMachine") {
|
|
6894
|
+
const machine = typeof raw.machine === "string" ? raw.machine : "";
|
|
6895
|
+
if (!machine) throw new Error("stateMachine condition requires machine");
|
|
6896
|
+
return {
|
|
6897
|
+
kind: "predicate",
|
|
6898
|
+
source,
|
|
6899
|
+
path: [machine],
|
|
6900
|
+
machine,
|
|
6901
|
+
operator,
|
|
6902
|
+
value: firstDefinedValue(raw)
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
const path7 = normalizePath(raw.path ?? raw.field ?? raw.input);
|
|
6906
|
+
if (path7.length === 0) {
|
|
6907
|
+
throw new Error(`${source} condition requires a path`);
|
|
6908
|
+
}
|
|
6909
|
+
return {
|
|
6910
|
+
kind: "predicate",
|
|
6911
|
+
source,
|
|
6912
|
+
path: path7,
|
|
6913
|
+
operator,
|
|
6914
|
+
value: firstDefinedValue(raw)
|
|
6915
|
+
};
|
|
6916
|
+
}
|
|
6917
|
+
throw new Error(
|
|
6918
|
+
"Policy condition must contain all, any, not, input, object, or stateMachine"
|
|
6919
|
+
);
|
|
6920
|
+
}
|
|
6921
|
+
function readPath(root, path7) {
|
|
6922
|
+
let current = root;
|
|
6923
|
+
for (const part of path7) {
|
|
6924
|
+
if (!isRecord(current) && !Array.isArray(current)) {
|
|
6925
|
+
return { exists: false, value: void 0 };
|
|
6926
|
+
}
|
|
6927
|
+
if (!(part in current)) {
|
|
6928
|
+
return { exists: false, value: void 0 };
|
|
6929
|
+
}
|
|
6930
|
+
current = current[part];
|
|
6931
|
+
}
|
|
6932
|
+
return { exists: true, value: current };
|
|
6933
|
+
}
|
|
6934
|
+
function evaluateCondition(condition, context) {
|
|
6935
|
+
switch (condition.kind) {
|
|
6936
|
+
case "always":
|
|
6937
|
+
return true;
|
|
6938
|
+
case "all":
|
|
6939
|
+
return condition.conditions.every(
|
|
6940
|
+
(child) => evaluateCondition(child, context)
|
|
6941
|
+
);
|
|
6942
|
+
case "any":
|
|
6943
|
+
return condition.conditions.some(
|
|
6944
|
+
(child) => evaluateCondition(child, context)
|
|
6945
|
+
);
|
|
6946
|
+
case "not":
|
|
6947
|
+
return !evaluateCondition(condition.condition, context);
|
|
6948
|
+
case "predicate":
|
|
6949
|
+
return evaluatePredicate(condition, context);
|
|
6950
|
+
}
|
|
6951
|
+
}
|
|
6952
|
+
function evaluatePredicate(predicate, context) {
|
|
6953
|
+
const target = predicate.source === "input" ? context.input : predicate.source === "object" ? context.object : context.stateMachines;
|
|
6954
|
+
const { exists, value } = readPath(target || {}, predicate.path);
|
|
6955
|
+
if (predicate.operator === "exists") return exists;
|
|
6956
|
+
if (!exists) return false;
|
|
6957
|
+
const expected = predicate.value;
|
|
6958
|
+
switch (predicate.operator) {
|
|
6959
|
+
case "eq":
|
|
6960
|
+
return value === expected;
|
|
6961
|
+
case "neq":
|
|
6962
|
+
return value !== expected;
|
|
6963
|
+
case "gt":
|
|
6964
|
+
return typeof value === "number" && typeof expected === "number" && value > expected;
|
|
6965
|
+
case "gte":
|
|
6966
|
+
return typeof value === "number" && typeof expected === "number" && value >= expected;
|
|
6967
|
+
case "lt":
|
|
6968
|
+
return typeof value === "number" && typeof expected === "number" && value < expected;
|
|
6969
|
+
case "lte":
|
|
6970
|
+
return typeof value === "number" && typeof expected === "number" && value <= expected;
|
|
6971
|
+
case "contains":
|
|
6972
|
+
return Array.isArray(value) ? value.includes(expected) : typeof value === "string" && typeof expected === "string" && value.includes(expected);
|
|
6973
|
+
case "not_contains":
|
|
6974
|
+
return Array.isArray(value) ? !value.includes(expected) : typeof value === "string" && typeof expected === "string" && !value.includes(expected);
|
|
6975
|
+
case "starts_with":
|
|
6976
|
+
return typeof value === "string" && typeof expected === "string" && value.startsWith(expected);
|
|
6977
|
+
case "ends_with":
|
|
6978
|
+
return typeof value === "string" && typeof expected === "string" && value.endsWith(expected);
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
function matchRule(rule, context) {
|
|
6982
|
+
if (!evaluateCondition(rule.condition, context)) return null;
|
|
6983
|
+
return {
|
|
6984
|
+
id: rule.id,
|
|
6985
|
+
outcome: rule.outcome,
|
|
6986
|
+
origin: rule.origin,
|
|
6987
|
+
reason: rule.reason,
|
|
6988
|
+
summary: rule.summary
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6991
|
+
function evaluatePolicies(input) {
|
|
6992
|
+
const context = input.context || {};
|
|
6993
|
+
const manifestRules = input.manifestRules || [];
|
|
6994
|
+
const profileRules = input.profileRules || [];
|
|
6995
|
+
const defaultActionPolicy = input.defaultActionPolicy || "deny";
|
|
6996
|
+
const buckets = [
|
|
6997
|
+
{
|
|
6998
|
+
outcome: "deny",
|
|
6999
|
+
rules: manifestRules.filter((rule) => rule.outcome === "deny")
|
|
7000
|
+
},
|
|
7001
|
+
{
|
|
7002
|
+
outcome: "deny",
|
|
7003
|
+
rules: profileRules.filter((rule) => rule.outcome === "deny")
|
|
7004
|
+
},
|
|
7005
|
+
{
|
|
7006
|
+
outcome: "confirm",
|
|
7007
|
+
rules: manifestRules.filter((rule) => rule.outcome === "confirm")
|
|
7008
|
+
},
|
|
7009
|
+
{
|
|
7010
|
+
outcome: "confirm",
|
|
7011
|
+
rules: profileRules.filter((rule) => rule.outcome === "confirm")
|
|
7012
|
+
},
|
|
7013
|
+
{
|
|
7014
|
+
outcome: "allow",
|
|
7015
|
+
rules: profileRules.filter((rule) => rule.outcome === "allow")
|
|
7016
|
+
}
|
|
7017
|
+
];
|
|
7018
|
+
for (const bucket of buckets) {
|
|
7019
|
+
const matches = bucket.rules.map((rule) => matchRule(rule, context)).filter((match) => match !== null);
|
|
7020
|
+
if (matches.length > 0) return { outcome: bucket.outcome, matches };
|
|
7021
|
+
}
|
|
7022
|
+
return {
|
|
7023
|
+
outcome: defaultActionPolicy,
|
|
7024
|
+
matches: defaultActionPolicy === "deny" ? [] : [
|
|
7025
|
+
{
|
|
7026
|
+
id: `profile-default-${defaultActionPolicy}`,
|
|
7027
|
+
outcome: defaultActionPolicy,
|
|
7028
|
+
origin: {
|
|
7029
|
+
kind: "permissionProfile",
|
|
7030
|
+
profileName: "active",
|
|
7031
|
+
source: "permissionProfile"
|
|
7032
|
+
},
|
|
7033
|
+
summary: `Profile default policy is ${defaultActionPolicy}`
|
|
7034
|
+
}
|
|
7035
|
+
]
|
|
7036
|
+
};
|
|
7037
|
+
}
|
|
7038
|
+
function ruleSummary(outcome, reason, condition) {
|
|
7039
|
+
if (reason) return reason;
|
|
7040
|
+
if (condition.kind === "always") return `${capitalize(outcome)} always`;
|
|
7041
|
+
return `${capitalize(outcome)} when ${summarizeCondition(condition)}`;
|
|
7042
|
+
}
|
|
7043
|
+
function capitalize(value) {
|
|
7044
|
+
return value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
7045
|
+
}
|
|
7046
|
+
function summarizeCondition(condition) {
|
|
7047
|
+
switch (condition.kind) {
|
|
7048
|
+
case "always":
|
|
7049
|
+
return "always";
|
|
7050
|
+
case "all":
|
|
7051
|
+
return condition.conditions.map(summarizeCondition).join(" and ");
|
|
7052
|
+
case "any":
|
|
7053
|
+
return condition.conditions.map(summarizeCondition).join(" or ");
|
|
7054
|
+
case "not":
|
|
7055
|
+
return `not (${summarizeCondition(condition.condition)})`;
|
|
7056
|
+
case "predicate": {
|
|
7057
|
+
const path7 = condition.source === "stateMachine" ? `stateMachine.${condition.machine || condition.path.join(".")}` : `${condition.source}.${condition.path.join(".")}`;
|
|
7058
|
+
if (condition.operator === "exists") return `${path7} exists`;
|
|
7059
|
+
return `${path7} ${condition.operator} ${String(condition.value)}`;
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
function makeRule(input) {
|
|
7064
|
+
const condition = input.condition || { kind: "always" };
|
|
7065
|
+
return {
|
|
7066
|
+
...input,
|
|
7067
|
+
condition,
|
|
7068
|
+
summary: ruleSummary(input.outcome, input.reason, condition)
|
|
7069
|
+
};
|
|
7070
|
+
}
|
|
7071
|
+
function actionIdentity(action, on) {
|
|
7072
|
+
return on ? `${on}:${action}` : `global:${action}`;
|
|
7073
|
+
}
|
|
7074
|
+
function normalizeAttachedClass(value) {
|
|
7075
|
+
if (!value) return null;
|
|
7076
|
+
if (!value.startsWith("@")) return value;
|
|
7077
|
+
const slashIndex = value.indexOf("/");
|
|
7078
|
+
return slashIndex === -1 ? value : value.slice(slashIndex + 1);
|
|
7079
|
+
}
|
|
7080
|
+
function resolveEffectReference(effects2, action, on) {
|
|
7081
|
+
const normalizedOn = normalizeAttachedClass(on);
|
|
7082
|
+
const matches = effects2.filter((effect) => {
|
|
7083
|
+
if (effect.name !== action) return false;
|
|
7084
|
+
const attachedClass = normalizeAttachedClass(effect.attachedClass);
|
|
7085
|
+
return normalizedOn ? attachedClass === normalizedOn : !attachedClass;
|
|
7086
|
+
});
|
|
7087
|
+
if (matches.length === 1) return matches[0];
|
|
7088
|
+
if (!normalizedOn) {
|
|
7089
|
+
const byName = effects2.filter((effect) => effect.name === action);
|
|
7090
|
+
if (byName.length > 1) {
|
|
7091
|
+
const classes = byName.map(
|
|
7092
|
+
(effect) => normalizeAttachedClass(effect.attachedClass) || "global"
|
|
7093
|
+
).join(", ");
|
|
7094
|
+
throw new Error(
|
|
7095
|
+
`Action "${action}" exists on ${classes}. Add "on" to disambiguate.`
|
|
7096
|
+
);
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
if (matches.length > 1) {
|
|
7100
|
+
throw new Error(`Action "${action}" on "${String(on)}" is ambiguous.`);
|
|
7101
|
+
}
|
|
7102
|
+
throw new Error(
|
|
7103
|
+
on ? `Action "${action}" on "${on}" was not found.` : `Global action "${action}" was not found.`
|
|
7104
|
+
);
|
|
7105
|
+
}
|
|
7106
|
+
function manifestPolicyRulesForEffect(effect, manifestId) {
|
|
7107
|
+
const policies = isRecord(effect.metamodels?.policies) ? effect.metamodels?.policies : null;
|
|
7108
|
+
if (!policies) return [];
|
|
7109
|
+
const rules = [];
|
|
7110
|
+
const add2 = (outcome, specs, prefix) => {
|
|
7111
|
+
if (!Array.isArray(specs)) return;
|
|
7112
|
+
specs.forEach((spec, index) => {
|
|
7113
|
+
if (!isRecord(spec)) {
|
|
7114
|
+
throw new Error(
|
|
7115
|
+
`Manifest policy ${prefix}[${index}] for ${effect.effectKey} must be an object`
|
|
7116
|
+
);
|
|
7117
|
+
}
|
|
7118
|
+
const condition = normalizeCondition(spec.when);
|
|
7119
|
+
rules.push(
|
|
7120
|
+
makeRule({
|
|
7121
|
+
id: typeof spec.id === "string" ? spec.id : `manifest-${prefix}-${effect.effectKey}-${index + 1}`,
|
|
7122
|
+
origin: { kind: "manifest", manifestId, effectKey: effect.effectKey },
|
|
7123
|
+
outcome,
|
|
7124
|
+
reason: typeof spec.reason === "string" ? spec.reason : void 0,
|
|
7125
|
+
condition
|
|
7126
|
+
})
|
|
7127
|
+
);
|
|
7128
|
+
});
|
|
7129
|
+
};
|
|
7130
|
+
add2("deny", policies.denyWhen, "denyWhen");
|
|
7131
|
+
add2("confirm", policies.confirmWhen, "confirmWhen");
|
|
7132
|
+
add2("allow", policies.allowWhen, "allowWhen");
|
|
7133
|
+
return rules;
|
|
7134
|
+
}
|
|
7135
|
+
function inputSchemaHasField(effect, path7) {
|
|
7136
|
+
if (path7.length === 0) return false;
|
|
7137
|
+
const properties = isRecord(effect.inputSchema?.properties) ? effect.inputSchema?.properties : {};
|
|
7138
|
+
return path7[0] in properties;
|
|
7139
|
+
}
|
|
7140
|
+
function validateLimitBands(effect, action, warnings) {
|
|
7141
|
+
const limits = action.limits || [];
|
|
7142
|
+
const byInput = /* @__PURE__ */ new Map();
|
|
7143
|
+
limits.forEach((limit, index) => {
|
|
7144
|
+
const path7 = normalizePath(limit.input);
|
|
7145
|
+
if (path7.length === 0) {
|
|
7146
|
+
throw new Error(`Limit ${index + 1} on ${action.action} requires input`);
|
|
7147
|
+
}
|
|
7148
|
+
if (!inputSchemaHasField(effect, path7)) {
|
|
7149
|
+
throw new Error(
|
|
7150
|
+
`Limit ${index + 1} on ${action.action} references unsupported input field "${path7.join(".")}"`
|
|
7151
|
+
);
|
|
7152
|
+
}
|
|
7153
|
+
if (!normalizeDecision(limit.decision)) {
|
|
7154
|
+
throw new Error(
|
|
7155
|
+
`Limit ${index + 1} on ${action.action} has invalid decision`
|
|
7156
|
+
);
|
|
7157
|
+
}
|
|
7158
|
+
if (limit.upTo === void 0 === (limit.above === void 0)) {
|
|
7159
|
+
throw new Error(
|
|
7160
|
+
`Limit ${index + 1} on ${action.action} must define exactly one of upTo or above`
|
|
7161
|
+
);
|
|
7162
|
+
}
|
|
7163
|
+
if (limit.reason === void 0) {
|
|
7164
|
+
warnings.push(`Limit ${index + 1} on ${action.action} has no reason`);
|
|
7165
|
+
}
|
|
7166
|
+
const key = path7.join(".");
|
|
7167
|
+
byInput.set(key, [...byInput.get(key) || [], limit]);
|
|
7168
|
+
});
|
|
7169
|
+
for (const [input, bands] of byInput.entries()) {
|
|
7170
|
+
const thresholds = /* @__PURE__ */ new Set();
|
|
7171
|
+
let previousUpTo = -Infinity;
|
|
7172
|
+
for (const band of bands) {
|
|
7173
|
+
const value = band.upTo ?? band.above;
|
|
7174
|
+
const key = `${band.upTo === void 0 ? "above" : "upTo"}:${value}`;
|
|
7175
|
+
if (thresholds.has(key)) {
|
|
7176
|
+
throw new Error(
|
|
7177
|
+
`Duplicate limit threshold ${key} for input "${input}"`
|
|
7178
|
+
);
|
|
7179
|
+
}
|
|
7180
|
+
thresholds.add(key);
|
|
7181
|
+
if (band.upTo !== void 0) {
|
|
7182
|
+
if (band.upTo < previousUpTo) {
|
|
7183
|
+
throw new Error(`Unordered limit bands for input "${input}"`);
|
|
7184
|
+
}
|
|
7185
|
+
previousUpTo = band.upTo;
|
|
7186
|
+
}
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
function compileProfileActionRules(input) {
|
|
7191
|
+
const { profile, action, effect, warnings } = input;
|
|
7192
|
+
const origin = {
|
|
7193
|
+
kind: "permissionProfile",
|
|
7194
|
+
profileName: action.ownerProfileName || profile.name,
|
|
7195
|
+
source: profile.source || "permissionProfile"
|
|
7196
|
+
};
|
|
7197
|
+
const rules = [];
|
|
7198
|
+
const baseId = action.id || `${profile.name}-${effect.effectKey}`;
|
|
7199
|
+
if (action.allow === "always") {
|
|
7200
|
+
rules.push(
|
|
7201
|
+
makeRule({
|
|
7202
|
+
id: `${baseId}-allow`,
|
|
7203
|
+
origin,
|
|
7204
|
+
outcome: "allow",
|
|
7205
|
+
reason: action.reason
|
|
7206
|
+
})
|
|
7207
|
+
);
|
|
7208
|
+
}
|
|
7209
|
+
if (action.confirm === "always") {
|
|
7210
|
+
rules.push(
|
|
7211
|
+
makeRule({
|
|
7212
|
+
id: `${baseId}-confirm`,
|
|
7213
|
+
origin,
|
|
7214
|
+
outcome: "confirm",
|
|
7215
|
+
reason: action.reason
|
|
7216
|
+
})
|
|
7217
|
+
);
|
|
7218
|
+
}
|
|
7219
|
+
if (action.deny === "always") {
|
|
7220
|
+
rules.push(
|
|
7221
|
+
makeRule({
|
|
7222
|
+
id: `${baseId}-deny`,
|
|
7223
|
+
origin,
|
|
7224
|
+
outcome: "deny",
|
|
7225
|
+
reason: action.reason
|
|
7226
|
+
})
|
|
7227
|
+
);
|
|
7228
|
+
}
|
|
7229
|
+
const addConditional = (outcome, specs, suffix) => {
|
|
7230
|
+
(specs || []).forEach((spec, index) => {
|
|
7231
|
+
rules.push(
|
|
7232
|
+
makeRule({
|
|
7233
|
+
id: spec.id || `${baseId}-${suffix}-${index + 1}`,
|
|
7234
|
+
origin,
|
|
7235
|
+
outcome,
|
|
7236
|
+
reason: spec.reason,
|
|
7237
|
+
condition: normalizeCondition(spec.when)
|
|
7238
|
+
})
|
|
7239
|
+
);
|
|
7240
|
+
});
|
|
7241
|
+
};
|
|
7242
|
+
addConditional("allow", action.allowWhen, "allowWhen");
|
|
7243
|
+
addConditional("confirm", action.confirmWhen, "confirmWhen");
|
|
7244
|
+
addConditional("deny", action.denyWhen, "denyWhen");
|
|
7245
|
+
validateLimitBands(effect, action, warnings);
|
|
7246
|
+
const previousUpToByInput = /* @__PURE__ */ new Map();
|
|
7247
|
+
(action.limits || []).forEach((limit, index) => {
|
|
7248
|
+
const path7 = normalizePath(limit.input);
|
|
7249
|
+
const pathKey = path7.join(".");
|
|
7250
|
+
const lowerBound = previousUpToByInput.get(pathKey);
|
|
7251
|
+
const upperCondition = {
|
|
7252
|
+
kind: "predicate",
|
|
7253
|
+
source: "input",
|
|
7254
|
+
path: path7,
|
|
7255
|
+
operator: limit.upTo !== void 0 ? "lte" : "gt",
|
|
7256
|
+
value: limit.upTo ?? limit.above
|
|
7257
|
+
};
|
|
7258
|
+
const condition = lowerBound !== void 0 && limit.upTo !== void 0 ? {
|
|
7259
|
+
kind: "all",
|
|
7260
|
+
conditions: [
|
|
7261
|
+
{
|
|
7262
|
+
kind: "predicate",
|
|
7263
|
+
source: "input",
|
|
7264
|
+
path: path7,
|
|
7265
|
+
operator: "gt",
|
|
7266
|
+
value: lowerBound
|
|
7267
|
+
},
|
|
7268
|
+
upperCondition
|
|
7269
|
+
]
|
|
7270
|
+
} : upperCondition;
|
|
7271
|
+
rules.push(
|
|
7272
|
+
makeRule({
|
|
7273
|
+
id: limit.id || `${baseId}-limit-${index + 1}`,
|
|
7274
|
+
origin,
|
|
7275
|
+
outcome: limit.decision,
|
|
7276
|
+
reason: limit.reason,
|
|
7277
|
+
condition
|
|
7278
|
+
})
|
|
7279
|
+
);
|
|
7280
|
+
if (limit.upTo !== void 0) previousUpToByInput.set(pathKey, limit.upTo);
|
|
7281
|
+
});
|
|
7282
|
+
return rules;
|
|
7283
|
+
}
|
|
7284
|
+
function flattenProfiles(profiles, errors) {
|
|
7285
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7286
|
+
for (const profile of profiles) {
|
|
7287
|
+
const name = normalizeProfileName(profile.name);
|
|
7288
|
+
if (!name) {
|
|
7289
|
+
errors.push(`Invalid permission profile name: ${String(profile.name)}`);
|
|
7290
|
+
continue;
|
|
7291
|
+
}
|
|
7292
|
+
if (byName.has(name)) {
|
|
7293
|
+
errors.push(`Duplicate permission profile name: ${name}`);
|
|
7294
|
+
continue;
|
|
7295
|
+
}
|
|
7296
|
+
byName.set(name, {
|
|
7297
|
+
...profile,
|
|
7298
|
+
name,
|
|
7299
|
+
defaultsOwnerName: profile.defaultsOwnerName || name,
|
|
7300
|
+
policies: (profile.policies || []).map((policy) => ({
|
|
7301
|
+
...policy,
|
|
7302
|
+
ownerProfileName: policy.ownerProfileName || name
|
|
7303
|
+
})),
|
|
7304
|
+
actions: (profile.actions || []).map((action) => ({
|
|
7305
|
+
...action,
|
|
7306
|
+
ownerProfileName: action.ownerProfileName || name
|
|
7307
|
+
}))
|
|
7308
|
+
});
|
|
7309
|
+
}
|
|
7310
|
+
const flattening = /* @__PURE__ */ new Set();
|
|
7311
|
+
const flattened = /* @__PURE__ */ new Map();
|
|
7312
|
+
const flatten = (name, depth) => {
|
|
7313
|
+
if (depth > 5) {
|
|
7314
|
+
errors.push(`Permission profile inheritance depth exceeds 5 at ${name}`);
|
|
7315
|
+
return null;
|
|
7316
|
+
}
|
|
7317
|
+
const existing = flattened.get(name);
|
|
7318
|
+
if (existing) return existing;
|
|
7319
|
+
const profile = byName.get(name);
|
|
7320
|
+
if (!profile) {
|
|
7321
|
+
errors.push(`Missing parent permission profile: ${name}`);
|
|
7322
|
+
return null;
|
|
7323
|
+
}
|
|
7324
|
+
if (flattening.has(name)) {
|
|
7325
|
+
errors.push(`Permission profile inheritance cycle detected at ${name}`);
|
|
7326
|
+
return null;
|
|
7327
|
+
}
|
|
7328
|
+
flattening.add(name);
|
|
7329
|
+
let result = profile;
|
|
7330
|
+
if (profile.extends) {
|
|
7331
|
+
if (profile.defaults !== void 0) {
|
|
7332
|
+
errors.push(
|
|
7333
|
+
`Permission profile "${name}" extends "${profile.extends}" and cannot define defaults`
|
|
7334
|
+
);
|
|
7335
|
+
}
|
|
7336
|
+
const parent = flatten(profile.extends, depth + 1);
|
|
7337
|
+
if (parent) {
|
|
7338
|
+
const actionMap = /* @__PURE__ */ new Map();
|
|
7339
|
+
for (const action of parent.actions || []) {
|
|
7340
|
+
actionMap.set(actionIdentity(action.action, action.on), action);
|
|
7341
|
+
}
|
|
7342
|
+
for (const action of profile.actions || []) {
|
|
7343
|
+
actionMap.set(actionIdentity(action.action, action.on), action);
|
|
7344
|
+
}
|
|
7345
|
+
result = {
|
|
7346
|
+
...profile,
|
|
7347
|
+
defaults: parent.defaults,
|
|
7348
|
+
defaultsOwnerName: parent.defaultsOwnerName || parent.name,
|
|
7349
|
+
policies: [...parent.policies || [], ...profile.policies || []],
|
|
7350
|
+
actions: [...actionMap.values()]
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
flattening.delete(name);
|
|
7355
|
+
flattened.set(name, result);
|
|
7356
|
+
return result;
|
|
7357
|
+
};
|
|
7358
|
+
for (const name of byName.keys()) flatten(name, 0);
|
|
7359
|
+
return [...flattened.values()];
|
|
7360
|
+
}
|
|
7361
|
+
function compileProfile(input) {
|
|
7362
|
+
const { profile, effects: effects2, manifestRules, warnings } = input;
|
|
7363
|
+
const defaultActionPolicy = normalizeDecision(profile.defaults?.actionPolicy) || "deny";
|
|
7364
|
+
const actionRules = {};
|
|
7365
|
+
const addRules = (effectKey, rules) => {
|
|
7366
|
+
if (rules.length === 0) return;
|
|
7367
|
+
actionRules[effectKey] = [...actionRules[effectKey] || [], ...rules];
|
|
7368
|
+
};
|
|
7369
|
+
for (const policy of profile.policies || []) {
|
|
7370
|
+
const outcome = normalizeDecision(policy.outcome || policy.decision);
|
|
7371
|
+
if (!outcome)
|
|
7372
|
+
throw new Error(
|
|
7373
|
+
`Profile "${profile.name}" has a policy with invalid decision`
|
|
7374
|
+
);
|
|
7375
|
+
const condition = normalizeCondition(policy.when);
|
|
7376
|
+
const targetEffects = policy.action ? [resolveEffectReference(effects2, policy.action, policy.on)] : effects2;
|
|
7377
|
+
for (const effect of targetEffects) {
|
|
7378
|
+
addRules(effect.effectKey, [
|
|
7379
|
+
makeRule({
|
|
7380
|
+
id: policy.id || `${profile.name}-${effect.effectKey}-profile-policy`,
|
|
7381
|
+
origin: {
|
|
7382
|
+
kind: "permissionProfile",
|
|
7383
|
+
profileName: policy.ownerProfileName || profile.name,
|
|
7384
|
+
source: profile.source || "permissionProfile"
|
|
7385
|
+
},
|
|
7386
|
+
outcome,
|
|
7387
|
+
reason: policy.reason,
|
|
7388
|
+
condition
|
|
7389
|
+
})
|
|
7390
|
+
]);
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
for (const action of profile.actions || []) {
|
|
7394
|
+
const effect = resolveEffectReference(effects2, action.action, action.on);
|
|
7395
|
+
addRules(
|
|
7396
|
+
effect.effectKey,
|
|
7397
|
+
compileProfileActionRules({ profile, action, effect, warnings })
|
|
7398
|
+
);
|
|
7399
|
+
}
|
|
7400
|
+
const actionVisibility = effects2.filter((effect) => {
|
|
7401
|
+
const rules = actionRules[effect.effectKey] || [];
|
|
7402
|
+
const denyVisibilityRules = [
|
|
7403
|
+
...manifestRules[effect.effectKey] || [],
|
|
7404
|
+
...rules
|
|
7405
|
+
];
|
|
7406
|
+
if (denyVisibilityRules.some(
|
|
7407
|
+
(rule) => rule.outcome === "deny" && rule.condition.kind === "always"
|
|
7408
|
+
)) {
|
|
7409
|
+
return false;
|
|
7410
|
+
}
|
|
7411
|
+
if (defaultActionPolicy === "allow" || defaultActionPolicy === "confirm")
|
|
7412
|
+
return true;
|
|
7413
|
+
return denyVisibilityRules.some(
|
|
7414
|
+
(rule) => rule.outcome === "allow" || rule.outcome === "confirm"
|
|
7415
|
+
);
|
|
7416
|
+
}).map((effect) => effect.effectKey).sort();
|
|
7417
|
+
const summaries = Object.entries(actionRules).flatMap(
|
|
7418
|
+
([effectKey, rules]) => rules.map((rule) => `${effectKey}: ${rule.summary}`)
|
|
7419
|
+
).sort();
|
|
7420
|
+
return {
|
|
7421
|
+
name: profile.name,
|
|
7422
|
+
label: profile.label,
|
|
7423
|
+
description: profile.description,
|
|
7424
|
+
source: profile.source || "permissionProfile",
|
|
7425
|
+
sourcePath: profile.sourcePath,
|
|
7426
|
+
defaultActionPolicy,
|
|
7427
|
+
defaultActionPolicyOwner: profile.defaultsOwnerName || profile.name,
|
|
7428
|
+
digest: "",
|
|
7429
|
+
actionRules,
|
|
7430
|
+
actionVisibility,
|
|
7431
|
+
summaries,
|
|
7432
|
+
flattened: profile
|
|
7433
|
+
};
|
|
7434
|
+
}
|
|
7435
|
+
function normalizeSourceProfiles(sources) {
|
|
7436
|
+
const sourceProfiles = (sources || []).filter((profile) => isRecord(profile)).map((profile) => ({
|
|
7437
|
+
...profile,
|
|
7438
|
+
source: profile.source || "permissionProfile"
|
|
7439
|
+
}));
|
|
7440
|
+
const nonBuiltin = sourceProfiles.filter(
|
|
7441
|
+
(profile) => !BUILTIN_PROFILE_NAMES.includes(
|
|
7442
|
+
profile.name
|
|
7443
|
+
)
|
|
7444
|
+
);
|
|
7445
|
+
return [...getBuiltinPermissionProfiles(), ...nonBuiltin];
|
|
7446
|
+
}
|
|
7447
|
+
async function compilePolicyBundle(input) {
|
|
7448
|
+
const errors = [];
|
|
7449
|
+
const warnings = [];
|
|
7450
|
+
const effects2 = [...input.effects].sort(
|
|
7451
|
+
(left, right) => left.effectKey.localeCompare(right.effectKey)
|
|
7452
|
+
);
|
|
7453
|
+
const profiles = flattenProfiles(
|
|
7454
|
+
normalizeSourceProfiles(input.profileSources),
|
|
7455
|
+
errors
|
|
7456
|
+
);
|
|
7457
|
+
const manifestRules = {};
|
|
7458
|
+
for (const effect of effects2) {
|
|
7459
|
+
try {
|
|
7460
|
+
const rules = manifestPolicyRulesForEffect(effect, input.manifestId);
|
|
7461
|
+
if (rules.length > 0) manifestRules[effect.effectKey] = rules;
|
|
7462
|
+
} catch (error2) {
|
|
7463
|
+
errors.push(String(error2 instanceof Error ? error2.message : error2));
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
const compiledProfiles = {};
|
|
7467
|
+
for (const profile of profiles) {
|
|
7468
|
+
try {
|
|
7469
|
+
const defaultActionPolicy = profile.defaults?.actionPolicy;
|
|
7470
|
+
if (defaultActionPolicy !== void 0 && !normalizeDecision(defaultActionPolicy)) {
|
|
7471
|
+
throw new Error(
|
|
7472
|
+
`Permission profile "${profile.name}" has invalid defaults.actionPolicy`
|
|
7473
|
+
);
|
|
7474
|
+
}
|
|
7475
|
+
const compiled = compileProfile({
|
|
7476
|
+
profile,
|
|
7477
|
+
effects: effects2,
|
|
7478
|
+
manifestRules,
|
|
7479
|
+
warnings
|
|
7480
|
+
});
|
|
7481
|
+
compiled.digest = await digestJson({
|
|
7482
|
+
profile: compiled.flattened,
|
|
7483
|
+
actionRules: compiled.actionRules,
|
|
7484
|
+
defaultActionPolicy: compiled.defaultActionPolicy
|
|
7485
|
+
});
|
|
7486
|
+
compiledProfiles[profile.name] = compiled;
|
|
7487
|
+
} catch (error2) {
|
|
7488
|
+
errors.push(String(error2 instanceof Error ? error2.message : error2));
|
|
7489
|
+
}
|
|
7490
|
+
}
|
|
7491
|
+
const permissionProfilesDigest = await digestJson(
|
|
7492
|
+
Object.values(compiledProfiles).map((profile) => ({
|
|
7493
|
+
name: profile.name,
|
|
7494
|
+
digest: profile.digest,
|
|
7495
|
+
source: profile.source
|
|
7496
|
+
}))
|
|
7497
|
+
);
|
|
7498
|
+
const bundleWithoutDigest = {
|
|
7499
|
+
schemaVersion: 1,
|
|
7500
|
+
buildId: input.buildId,
|
|
7501
|
+
sandboxId: input.sandboxId,
|
|
7502
|
+
manifestDigest: input.manifestDigest,
|
|
7503
|
+
permissionProfilesDigest,
|
|
7504
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7505
|
+
effects: effects2.map((effect) => ({
|
|
7506
|
+
effectKey: effect.effectKey,
|
|
7507
|
+
name: effect.name,
|
|
7508
|
+
attachedClass: normalizeAttachedClass(effect.attachedClass),
|
|
7509
|
+
isStatic: effect.isStatic === true
|
|
7510
|
+
})),
|
|
7511
|
+
manifestRules,
|
|
7512
|
+
profiles: compiledProfiles
|
|
7513
|
+
};
|
|
7514
|
+
const bundleDigest = await digestJson(bundleWithoutDigest);
|
|
7515
|
+
return {
|
|
7516
|
+
bundle: { ...bundleWithoutDigest, bundleDigest },
|
|
7517
|
+
errors,
|
|
7518
|
+
warnings
|
|
7519
|
+
};
|
|
7520
|
+
}
|
|
7521
|
+
function evaluateCompiledPolicyBundle(input) {
|
|
7522
|
+
const profile = input.bundle.profiles[input.profileName];
|
|
7523
|
+
if (!profile) {
|
|
7524
|
+
return {
|
|
7525
|
+
outcome: "deny",
|
|
7526
|
+
matches: [
|
|
7527
|
+
{
|
|
7528
|
+
id: "missing-profile",
|
|
7529
|
+
outcome: "deny",
|
|
7530
|
+
origin: {
|
|
7531
|
+
kind: "permissionProfile",
|
|
7532
|
+
profileName: input.profileName,
|
|
7533
|
+
source: "permissionProfile"
|
|
7534
|
+
},
|
|
7535
|
+
reason: `Permission profile "${input.profileName}" does not exist in this version`,
|
|
7536
|
+
summary: `Permission profile "${input.profileName}" does not exist in this version`
|
|
7537
|
+
}
|
|
7538
|
+
]
|
|
7539
|
+
};
|
|
7540
|
+
}
|
|
7541
|
+
if (!profile.actionVisibility.includes(input.effectKey)) {
|
|
7542
|
+
return {
|
|
7543
|
+
outcome: "deny",
|
|
7544
|
+
matches: [
|
|
7545
|
+
{
|
|
7546
|
+
id: "action-not-visible",
|
|
7547
|
+
outcome: "deny",
|
|
7548
|
+
origin: {
|
|
7549
|
+
kind: "permissionProfile",
|
|
7550
|
+
profileName: input.profileName,
|
|
7551
|
+
source: profile.source
|
|
7552
|
+
},
|
|
7553
|
+
reason: `Action ${input.effectKey} is not visible for ${input.profileName}`,
|
|
7554
|
+
summary: `Action ${input.effectKey} is not visible for ${input.profileName}`
|
|
7555
|
+
}
|
|
7556
|
+
]
|
|
7557
|
+
};
|
|
7558
|
+
}
|
|
7559
|
+
const decision = evaluatePolicies({
|
|
7560
|
+
manifestRules: input.bundle.manifestRules[input.effectKey] || [],
|
|
7561
|
+
profileRules: profile.actionRules[input.effectKey] || [],
|
|
7562
|
+
defaultActionPolicy: profile.defaultActionPolicy,
|
|
7563
|
+
context: input.context
|
|
7564
|
+
});
|
|
7565
|
+
return {
|
|
7566
|
+
outcome: decision.outcome,
|
|
7567
|
+
matches: decision.matches.map(
|
|
7568
|
+
(match) => match.id === `profile-default-${decision.outcome}` ? {
|
|
7569
|
+
...match,
|
|
7570
|
+
origin: {
|
|
7571
|
+
kind: "permissionProfile",
|
|
7572
|
+
profileName: profile.name,
|
|
7573
|
+
source: profile.source
|
|
7574
|
+
}
|
|
7575
|
+
} : match
|
|
7576
|
+
)
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
function extractManifestEffects(manifest) {
|
|
7580
|
+
const effects2 = [];
|
|
7581
|
+
for (const volume of manifest.volumes || []) {
|
|
7582
|
+
for (const operation of volume.operations || []) {
|
|
7583
|
+
const effect = operation.withEffect;
|
|
7584
|
+
if (!effect?.name) continue;
|
|
7585
|
+
const attachedClass = normalizeAttachedClass(effect.attachedClass);
|
|
7586
|
+
const effectKey = attachedClass ? effect.isStatic ? `class:${attachedClass}:static:${effect.name}` : `class:${attachedClass}:instance:${effect.name}` : `global:${effect.name}`;
|
|
7587
|
+
effects2.push({
|
|
7588
|
+
effectKey,
|
|
7589
|
+
name: effect.name,
|
|
7590
|
+
attachedClass,
|
|
7591
|
+
isStatic: effect.isStatic === true,
|
|
7592
|
+
inputSchema: effect.inputSchema || null,
|
|
7593
|
+
metamodels: effect.metamodels || null
|
|
7594
|
+
});
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
return effects2;
|
|
7598
|
+
}
|
|
7599
|
+
|
|
6772
7600
|
// src/cli/config.ts
|
|
6773
7601
|
var MANIFEST_FILE = "granular.json";
|
|
6774
7602
|
var RC_FILE = ".granularrc";
|
|
6775
7603
|
var ENV_LOCAL_FILE = ".env.local";
|
|
7604
|
+
var PERMISSIONS_DIR = "permissions";
|
|
6776
7605
|
function getProjectRoot() {
|
|
6777
7606
|
return process.cwd();
|
|
6778
7607
|
}
|
|
@@ -6785,6 +7614,9 @@ function getRcPath() {
|
|
|
6785
7614
|
function getEnvLocalPath() {
|
|
6786
7615
|
return path3__namespace.join(getProjectRoot(), ENV_LOCAL_FILE);
|
|
6787
7616
|
}
|
|
7617
|
+
function getPermissionsDir() {
|
|
7618
|
+
return path3__namespace.join(getProjectRoot(), PERMISSIONS_DIR);
|
|
7619
|
+
}
|
|
6788
7620
|
function readManifestFile() {
|
|
6789
7621
|
const filePath = getManifestPath();
|
|
6790
7622
|
if (!fs__namespace.existsSync(filePath)) return null;
|
|
@@ -6799,6 +7631,40 @@ function writeManifestFile(project) {
|
|
|
6799
7631
|
const filePath = getManifestPath();
|
|
6800
7632
|
fs__namespace.writeFileSync(filePath, JSON.stringify(project, null, 2) + "\n", "utf-8");
|
|
6801
7633
|
}
|
|
7634
|
+
function ensureBuiltinPermissionFiles() {
|
|
7635
|
+
const permissionsDir = getPermissionsDir();
|
|
7636
|
+
if (!fs__namespace.existsSync(permissionsDir)) {
|
|
7637
|
+
fs__namespace.mkdirSync(permissionsDir, { recursive: true });
|
|
7638
|
+
}
|
|
7639
|
+
for (const profile of getBuiltinPermissionProfiles()) {
|
|
7640
|
+
const filePath = path3__namespace.join(permissionsDir, `${profile.name}.json`);
|
|
7641
|
+
if (!fs__namespace.existsSync(filePath)) {
|
|
7642
|
+
fs__namespace.writeFileSync(filePath, JSON.stringify(profile, null, 2) + "\n", "utf-8");
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
function readPermissionProfileFiles() {
|
|
7647
|
+
const permissionsDir = getPermissionsDir();
|
|
7648
|
+
if (!fs__namespace.existsSync(permissionsDir)) {
|
|
7649
|
+
return getBuiltinPermissionProfiles();
|
|
7650
|
+
}
|
|
7651
|
+
const profiles = [];
|
|
7652
|
+
const entries = fs__namespace.readdirSync(permissionsDir).filter((entry) => entry.endsWith(".json")).sort();
|
|
7653
|
+
for (const entry of entries) {
|
|
7654
|
+
const filePath = path3__namespace.join(permissionsDir, entry);
|
|
7655
|
+
const raw = fs__namespace.readFileSync(filePath, "utf-8");
|
|
7656
|
+
const parsed = JSON.parse(raw);
|
|
7657
|
+
profiles.push({
|
|
7658
|
+
...parsed,
|
|
7659
|
+
name: parsed.name || entry.replace(/\.json$/, ""),
|
|
7660
|
+
sourcePath: path3__namespace.relative(getProjectRoot(), filePath)
|
|
7661
|
+
});
|
|
7662
|
+
}
|
|
7663
|
+
if (profiles.length === 0) {
|
|
7664
|
+
return getBuiltinPermissionProfiles();
|
|
7665
|
+
}
|
|
7666
|
+
return profiles;
|
|
7667
|
+
}
|
|
6802
7668
|
function manifestExists() {
|
|
6803
7669
|
return fs__namespace.existsSync(getManifestPath());
|
|
6804
7670
|
}
|
|
@@ -6926,8 +7792,8 @@ var ApiClient = class {
|
|
|
6926
7792
|
this.apiKey = resolveAuthTokenForApiUrl(apiKey, apiUrl);
|
|
6927
7793
|
this.baseUrl = apiUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws$/, "");
|
|
6928
7794
|
}
|
|
6929
|
-
async request(
|
|
6930
|
-
const url = `${this.baseUrl}${
|
|
7795
|
+
async request(path7, options = {}) {
|
|
7796
|
+
const url = `${this.baseUrl}${path7}`;
|
|
6931
7797
|
const response = await fetch(url, {
|
|
6932
7798
|
...options,
|
|
6933
7799
|
headers: {
|
|
@@ -7098,31 +7964,52 @@ var ApiClient = class {
|
|
|
7098
7964
|
}
|
|
7099
7965
|
// ── Permission Profiles ──
|
|
7100
7966
|
async createPermissionProfile(sandboxId, data) {
|
|
7967
|
+
const profile = {
|
|
7968
|
+
...data.rules,
|
|
7969
|
+
schemaVersion: 1,
|
|
7970
|
+
name: data.name
|
|
7971
|
+
};
|
|
7972
|
+
const result = await this.syncPermissionProfileSources(sandboxId, [profile]);
|
|
7973
|
+
const synced = result.items?.find((item) => item.name === data.name) || result.items?.[0];
|
|
7974
|
+
if (!synced) {
|
|
7975
|
+
throw new Error(`Permission profile source sync did not return ${data.name}`);
|
|
7976
|
+
}
|
|
7977
|
+
return synced;
|
|
7978
|
+
}
|
|
7979
|
+
async ensureDefaultProfile(sandboxId) {
|
|
7980
|
+
return await this.createPermissionProfile(sandboxId, {
|
|
7981
|
+
name: "allow-all",
|
|
7982
|
+
rules: {
|
|
7983
|
+
schemaVersion: 1,
|
|
7984
|
+
name: "allow-all",
|
|
7985
|
+
description: "Every declared action is visible unless a manifest policy denies it.",
|
|
7986
|
+
defaults: { actionPolicy: "allow" },
|
|
7987
|
+
actions: []
|
|
7988
|
+
}
|
|
7989
|
+
});
|
|
7990
|
+
}
|
|
7991
|
+
async syncPermissionProfileSources(sandboxId, profiles) {
|
|
7101
7992
|
return this.request(
|
|
7102
|
-
`/control/sandboxes/${sandboxId}/permission-
|
|
7993
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`,
|
|
7103
7994
|
{
|
|
7104
|
-
method: "
|
|
7105
|
-
body: JSON.stringify(
|
|
7995
|
+
method: "PUT",
|
|
7996
|
+
body: JSON.stringify({ profiles })
|
|
7106
7997
|
}
|
|
7107
7998
|
);
|
|
7108
7999
|
}
|
|
7109
|
-
async
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
}
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
const existing = profiles.items.find((p) => p.name === "default");
|
|
7123
|
-
if (existing) return existing;
|
|
7124
|
-
throw new Error("Could not create or find default permission profile");
|
|
7125
|
-
}
|
|
8000
|
+
async getVersionPolicies(versionId) {
|
|
8001
|
+
return this.request(`/control/ontology-versions/${versionId}/policies`);
|
|
8002
|
+
}
|
|
8003
|
+
async getVersionPermissionProfiles(versionId) {
|
|
8004
|
+
return this.request(
|
|
8005
|
+
`/control/ontology-versions/${versionId}/permission-profiles`
|
|
8006
|
+
);
|
|
8007
|
+
}
|
|
8008
|
+
async evaluateVersionPolicy(versionId, request) {
|
|
8009
|
+
return this.request(`/control/builds/${versionId}/policies/evaluate`, {
|
|
8010
|
+
method: "POST",
|
|
8011
|
+
body: JSON.stringify(request)
|
|
8012
|
+
});
|
|
7126
8013
|
}
|
|
7127
8014
|
// ── Subjects ──
|
|
7128
8015
|
async createSubject(identityId, name) {
|
|
@@ -9521,8 +10408,8 @@ function getErrorMap() {
|
|
|
9521
10408
|
|
|
9522
10409
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
9523
10410
|
var makeIssue = (params) => {
|
|
9524
|
-
const { data, path:
|
|
9525
|
-
const fullPath = [...
|
|
10411
|
+
const { data, path: path7, errorMaps, issueData } = params;
|
|
10412
|
+
const fullPath = [...path7, ...issueData.path || []];
|
|
9526
10413
|
const fullIssue = {
|
|
9527
10414
|
...issueData,
|
|
9528
10415
|
path: fullPath
|
|
@@ -9638,11 +10525,11 @@ var errorUtil;
|
|
|
9638
10525
|
|
|
9639
10526
|
// ../../node_modules/zod/v3/types.js
|
|
9640
10527
|
var ParseInputLazyPath = class {
|
|
9641
|
-
constructor(parent, value,
|
|
10528
|
+
constructor(parent, value, path7, key) {
|
|
9642
10529
|
this._cachedPath = [];
|
|
9643
10530
|
this.parent = parent;
|
|
9644
10531
|
this.data = value;
|
|
9645
|
-
this._path =
|
|
10532
|
+
this._path = path7;
|
|
9646
10533
|
this._key = key;
|
|
9647
10534
|
}
|
|
9648
10535
|
get path() {
|
|
@@ -13148,6 +14035,67 @@ external_exports.object({
|
|
|
13148
14035
|
transitions: external_exports.array(StateMachineTransitionSchema),
|
|
13149
14036
|
finalStates: external_exports.array(external_exports.string()).optional()
|
|
13150
14037
|
}).strict();
|
|
14038
|
+
var POLICY_OPERATORS = [
|
|
14039
|
+
"eq",
|
|
14040
|
+
"neq",
|
|
14041
|
+
"gt",
|
|
14042
|
+
"gte",
|
|
14043
|
+
"lt",
|
|
14044
|
+
"lte",
|
|
14045
|
+
"contains",
|
|
14046
|
+
"not_contains",
|
|
14047
|
+
"starts_with",
|
|
14048
|
+
"ends_with",
|
|
14049
|
+
"exists"
|
|
14050
|
+
];
|
|
14051
|
+
var PolicyPredicateSchema = external_exports.object({
|
|
14052
|
+
path: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional(),
|
|
14053
|
+
field: external_exports.string().optional(),
|
|
14054
|
+
input: external_exports.string().optional(),
|
|
14055
|
+
operator: external_exports.enum([...POLICY_OPERATORS]),
|
|
14056
|
+
stringValue: external_exports.string().optional(),
|
|
14057
|
+
numberValue: external_exports.number().optional(),
|
|
14058
|
+
booleanValue: external_exports.boolean().optional(),
|
|
14059
|
+
value: external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]).optional()
|
|
14060
|
+
}).strict();
|
|
14061
|
+
var PolicyStateMachinePredicateSchema = external_exports.object({
|
|
14062
|
+
machine: external_exports.string().min(1),
|
|
14063
|
+
operator: external_exports.enum([...POLICY_OPERATORS]),
|
|
14064
|
+
state: external_exports.string().optional(),
|
|
14065
|
+
stringValue: external_exports.string().optional()
|
|
14066
|
+
}).strict();
|
|
14067
|
+
var PolicyConditionSchema = external_exports.lazy(
|
|
14068
|
+
() => external_exports.object({
|
|
14069
|
+
all: external_exports.array(PolicyConditionSchema).optional(),
|
|
14070
|
+
any: external_exports.array(PolicyConditionSchema).optional(),
|
|
14071
|
+
not: PolicyConditionSchema.optional(),
|
|
14072
|
+
input: PolicyPredicateSchema.optional(),
|
|
14073
|
+
object: PolicyPredicateSchema.optional(),
|
|
14074
|
+
stateMachine: PolicyStateMachinePredicateSchema.optional()
|
|
14075
|
+
}).strict().refine(
|
|
14076
|
+
(data) => [
|
|
14077
|
+
data.all,
|
|
14078
|
+
data.any,
|
|
14079
|
+
data.not,
|
|
14080
|
+
data.input,
|
|
14081
|
+
data.object,
|
|
14082
|
+
data.stateMachine
|
|
14083
|
+
].filter((value) => value !== void 0).length === 1,
|
|
14084
|
+
{
|
|
14085
|
+
message: "Policy condition must define exactly one of all, any, not, input, object, or stateMachine"
|
|
14086
|
+
}
|
|
14087
|
+
)
|
|
14088
|
+
);
|
|
14089
|
+
var PolicyRuleSchema = external_exports.object({
|
|
14090
|
+
id: external_exports.string().min(1).optional(),
|
|
14091
|
+
reason: external_exports.string().optional(),
|
|
14092
|
+
when: PolicyConditionSchema
|
|
14093
|
+
}).strict();
|
|
14094
|
+
var PoliciesSchema = external_exports.object({
|
|
14095
|
+
allowWhen: external_exports.array(PolicyRuleSchema).optional(),
|
|
14096
|
+
confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
|
|
14097
|
+
denyWhen: external_exports.array(PolicyRuleSchema).optional()
|
|
14098
|
+
}).strict();
|
|
13151
14099
|
external_exports.object({
|
|
13152
14100
|
postCondition: external_exports.union([
|
|
13153
14101
|
external_exports.string(),
|
|
@@ -13177,7 +14125,8 @@ external_exports.object({
|
|
|
13177
14125
|
reason: external_exports.string().optional(),
|
|
13178
14126
|
mode: external_exports.string().optional()
|
|
13179
14127
|
}).strict()
|
|
13180
|
-
]).optional()
|
|
14128
|
+
]).optional(),
|
|
14129
|
+
policies: PoliciesSchema.optional()
|
|
13181
14130
|
}).strict();
|
|
13182
14131
|
|
|
13183
14132
|
// ../metamodel-core/src/index.ts
|
|
@@ -14046,6 +14995,54 @@ var noteMetamodelPackage = defineMetamodelPackage({
|
|
|
14046
14995
|
}
|
|
14047
14996
|
});
|
|
14048
14997
|
|
|
14998
|
+
// ../metamodel-policy/src/index.ts
|
|
14999
|
+
function escapeGraphqlString(value) {
|
|
15000
|
+
return JSON.stringify(value);
|
|
15001
|
+
}
|
|
15002
|
+
function buildPolicyMutations(effectKey, spec) {
|
|
15003
|
+
const policies = spec.policies;
|
|
15004
|
+
if (!policies) return [];
|
|
15005
|
+
const mutations = [];
|
|
15006
|
+
const addRules = (key, outcome) => {
|
|
15007
|
+
const rules = policies[key] || [];
|
|
15008
|
+
rules.forEach((rule, index) => {
|
|
15009
|
+
const condition = normalizeCondition(rule.when);
|
|
15010
|
+
const summary = rule.reason || summarizeCondition(condition);
|
|
15011
|
+
const id = rule.id || `${effectKey}:${outcome}:${index + 1}`;
|
|
15012
|
+
mutations.push({
|
|
15013
|
+
label: `set policy ${outcome} on ${effectKey}`,
|
|
15014
|
+
query: `mutation { set_policy_rule(effect_key: ${escapeGraphqlString(effectKey)}, policy_id: ${escapeGraphqlString(id)}, outcome: ${escapeGraphqlString(outcome)}, reason: ${escapeGraphqlString(summary)}, condition_json: ${escapeGraphqlString(JSON.stringify(condition))}) }`
|
|
15015
|
+
});
|
|
15016
|
+
});
|
|
15017
|
+
};
|
|
15018
|
+
addRules("allowWhen", "allow");
|
|
15019
|
+
addRules("confirmWhen", "confirm");
|
|
15020
|
+
addRules("denyWhen", "deny");
|
|
15021
|
+
return mutations;
|
|
15022
|
+
}
|
|
15023
|
+
var policyMetamodelPackage = defineMetamodelPackage({
|
|
15024
|
+
id: "policy",
|
|
15025
|
+
manifest: {
|
|
15026
|
+
buildEffectMutations: buildPolicyMutations
|
|
15027
|
+
},
|
|
15028
|
+
summary: {
|
|
15029
|
+
selections: {
|
|
15030
|
+
methodFields: ["policies"]
|
|
15031
|
+
},
|
|
15032
|
+
readMethodSummary(rawMethod) {
|
|
15033
|
+
return rawMethod.policies ? { metamodels: { policies: rawMethod.policies } } : {};
|
|
15034
|
+
}
|
|
15035
|
+
},
|
|
15036
|
+
docs: {
|
|
15037
|
+
effectRows: [
|
|
15038
|
+
{
|
|
15039
|
+
key: "policies",
|
|
15040
|
+
description: "Universal effect policies with allowWhen, confirmWhen, and denyWhen structural conditions."
|
|
15041
|
+
}
|
|
15042
|
+
]
|
|
15043
|
+
}
|
|
15044
|
+
});
|
|
15045
|
+
|
|
14049
15046
|
// ../metamodel-required/src/index.ts
|
|
14050
15047
|
function buildRequiredFieldMutations(fieldPath, required) {
|
|
14051
15048
|
if (!required) return [];
|
|
@@ -14820,7 +15817,8 @@ var DEFAULT_METAMODEL_PACKAGES = [
|
|
|
14820
15817
|
searchableMetamodelPackage,
|
|
14821
15818
|
validationRuleMetamodelPackage,
|
|
14822
15819
|
stateMachineMetamodelPackage,
|
|
14823
|
-
effectBehaviorsMetamodelPackage
|
|
15820
|
+
effectBehaviorsMetamodelPackage,
|
|
15821
|
+
policyMetamodelPackage
|
|
14824
15822
|
];
|
|
14825
15823
|
var DEFAULT_METAMODEL_REGISTRY = createMetamodelRegistry(
|
|
14826
15824
|
DEFAULT_METAMODEL_PACKAGES
|
|
@@ -15126,7 +16124,112 @@ Example:
|
|
|
15126
16124
|
|
|
15127
16125
|
**Lifecycle:** declare in manifest \u2192 \`granular build\` creates or reuses a version and runs the build \u2192 implement handlers \u2192 \`granular.ontology(sandboxId).effects.registerMany([...])\` \u2192 jobs call generated methods \u2192 your handler runs and returns the result to the job.
|
|
15128
16126
|
|
|
15129
|
-
**Permissions:** \`openEnvironment({ permissions: [...] })\` assigns
|
|
16127
|
+
**Permissions:** \`openEnvironment({ permissions: [...] })\` assigns one permission profile for the environment/session. Profile files live in \`permissions/*.json\`, are synced by \`granular build\` / \`granular deploy\`, are compiled into the ontology version, and decide whether each generated action is visible, allowed, confirmed, or denied.
|
|
16128
|
+
|
|
16129
|
+
### Manifest logic policies
|
|
16130
|
+
|
|
16131
|
+
Use \`withEffect.metamodels.policies\` for product-wide guardrails that apply to every profile. Manifest policies are strongest for rules such as \u201Clocked objects cannot be changed\u201D or \u201Cexternal payments need confirmation.\u201D
|
|
16132
|
+
|
|
16133
|
+
\`\`\`json
|
|
16134
|
+
{
|
|
16135
|
+
"withEffect": {
|
|
16136
|
+
"name": "approve_ticket",
|
|
16137
|
+
"attachedClass": "ticket",
|
|
16138
|
+
"inputSchema": { "type": "object", "properties": { "note": { "type": "string" } } },
|
|
16139
|
+
"outputSchema": { "type": "object", "properties": { "ok": { "type": "boolean" } } },
|
|
16140
|
+
"metamodels": {
|
|
16141
|
+
"policies": {
|
|
16142
|
+
"denyWhen": [
|
|
16143
|
+
{
|
|
16144
|
+
"id": "locked-ticket-deny",
|
|
16145
|
+
"reason": "Locked tickets cannot be approved",
|
|
16146
|
+
"when": {
|
|
16147
|
+
"object": { "path": "locked", "operator": "eq", "booleanValue": true }
|
|
16148
|
+
}
|
|
16149
|
+
}
|
|
16150
|
+
],
|
|
16151
|
+
"confirmWhen": [
|
|
16152
|
+
{
|
|
16153
|
+
"id": "high-risk-confirm",
|
|
16154
|
+
"reason": "High-risk active tickets require confirmation",
|
|
16155
|
+
"when": {
|
|
16156
|
+
"all": [
|
|
16157
|
+
{ "object": { "path": "risk_score", "operator": "gt", "numberValue": 50 } },
|
|
16158
|
+
{ "stateMachine": { "machine": "lifecycle", "operator": "eq", "stringValue": "active" } }
|
|
16159
|
+
]
|
|
16160
|
+
}
|
|
16161
|
+
}
|
|
16162
|
+
]
|
|
16163
|
+
}
|
|
16164
|
+
}
|
|
16165
|
+
}
|
|
16166
|
+
}
|
|
16167
|
+
\`\`\`
|
|
16168
|
+
|
|
16169
|
+
### Permission profile files
|
|
16170
|
+
|
|
16171
|
+
Each file under \`permissions/\` defines one role/profile. Built-ins are \`allow-all\`, \`confirm-all\`, and \`deny-all\`; \`granular init\` and \`granular pull\` materialize them as files.
|
|
16172
|
+
|
|
16173
|
+
\`\`\`json
|
|
16174
|
+
{
|
|
16175
|
+
"schemaVersion": 1,
|
|
16176
|
+
"name": "reviewer",
|
|
16177
|
+
"description": "Reviewer can comment and conditionally approve tickets",
|
|
16178
|
+
"defaults": { "actionPolicy": "deny" },
|
|
16179
|
+
"policies": [
|
|
16180
|
+
{
|
|
16181
|
+
"id": "reviewer-low-risk-allow",
|
|
16182
|
+
"action": "approve_ticket",
|
|
16183
|
+
"on": "ticket",
|
|
16184
|
+
"decision": "allow",
|
|
16185
|
+
"reason": "Active low-risk tickets can be approved by reviewers",
|
|
16186
|
+
"when": {
|
|
16187
|
+
"all": [
|
|
16188
|
+
{ "object": { "path": "risk_score", "operator": "lte", "numberValue": 50 } },
|
|
16189
|
+
{ "stateMachine": { "machine": "lifecycle", "operator": "eq", "stringValue": "active" } },
|
|
16190
|
+
{ "not": { "object": { "path": "locked", "operator": "eq", "booleanValue": true } } }
|
|
16191
|
+
]
|
|
16192
|
+
}
|
|
16193
|
+
}
|
|
16194
|
+
],
|
|
16195
|
+
"actions": [
|
|
16196
|
+
{
|
|
16197
|
+
"action": "comment_ticket",
|
|
16198
|
+
"on": "ticket",
|
|
16199
|
+
"allow": "always",
|
|
16200
|
+
"reason": "Reviewers can comment on tickets"
|
|
16201
|
+
}
|
|
16202
|
+
]
|
|
16203
|
+
}
|
|
16204
|
+
\`\`\`
|
|
16205
|
+
|
|
16206
|
+
Profile rules:
|
|
16207
|
+
|
|
16208
|
+
- \`defaults.actionPolicy\` is \`allow\`, \`confirm\`, or \`deny\`; omitted means \`deny\`.
|
|
16209
|
+
- \`policies[]\` can target one action with \`action\` + optional \`on\`, or all declared actions if \`action\` is omitted.
|
|
16210
|
+
- \`actions[]\` supports \`allow: "always"\`, \`confirm: "always"\`, \`deny: "always"\`, \`allowWhen\`, \`confirmWhen\`, \`denyWhen\`, and numeric \`limits\`.
|
|
16211
|
+
- \`limits\` is a shorthand over numeric input fields: \`{ "input": "amount", "upTo": 500, "decision": "allow" }\` and \`{ "input": "amount", "above": 5000, "decision": "deny" }\`.
|
|
16212
|
+
- \`extends\` inherits exactly one parent profile. A child profile cannot define its own \`defaults\`; it can add rules and override action rules by the same \`{ action, on }\`.
|
|
16213
|
+
|
|
16214
|
+
Condition sources:
|
|
16215
|
+
|
|
16216
|
+
- \`input\`: the action input passed by job code.
|
|
16217
|
+
- \`object\`: fields on the target object for instance effects.
|
|
16218
|
+
- \`stateMachine\`: current state of a workflow attached to the target object.
|
|
16219
|
+
- Logical composition: \`all\`, \`any\`, and \`not\`.
|
|
16220
|
+
|
|
16221
|
+
Decision precedence is deterministic: manifest \`deny\`, profile \`deny\`, manifest/profile \`confirm\`, profile \`allow\`, then the profile default. Runtime blocks \`deny\` before provider invocation and asks a human confirmation for \`confirm\`.
|
|
16222
|
+
|
|
16223
|
+
Useful commands:
|
|
16224
|
+
|
|
16225
|
+
\`\`\`bash
|
|
16226
|
+
granular permissions validate
|
|
16227
|
+
granular permissions preview --profile reviewer --action approve_ticket --on ticket \\
|
|
16228
|
+
--input '{"note":"ok"}' \\
|
|
16229
|
+
--object '{"risk_score":40,"locked":false}' \\
|
|
16230
|
+
--state-machines '{"lifecycle":"active"}'
|
|
16231
|
+
granular build
|
|
16232
|
+
\`\`\`
|
|
15130
16233
|
|
|
15131
16234
|
### Effect metamodels
|
|
15132
16235
|
|
|
@@ -15247,6 +16350,8 @@ Use \`environment.graphql(query, variables?)\` when you need **query/mutation ac
|
|
|
15247
16350
|
| \`granular build\` | Upload \`granular.json\`, create or reuse the matching ontology version, and run the build (**validation**). |
|
|
15248
16351
|
| \`granular deploy\` | Push the current revision, build it, and update \`dev\`. |
|
|
15249
16352
|
| \`granular deploy --prod\` | If the current version is already on \`dev\`, also point \`prod\` to it. Otherwise build it, then point both \`dev\` and \`prod\` to it. |
|
|
16353
|
+
| \`granular permissions validate\` | Validate \`permissions/*.json\` against local \`granular.json\`, including action references, inheritance, limits, and conditions. |
|
|
16354
|
+
| \`granular permissions preview --profile <name> --action <name> [--on <class>] [--input '{}'] [--object '{}'] [--state-machines '{}'] [--version-id <id>]\` | Preview the exact \`allow\` / \`confirm\` / \`deny\` decision locally or against a compiled version. |
|
|
15250
16355
|
| \`granular version current --json\` | Resolve the latest/current ontology version for agent workflows. |
|
|
15251
16356
|
| \`granular version diff\` | Show the semantic diff between ontology versions. |
|
|
15252
16357
|
| \`granular tag move\` | Move \`dev\`, \`prod\`, or another tag to a chosen version. |
|
|
@@ -15261,7 +16366,7 @@ Use \`environment.graphql(query, variables?)\` when you need **query/mutation ac
|
|
|
15261
16366
|
| \`granular graphql --query '...' --json\` | Run a GraphQL query or mutation against the live graph. |
|
|
15262
16367
|
| \`granular effects list/diff --json\` | Inspect declared versus live ready effects. |
|
|
15263
16368
|
| \`granular job run --file ./job.ts --json\` | Execute a real runtime job from the terminal or CI. |
|
|
15264
|
-
| \`granular pull <version>\` | Pull the manifest for a specific ontology version. |
|
|
16369
|
+
| \`granular pull <version>\` | Pull the manifest and versioned \`permissions/*.json\` files for a specific ontology version. |
|
|
15265
16370
|
| \`granular login\`, \`granular whoami\`, \`granular status --verbose --json\` | Auth, config, and project info. |
|
|
15266
16371
|
|
|
15267
16372
|
---
|
|
@@ -15428,7 +16533,7 @@ function generateSandboxAgentDoc(manifest, meta) {
|
|
|
15428
16533
|
lines.push(` ontology: '${meta.sandboxId}',`);
|
|
15429
16534
|
lines.push(` tag: 'dev',`);
|
|
15430
16535
|
lines.push(` userId: 'your_app_user_id',`);
|
|
15431
|
-
lines.push(` permissions: ['
|
|
16536
|
+
lines.push(` permissions: ['allow-all'],`);
|
|
15432
16537
|
lines.push(`});`);
|
|
15433
16538
|
lines.push("```");
|
|
15434
16539
|
lines.push("");
|
|
@@ -15978,7 +17083,7 @@ Use this when proving that the ontology works in real runtime conditions.
|
|
|
15978
17083
|
|
|
15979
17084
|
- Load \`GRANULAR_API_KEY\` from environment when running unattended tests.
|
|
15980
17085
|
- Read the ontology id from \`.granularrc\` if the repo uses \`granular init\`.
|
|
15981
|
-
- Connect with \`new Granular({ apiKey }).connect({ ontology, environment: 'dev', userId, permissions: ['
|
|
17086
|
+
- Connect with \`new Granular({ apiKey }).connect({ ontology, environment: 'dev', userId, permissions: ['allow-all'] })\`.
|
|
15982
17087
|
- Assert on real behavior rather than giant snapshots.
|
|
15983
17088
|
|
|
15984
17089
|
# What to verify
|
|
@@ -16725,9 +17830,11 @@ async function initCommand(projectName, options) {
|
|
|
16725
17830
|
}
|
|
16726
17831
|
const project = createDefaultManifest(name, templateId);
|
|
16727
17832
|
writeManifestFile(project);
|
|
17833
|
+
ensureBuiltinPermissionFiles();
|
|
16728
17834
|
success(
|
|
16729
17835
|
`Created ${brand.bold("granular.json")} with the ${template.label} starter ontology`
|
|
16730
17836
|
);
|
|
17837
|
+
success(`Created ${brand.bold("permissions/")} with built-in profiles`);
|
|
16731
17838
|
writeRcFile({
|
|
16732
17839
|
sandboxId: sandbox.sandboxId,
|
|
16733
17840
|
sandboxName: sandbox.name,
|
|
@@ -17060,8 +18167,32 @@ async function loginCommand(options = {}) {
|
|
|
17060
18167
|
dim("API key saved to .env.local (used by CLI and SDK examples)");
|
|
17061
18168
|
console.log();
|
|
17062
18169
|
}
|
|
17063
|
-
|
|
17064
|
-
|
|
18170
|
+
function cleanPermissionProfile(profile) {
|
|
18171
|
+
const {
|
|
18172
|
+
source,
|
|
18173
|
+
sourcePath,
|
|
18174
|
+
defaultsOwnerName,
|
|
18175
|
+
...rest
|
|
18176
|
+
} = profile;
|
|
18177
|
+
return rest;
|
|
18178
|
+
}
|
|
18179
|
+
function writePermissionProfileFiles(profiles) {
|
|
18180
|
+
const permissionsDir = getPermissionsDir();
|
|
18181
|
+
fs__namespace.mkdirSync(permissionsDir, { recursive: true });
|
|
18182
|
+
const written = [];
|
|
18183
|
+
for (const profile of profiles) {
|
|
18184
|
+
if (!profile?.name) continue;
|
|
18185
|
+
const fileName = `${profile.name}.json`;
|
|
18186
|
+
const filePath = path3__namespace.join(permissionsDir, fileName);
|
|
18187
|
+
fs__namespace.writeFileSync(
|
|
18188
|
+
filePath,
|
|
18189
|
+
JSON.stringify(cleanPermissionProfile(profile), null, 2) + "\n",
|
|
18190
|
+
"utf-8"
|
|
18191
|
+
);
|
|
18192
|
+
written.push(path3__namespace.join("permissions", fileName));
|
|
18193
|
+
}
|
|
18194
|
+
return written.sort();
|
|
18195
|
+
}
|
|
17065
18196
|
async function pullCommand(versionId) {
|
|
17066
18197
|
printHeader();
|
|
17067
18198
|
const config = resolveConfig({ requireApiKey: true });
|
|
@@ -17085,13 +18216,18 @@ async function pullCommand(versionId) {
|
|
|
17085
18216
|
manifest: full.content
|
|
17086
18217
|
};
|
|
17087
18218
|
writeManifestFile(project);
|
|
18219
|
+
spin.text = " Downloading permission profile files...";
|
|
18220
|
+
const profileVersions = await api.getVersionPermissionProfiles(versionId);
|
|
18221
|
+
const pulledProfiles = (profileVersions.items || []).map((item) => item.profile).filter((profile) => Boolean(profile?.name));
|
|
18222
|
+
const writtenProfiles = writePermissionProfileFiles(pulledProfiles);
|
|
17088
18223
|
spin.succeed(" Manifest pulled successfully.");
|
|
17089
18224
|
console.log();
|
|
17090
18225
|
keyValue({
|
|
17091
18226
|
"Manifest ID": full.manifestId,
|
|
17092
18227
|
"Source Version": versionId,
|
|
17093
18228
|
"Version": targetVersionLabel,
|
|
17094
|
-
"Written to": "granular.json"
|
|
18229
|
+
"Written to": "granular.json",
|
|
18230
|
+
"Permission profiles": writtenProfiles.length > 0 ? writtenProfiles.join(", ") : "none"
|
|
17095
18231
|
});
|
|
17096
18232
|
console.log();
|
|
17097
18233
|
} catch (err) {
|
|
@@ -17110,6 +18246,28 @@ async function buildCommand() {
|
|
|
17110
18246
|
}
|
|
17111
18247
|
const api = new ApiClient(config.apiKey, config.apiUrl);
|
|
17112
18248
|
const manifest = config.project.manifest;
|
|
18249
|
+
const permissionProfiles = readPermissionProfileFiles();
|
|
18250
|
+
const localPolicyValidation = await compilePolicyBundle({
|
|
18251
|
+
sandboxId: config.sandboxId,
|
|
18252
|
+
manifestDigest: void 0,
|
|
18253
|
+
effects: extractManifestEffects(manifest),
|
|
18254
|
+
profileSources: permissionProfiles
|
|
18255
|
+
});
|
|
18256
|
+
if (localPolicyValidation.errors.length > 0) {
|
|
18257
|
+
error("Permission policy validation failed:");
|
|
18258
|
+
for (const error2 of localPolicyValidation.errors) {
|
|
18259
|
+
error(` ${error2}`);
|
|
18260
|
+
}
|
|
18261
|
+
process.exit(1);
|
|
18262
|
+
}
|
|
18263
|
+
const syncingProfiles = spinner(`Syncing ${permissionProfiles.length} permission profile file(s)...`);
|
|
18264
|
+
try {
|
|
18265
|
+
const result = await api.syncPermissionProfileSources(config.sandboxId, permissionProfiles);
|
|
18266
|
+
syncingProfiles.succeed(` Permission profiles synced: ${brand.secondary(result.digest)}`);
|
|
18267
|
+
} catch (err) {
|
|
18268
|
+
syncingProfiles.fail(` Failed to sync permission profiles: ${err.message}`);
|
|
18269
|
+
process.exit(1);
|
|
18270
|
+
}
|
|
17113
18271
|
const uploading = spinner(`Uploading manifest "${manifest.name}"...`);
|
|
17114
18272
|
let uploadedManifest;
|
|
17115
18273
|
try {
|
|
@@ -17184,6 +18342,25 @@ async function deployCommand(options = {}) {
|
|
|
17184
18342
|
step(options.prod ? "Deploy to dev and prod" : "Deploy to dev", `Sandbox ${config.sandboxId}`);
|
|
17185
18343
|
console.log();
|
|
17186
18344
|
const manifest = config.project.manifest;
|
|
18345
|
+
const permissionProfiles = readPermissionProfileFiles();
|
|
18346
|
+
const localPolicyValidation = await compilePolicyBundle({
|
|
18347
|
+
sandboxId: config.sandboxId,
|
|
18348
|
+
effects: extractManifestEffects(manifest),
|
|
18349
|
+
profileSources: permissionProfiles
|
|
18350
|
+
});
|
|
18351
|
+
if (localPolicyValidation.errors.length > 0) {
|
|
18352
|
+
error("Permission policy validation failed:");
|
|
18353
|
+
for (const error2 of localPolicyValidation.errors) error(` ${error2}`);
|
|
18354
|
+
process.exit(1);
|
|
18355
|
+
}
|
|
18356
|
+
const syncingProfiles = spinner(`Syncing ${permissionProfiles.length} permission profile file(s)...`);
|
|
18357
|
+
try {
|
|
18358
|
+
const result = await api.syncPermissionProfileSources(config.sandboxId, permissionProfiles);
|
|
18359
|
+
syncingProfiles.succeed(` Permission profiles synced: ${brand.secondary(result.digest)}`);
|
|
18360
|
+
} catch (err) {
|
|
18361
|
+
syncingProfiles.fail(` Failed to sync permission profiles: ${err.message}`);
|
|
18362
|
+
process.exit(1);
|
|
18363
|
+
}
|
|
17187
18364
|
const uploading = spinner(`Uploading manifest "${manifest.name}"...`);
|
|
17188
18365
|
let uploadedManifest;
|
|
17189
18366
|
try {
|
|
@@ -17726,11 +18903,22 @@ var TOKEN_REFRESH_LEEWAY_MS = 2 * 60 * 1e3;
|
|
|
17726
18903
|
var TOKEN_REFRESH_RETRY_MS = 30 * 1e3;
|
|
17727
18904
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
17728
18905
|
var DEBUG_WS = process.env.GRANULAR_DEBUG_WS === "1";
|
|
18906
|
+
var DEFAULT_RPC_TIMEOUT_MS = 3e4;
|
|
18907
|
+
var DOMAIN_PACKAGE_RPC_TIMEOUT_MS = 12e4;
|
|
17729
18908
|
function debugWs(...args) {
|
|
17730
18909
|
if (DEBUG_WS) {
|
|
17731
18910
|
console.log(...args);
|
|
17732
18911
|
}
|
|
17733
18912
|
}
|
|
18913
|
+
function rpcTimeoutMsForMethod(method) {
|
|
18914
|
+
switch (method) {
|
|
18915
|
+
case "domain.fetchPackagePart":
|
|
18916
|
+
case "domain.getSummary":
|
|
18917
|
+
return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
|
|
18918
|
+
default:
|
|
18919
|
+
return DEFAULT_RPC_TIMEOUT_MS;
|
|
18920
|
+
}
|
|
18921
|
+
}
|
|
17734
18922
|
var WSClient = class {
|
|
17735
18923
|
ws = null;
|
|
17736
18924
|
url;
|
|
@@ -18155,13 +19343,14 @@ var WSClient = class {
|
|
|
18155
19343
|
return new Promise((resolve2, reject) => {
|
|
18156
19344
|
this.messageQueue.push({ resolve: resolve2, reject, id });
|
|
18157
19345
|
this.ws.send(JSON.stringify(request));
|
|
19346
|
+
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
18158
19347
|
setTimeout(() => {
|
|
18159
19348
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
18160
19349
|
if (pending) {
|
|
18161
19350
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
18162
19351
|
reject(new Error(`RPC timeout: ${method}`));
|
|
18163
19352
|
}
|
|
18164
|
-
},
|
|
19353
|
+
}, timeoutMs);
|
|
18165
19354
|
});
|
|
18166
19355
|
}
|
|
18167
19356
|
async handleIncomingRpc(request) {
|
|
@@ -18275,10 +19464,48 @@ function normalizePromptText(value) {
|
|
|
18275
19464
|
function extractPromptTokens(value) {
|
|
18276
19465
|
return normalizePromptText(value).split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
18277
19466
|
}
|
|
19467
|
+
function parseJsonPromptChoiceOption(option) {
|
|
19468
|
+
const trimmed = option.trim();
|
|
19469
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
|
|
19470
|
+
try {
|
|
19471
|
+
const parsed = JSON.parse(trimmed);
|
|
19472
|
+
return asRecord(parsed);
|
|
19473
|
+
} catch {
|
|
19474
|
+
return null;
|
|
19475
|
+
}
|
|
19476
|
+
}
|
|
19477
|
+
function normalizePromptChoiceOption(option) {
|
|
19478
|
+
if (typeof option === "string") {
|
|
19479
|
+
const record2 = parseJsonPromptChoiceOption(option);
|
|
19480
|
+
if (!record2) {
|
|
19481
|
+
return { value: option, label: option };
|
|
19482
|
+
}
|
|
19483
|
+
const value2 = typeof record2.value === "string" ? record2.value : typeof record2.id === "string" ? record2.id : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
|
|
19484
|
+
return {
|
|
19485
|
+
value: value2,
|
|
19486
|
+
label: typeof record2.label === "string" ? record2.label : value2,
|
|
19487
|
+
description: typeof record2.description === "string" ? record2.description : void 0
|
|
19488
|
+
};
|
|
19489
|
+
}
|
|
19490
|
+
const record = option;
|
|
19491
|
+
if (!record) {
|
|
19492
|
+
return { value: "", label: "" };
|
|
19493
|
+
}
|
|
19494
|
+
const nestedJson = (typeof record.value === "string" ? parseJsonPromptChoiceOption(record.value) : null) || (typeof record.label === "string" ? parseJsonPromptChoiceOption(record.label) : null);
|
|
19495
|
+
if (nestedJson) {
|
|
19496
|
+
return normalizePromptChoiceOption(nestedJson);
|
|
19497
|
+
}
|
|
19498
|
+
const value = typeof record.value === "string" ? record.value : typeof record.label === "string" ? record.label : JSON.stringify(record);
|
|
19499
|
+
return {
|
|
19500
|
+
value,
|
|
19501
|
+
label: typeof record.label === "string" ? record.label : value,
|
|
19502
|
+
description: typeof record.description === "string" ? record.description : void 0
|
|
19503
|
+
};
|
|
19504
|
+
}
|
|
18278
19505
|
function scorePromptChoiceMatch(answer, answerTokens, option) {
|
|
18279
|
-
const
|
|
18280
|
-
const
|
|
18281
|
-
const description =
|
|
19506
|
+
const choice = normalizePromptChoiceOption(option);
|
|
19507
|
+
const { value, label } = choice;
|
|
19508
|
+
const description = choice.description || "";
|
|
18282
19509
|
const haystack = normalizePromptText([value, label, description].filter(Boolean).join(" "));
|
|
18283
19510
|
if (!haystack) return { score: 0, resolvedValue: value || label || null };
|
|
18284
19511
|
let score = 0;
|
|
@@ -18314,7 +19541,9 @@ function normalizePrompt(rawValue) {
|
|
|
18314
19541
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
18315
19542
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
18316
19543
|
message: typeof source.message === "string" ? source.message : "",
|
|
18317
|
-
options: Array.isArray(source.options) ? source.options
|
|
19544
|
+
options: Array.isArray(source.options) ? source.options.map(
|
|
19545
|
+
(option) => typeof option === "string" || asRecord(option) ? normalizePromptChoiceOption(option) : option
|
|
19546
|
+
) : void 0,
|
|
18318
19547
|
defaultValue: source.defaultValue,
|
|
18319
19548
|
placeholder: typeof source.placeholder === "string" ? source.placeholder : void 0,
|
|
18320
19549
|
allowEmpty: typeof source.allowEmpty === "boolean" ? source.allowEmpty : void 0,
|
|
@@ -18342,6 +19571,22 @@ function resolvePromptAnswer(prompt3, answer) {
|
|
|
18342
19571
|
}
|
|
18343
19572
|
|
|
18344
19573
|
// src/session.ts
|
|
19574
|
+
var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
|
|
19575
|
+
function withPromptTranscriptTimeout(promise) {
|
|
19576
|
+
let timeout = null;
|
|
19577
|
+
return Promise.race([
|
|
19578
|
+
promise,
|
|
19579
|
+
new Promise((_, reject) => {
|
|
19580
|
+
timeout = setTimeout(() => {
|
|
19581
|
+
reject(new Error("Timed out appending prompt answer transcript."));
|
|
19582
|
+
}, PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS);
|
|
19583
|
+
})
|
|
19584
|
+
]).finally(() => {
|
|
19585
|
+
if (timeout) {
|
|
19586
|
+
clearTimeout(timeout);
|
|
19587
|
+
}
|
|
19588
|
+
});
|
|
19589
|
+
}
|
|
18345
19590
|
var Session = class {
|
|
18346
19591
|
client;
|
|
18347
19592
|
clientId;
|
|
@@ -18358,6 +19603,8 @@ var Session = class {
|
|
|
18358
19603
|
lastKnownTools = /* @__PURE__ */ new Map();
|
|
18359
19604
|
/** Last seen live prompts, keyed by prompt id, for answer normalization */
|
|
18360
19605
|
promptCache = /* @__PURE__ */ new Map();
|
|
19606
|
+
/** Prompt ids locally answered before the document sync catches up. */
|
|
19607
|
+
hiddenPromptIds = /* @__PURE__ */ new Set();
|
|
18361
19608
|
constructor(client, clientId) {
|
|
18362
19609
|
this.client = client;
|
|
18363
19610
|
this.clientId = clientId || `client_${Date.now()}`;
|
|
@@ -18493,8 +19740,9 @@ var Session = class {
|
|
|
18493
19740
|
* `effect.invoke` RPC back to the sandbox effect host, where the registered handlers
|
|
18494
19741
|
* execute locally and return the result to the sandbox.
|
|
18495
19742
|
*/
|
|
18496
|
-
async submitJob(code,
|
|
18497
|
-
|
|
19743
|
+
async submitJob(code, domainRevisionOrOptions) {
|
|
19744
|
+
const options = typeof domainRevisionOrOptions === "string" ? { domainRevision: domainRevisionOrOptions } : domainRevisionOrOptions || {};
|
|
19745
|
+
let revision = options.domainRevision || this.currentDomainRevision || this.extractDomainRevisionFromDoc(this.client.doc) || void 0;
|
|
18498
19746
|
if (!revision) {
|
|
18499
19747
|
try {
|
|
18500
19748
|
const summary = await this.getDomain();
|
|
@@ -18509,7 +19757,9 @@ var Session = class {
|
|
|
18509
19757
|
}
|
|
18510
19758
|
const result = await this.client.call("job.submit", {
|
|
18511
19759
|
domainRevision: revision,
|
|
18512
|
-
code
|
|
19760
|
+
code,
|
|
19761
|
+
metadata: options.metadata,
|
|
19762
|
+
agent: options.agent
|
|
18513
19763
|
});
|
|
18514
19764
|
if (!result.jobId) {
|
|
18515
19765
|
throw new Error("Failed to submit job: no jobId returned");
|
|
@@ -18550,25 +19800,39 @@ var Session = class {
|
|
|
18550
19800
|
const prompt3 = this.promptCache.get(promptId);
|
|
18551
19801
|
const resolvedAnswer = resolvePromptAnswer(prompt3, answer);
|
|
18552
19802
|
this.promptCache.delete(promptId);
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
answer
|
|
18556
|
-
|
|
18557
|
-
|
|
19803
|
+
this.hiddenPromptIds.add(promptId);
|
|
19804
|
+
try {
|
|
19805
|
+
await this.client.call("prompt.answer", {
|
|
19806
|
+
promptId,
|
|
19807
|
+
answer: resolvedAnswer,
|
|
19808
|
+
value: resolvedAnswer
|
|
19809
|
+
});
|
|
19810
|
+
} catch (error2) {
|
|
19811
|
+
this.hiddenPromptIds.delete(promptId);
|
|
19812
|
+
if (prompt3) {
|
|
19813
|
+
this.promptCache.set(promptId, prompt3);
|
|
19814
|
+
}
|
|
19815
|
+
throw error2;
|
|
19816
|
+
}
|
|
18558
19817
|
try {
|
|
18559
19818
|
const content = this.stringifyConversationValue(resolvedAnswer);
|
|
18560
19819
|
if (content.trim()) {
|
|
18561
|
-
await
|
|
18562
|
-
|
|
18563
|
-
|
|
18564
|
-
|
|
18565
|
-
|
|
19820
|
+
await withPromptTranscriptTimeout(
|
|
19821
|
+
this.appendConversationMessage({
|
|
19822
|
+
role: "user",
|
|
19823
|
+
content,
|
|
19824
|
+
promptId
|
|
19825
|
+
})
|
|
19826
|
+
);
|
|
18566
19827
|
}
|
|
18567
19828
|
} catch {
|
|
18568
19829
|
}
|
|
18569
19830
|
}
|
|
18570
19831
|
async appendConversationMessage(input) {
|
|
18571
|
-
return this.client.call(
|
|
19832
|
+
return this.client.call(
|
|
19833
|
+
"conversation.append",
|
|
19834
|
+
input
|
|
19835
|
+
);
|
|
18572
19836
|
}
|
|
18573
19837
|
/**
|
|
18574
19838
|
* Get the current list of available effects.
|
|
@@ -18577,9 +19841,53 @@ var Session = class {
|
|
|
18577
19841
|
getEffects() {
|
|
18578
19842
|
const doc = this.client.doc;
|
|
18579
19843
|
const toolMap = /* @__PURE__ */ new Map();
|
|
18580
|
-
const
|
|
18581
|
-
|
|
18582
|
-
|
|
19844
|
+
const domainPackages = doc.domain?.packages;
|
|
19845
|
+
const packageCandidates = domainPackages && typeof domainPackages === "object" ? [
|
|
19846
|
+
domainPackages.domain,
|
|
19847
|
+
domainPackages["@sandbox/domain"],
|
|
19848
|
+
...Object.values(domainPackages)
|
|
19849
|
+
].filter(Boolean) : [];
|
|
19850
|
+
for (const domainPkg of packageCandidates) {
|
|
19851
|
+
if (domainPkg?.tools && Array.isArray(domainPkg.tools)) {
|
|
19852
|
+
for (const tool of domainPkg.tools) {
|
|
19853
|
+
if (!tool?.name || toolMap.has(tool.name)) continue;
|
|
19854
|
+
toolMap.set(tool.name, {
|
|
19855
|
+
name: tool.name,
|
|
19856
|
+
description: tool.description,
|
|
19857
|
+
inputSchema: tool.inputSchema,
|
|
19858
|
+
outputSchema: tool.outputSchema,
|
|
19859
|
+
className: tool.className || void 0,
|
|
19860
|
+
static: tool.static || false,
|
|
19861
|
+
ready: false,
|
|
19862
|
+
publishedAt: void 0
|
|
19863
|
+
});
|
|
19864
|
+
}
|
|
19865
|
+
}
|
|
19866
|
+
if (!domainPkg?.classes || typeof domainPkg.classes !== "object") {
|
|
19867
|
+
continue;
|
|
19868
|
+
}
|
|
19869
|
+
for (const [className, classDef] of Object.entries(
|
|
19870
|
+
domainPkg.classes
|
|
19871
|
+
)) {
|
|
19872
|
+
const methods = Array.isArray(classDef?.methods) ? classDef.methods : [];
|
|
19873
|
+
for (const method of methods) {
|
|
19874
|
+
if (!method?.name || toolMap.has(method.name)) continue;
|
|
19875
|
+
toolMap.set(method.name, {
|
|
19876
|
+
name: method.name,
|
|
19877
|
+
description: method.description,
|
|
19878
|
+
inputSchema: method.inputSchema,
|
|
19879
|
+
outputSchema: method.outputSchema,
|
|
19880
|
+
className: method.className || classDef?.name || className,
|
|
19881
|
+
static: method.static || false,
|
|
19882
|
+
ready: false,
|
|
19883
|
+
publishedAt: void 0
|
|
19884
|
+
});
|
|
19885
|
+
}
|
|
19886
|
+
}
|
|
19887
|
+
}
|
|
19888
|
+
const legacyDomainPkg = doc.domain?.packages?.domain;
|
|
19889
|
+
if (legacyDomainPkg?.tools && Array.isArray(legacyDomainPkg.tools)) {
|
|
19890
|
+
for (const tool of legacyDomainPkg.tools) {
|
|
18583
19891
|
if (!tool?.name) continue;
|
|
18584
19892
|
toolMap.set(tool.name, {
|
|
18585
19893
|
name: tool.name,
|
|
@@ -18593,6 +19901,27 @@ var Session = class {
|
|
|
18593
19901
|
});
|
|
18594
19902
|
}
|
|
18595
19903
|
}
|
|
19904
|
+
if (legacyDomainPkg?.classes && typeof legacyDomainPkg.classes === "object") {
|
|
19905
|
+
for (const [className, classDef] of Object.entries(
|
|
19906
|
+
legacyDomainPkg.classes
|
|
19907
|
+
)) {
|
|
19908
|
+
const methods = Array.isArray(classDef?.methods) ? classDef.methods : [];
|
|
19909
|
+
for (const method of methods) {
|
|
19910
|
+
if (!method?.name || toolMap.has(method.name)) continue;
|
|
19911
|
+
toolMap.set(method.name, {
|
|
19912
|
+
name: method.name,
|
|
19913
|
+
description: method.description,
|
|
19914
|
+
inputSchema: method.inputSchema,
|
|
19915
|
+
outputSchema: method.outputSchema,
|
|
19916
|
+
className: method.className || classDef?.name || className,
|
|
19917
|
+
static: method.static || false,
|
|
19918
|
+
ready: false,
|
|
19919
|
+
publishedAt: void 0
|
|
19920
|
+
});
|
|
19921
|
+
}
|
|
19922
|
+
}
|
|
19923
|
+
}
|
|
19924
|
+
const hasPolicyFilteredDomainTools = toolMap.size > 0;
|
|
18596
19925
|
const catalogs = doc.catalog?.rawToolCatalogs || {};
|
|
18597
19926
|
for (const [clientId, catalog] of Object.entries(catalogs)) {
|
|
18598
19927
|
const cat = catalog;
|
|
@@ -18600,6 +19929,7 @@ var Session = class {
|
|
|
18600
19929
|
for (const tool of cat.tools) {
|
|
18601
19930
|
if (!tool?.name) continue;
|
|
18602
19931
|
const existing = toolMap.get(tool.name);
|
|
19932
|
+
if (hasPolicyFilteredDomainTools && !existing) continue;
|
|
18603
19933
|
if (existing?.publishedAt && cat.publishedAt && existing.publishedAt > cat.publishedAt)
|
|
18604
19934
|
continue;
|
|
18605
19935
|
const isLocal = clientId === this.clientId;
|
|
@@ -18619,6 +19949,24 @@ var Session = class {
|
|
|
18619
19949
|
}
|
|
18620
19950
|
return Array.from(toolMap.values());
|
|
18621
19951
|
}
|
|
19952
|
+
/**
|
|
19953
|
+
* Return the currently open prompt payloads known to this session.
|
|
19954
|
+
*
|
|
19955
|
+
* These come from live `prompt` / `prompt.request` websocket events and
|
|
19956
|
+
* preserve the exact shape used by `answerPrompt(...)`.
|
|
19957
|
+
*/
|
|
19958
|
+
getPrompts() {
|
|
19959
|
+
return Array.from(this.promptCache.values()).map((prompt3) => ({
|
|
19960
|
+
...prompt3,
|
|
19961
|
+
options: Array.isArray(prompt3.options) ? prompt3.options.map(
|
|
19962
|
+
(option) => typeof option === "string" ? option : { ...option }
|
|
19963
|
+
) : void 0,
|
|
19964
|
+
metadata: prompt3.metadata ? { ...prompt3.metadata } : void 0
|
|
19965
|
+
}));
|
|
19966
|
+
}
|
|
19967
|
+
getHiddenPromptIds() {
|
|
19968
|
+
return Array.from(this.hiddenPromptIds);
|
|
19969
|
+
}
|
|
18622
19970
|
/**
|
|
18623
19971
|
* Backwards-compatible alias for `getEffects()`.
|
|
18624
19972
|
*/
|
|
@@ -18718,11 +20066,7 @@ var Session = class {
|
|
|
18718
20066
|
if (!normalizedDocs) {
|
|
18719
20067
|
return normalizedTypes;
|
|
18720
20068
|
}
|
|
18721
|
-
return [
|
|
18722
|
-
normalizedTypes,
|
|
18723
|
-
"Generated usage notes from ./sandbox-tools docs:",
|
|
18724
|
-
normalizedDocs
|
|
18725
|
-
].join("\n\n");
|
|
20069
|
+
return [normalizedTypes, "[Docs]", normalizedDocs].join("\n\n");
|
|
18726
20070
|
}
|
|
18727
20071
|
if (normalizedDocs) {
|
|
18728
20072
|
return normalizedDocs;
|
|
@@ -18944,6 +20288,7 @@ import { ${allImports} } from "./sandbox-tools";
|
|
|
18944
20288
|
const emitPrompt = (payload) => {
|
|
18945
20289
|
const prompt3 = normalizePrompt(payload);
|
|
18946
20290
|
if (!prompt3) return;
|
|
20291
|
+
this.hiddenPromptIds.delete(prompt3.id);
|
|
18947
20292
|
this.promptCache.set(prompt3.id, prompt3);
|
|
18948
20293
|
this.emit("prompt", prompt3);
|
|
18949
20294
|
};
|
|
@@ -19126,6 +20471,7 @@ var JobImplementation = class {
|
|
|
19126
20471
|
eventListeners = /* @__PURE__ */ new Map();
|
|
19127
20472
|
bufferedAgentMessages = [];
|
|
19128
20473
|
bufferedAgentMessageIds = /* @__PURE__ */ new Set();
|
|
20474
|
+
resultSettled = false;
|
|
19129
20475
|
metadata;
|
|
19130
20476
|
constructor(id, client, initialState) {
|
|
19131
20477
|
this.id = id;
|
|
@@ -19150,7 +20496,9 @@ var JobImplementation = class {
|
|
|
19150
20496
|
if (execData.error) {
|
|
19151
20497
|
this.finalize("failed", void 0, execData.error);
|
|
19152
20498
|
} else {
|
|
19153
|
-
this.finalize("succeeded", execData.result
|
|
20499
|
+
this.finalize("succeeded", execData.result, void 0, {
|
|
20500
|
+
hasResult: Object.prototype.hasOwnProperty.call(execData, "result")
|
|
20501
|
+
});
|
|
19154
20502
|
}
|
|
19155
20503
|
this.emit("status", this.status);
|
|
19156
20504
|
}
|
|
@@ -19186,9 +20534,6 @@ var JobImplementation = class {
|
|
|
19186
20534
|
if (normalizedStatus === "failed" || normalizedStatus === "timeout" || normalizedStatus === "canceled") {
|
|
19187
20535
|
this.finalize(normalizedStatus);
|
|
19188
20536
|
}
|
|
19189
|
-
if (normalizedStatus === "succeeded") {
|
|
19190
|
-
this.finalize("succeeded");
|
|
19191
|
-
}
|
|
19192
20537
|
this.emit("status", normalizedStatus);
|
|
19193
20538
|
});
|
|
19194
20539
|
this.client.on(`job.${id}.stdout`, (line) => {
|
|
@@ -19208,7 +20553,7 @@ var JobImplementation = class {
|
|
|
19208
20553
|
this.emit("stderr", line);
|
|
19209
20554
|
});
|
|
19210
20555
|
this.client.on(`job.${id}.result`, (result) => {
|
|
19211
|
-
this.finalize("succeeded", result);
|
|
20556
|
+
this.finalize("succeeded", result, void 0, { hasResult: true });
|
|
19212
20557
|
});
|
|
19213
20558
|
this.client.on(`job.${id}.error`, (error2) => {
|
|
19214
20559
|
this.finalize("failed", void 0, error2);
|
|
@@ -19229,7 +20574,9 @@ var JobImplementation = class {
|
|
|
19229
20574
|
this.client.on("job.completed", (data) => {
|
|
19230
20575
|
const jobData = data;
|
|
19231
20576
|
if (jobData.jobId === id) {
|
|
19232
|
-
this.finalize("succeeded", jobData.result
|
|
20577
|
+
this.finalize("succeeded", jobData.result, void 0, {
|
|
20578
|
+
hasResult: true
|
|
20579
|
+
});
|
|
19233
20580
|
this.emit("status", this.status);
|
|
19234
20581
|
}
|
|
19235
20582
|
});
|
|
@@ -19354,7 +20701,7 @@ var JobImplementation = class {
|
|
|
19354
20701
|
this.metadata.status = "running";
|
|
19355
20702
|
}
|
|
19356
20703
|
}
|
|
19357
|
-
finalize(status, result, error2) {
|
|
20704
|
+
finalize(status, result, error2, options = {}) {
|
|
19358
20705
|
if (!this.metadata.startedAt) {
|
|
19359
20706
|
this.metadata.startedAt = Date.now();
|
|
19360
20707
|
}
|
|
@@ -19362,14 +20709,18 @@ var JobImplementation = class {
|
|
|
19362
20709
|
this.metadata.status = status;
|
|
19363
20710
|
this.metadata.completedAt = this.metadata.completedAt || Date.now();
|
|
19364
20711
|
this.metadata.durationMs = this.metadata.completedAt - this.metadata.startedAt;
|
|
19365
|
-
if (result !== void 0) {
|
|
20712
|
+
if (!this.resultSettled && (options.hasResult || result !== void 0)) {
|
|
19366
20713
|
this.metadata.result = sanitizeFeedbackValue(result);
|
|
20714
|
+
this.resultSettled = true;
|
|
19367
20715
|
this._resolveResult(result);
|
|
19368
20716
|
}
|
|
19369
|
-
if (error2 !== void 0) {
|
|
19370
|
-
const
|
|
20717
|
+
if (!this.resultSettled && (error2 !== void 0 || status === "failed" || status === "timeout" || status === "canceled")) {
|
|
20718
|
+
const fallbackError = new Error(`Job ${this.id} ${status}.`);
|
|
20719
|
+
const cause = error2 ?? fallbackError;
|
|
20720
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
19371
20721
|
this.metadata.error = truncateFeedbackString(message);
|
|
19372
|
-
this.
|
|
20722
|
+
this.resultSettled = true;
|
|
20723
|
+
this._rejectResult(cause);
|
|
19373
20724
|
}
|
|
19374
20725
|
}
|
|
19375
20726
|
upsertToolCall(next) {
|
|
@@ -19452,6 +20803,17 @@ function humanTextFromStdout(stdout) {
|
|
|
19452
20803
|
}
|
|
19453
20804
|
return null;
|
|
19454
20805
|
}
|
|
20806
|
+
function responseTextFromAgentMessages(agentMessages) {
|
|
20807
|
+
for (const message of [...agentMessages].reverse()) {
|
|
20808
|
+
const record = asRecord2(message);
|
|
20809
|
+
if (!record) continue;
|
|
20810
|
+
for (const key of RESPONSE_KEYS) {
|
|
20811
|
+
const normalized = normalizeText(record[key]);
|
|
20812
|
+
if (normalized) return normalized;
|
|
20813
|
+
}
|
|
20814
|
+
}
|
|
20815
|
+
return null;
|
|
20816
|
+
}
|
|
19455
20817
|
function pushString(target, value) {
|
|
19456
20818
|
if (typeof value === "string" && value.trim()) {
|
|
19457
20819
|
target.add(value.trim());
|
|
@@ -19477,6 +20839,41 @@ function collectReferencesFromRecord(record, refs) {
|
|
|
19477
20839
|
for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
|
|
19478
20840
|
pushStringArray(refs.variableNames, record[key]);
|
|
19479
20841
|
}
|
|
20842
|
+
function stringValue(record, keys) {
|
|
20843
|
+
for (const key of keys) {
|
|
20844
|
+
const value = record[key];
|
|
20845
|
+
if (typeof value === "string" && value.trim()) {
|
|
20846
|
+
return value.trim();
|
|
20847
|
+
}
|
|
20848
|
+
}
|
|
20849
|
+
return null;
|
|
20850
|
+
}
|
|
20851
|
+
function findEntryPathForRecord(record, heap) {
|
|
20852
|
+
const directPath = stringValue(record, ["entryPath", "path"]);
|
|
20853
|
+
if (directPath && heap.entriesByPath?.[directPath]) {
|
|
20854
|
+
return directPath;
|
|
20855
|
+
}
|
|
20856
|
+
const id = stringValue(record, ["id", "_id", "recordId", "objectId"]);
|
|
20857
|
+
if (!id) {
|
|
20858
|
+
return null;
|
|
20859
|
+
}
|
|
20860
|
+
const className = stringValue(record, [
|
|
20861
|
+
"className",
|
|
20862
|
+
"_className",
|
|
20863
|
+
"__className",
|
|
20864
|
+
"prototype",
|
|
20865
|
+
"type"
|
|
20866
|
+
]);
|
|
20867
|
+
const entries = Object.values(heap.entriesByPath || {});
|
|
20868
|
+
const exact = entries.find(
|
|
20869
|
+
(entry) => entry.id === id && (!className || entry.className === className || entry.prototypes?.includes(className))
|
|
20870
|
+
);
|
|
20871
|
+
if (exact?.path) {
|
|
20872
|
+
return exact.path;
|
|
20873
|
+
}
|
|
20874
|
+
const idOnlyMatches = entries.filter((entry) => entry.id === id);
|
|
20875
|
+
return idOnlyMatches.length === 1 ? idOnlyMatches[0].path : null;
|
|
20876
|
+
}
|
|
19480
20877
|
function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
19481
20878
|
if (value === null || value === void 0 || depth > 4 || seen.has(value))
|
|
19482
20879
|
return;
|
|
@@ -19497,6 +20894,8 @@ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__
|
|
|
19497
20894
|
const record = asRecord2(value);
|
|
19498
20895
|
if (!record) return;
|
|
19499
20896
|
seen.add(value);
|
|
20897
|
+
const entryPath = findEntryPathForRecord(record, heap);
|
|
20898
|
+
if (entryPath) refs.entryPaths.add(entryPath);
|
|
19500
20899
|
collectReferencesFromRecord(record, refs);
|
|
19501
20900
|
for (const key of UI_CONTAINER_KEYS) {
|
|
19502
20901
|
const nested = asRecord2(record[key]);
|
|
@@ -19594,8 +20993,8 @@ function getJobRelatedLists(heap, jobId) {
|
|
|
19594
20993
|
function entriesFromLists(lists, heap) {
|
|
19595
20994
|
const entries = [];
|
|
19596
20995
|
for (const list of lists) {
|
|
19597
|
-
for (const
|
|
19598
|
-
const entry = heap.entriesByPath?.[
|
|
20996
|
+
for (const path7 of list.paths || []) {
|
|
20997
|
+
const entry = heap.entriesByPath?.[path7];
|
|
19599
20998
|
if (entry) entries.push(entry);
|
|
19600
20999
|
}
|
|
19601
21000
|
}
|
|
@@ -19605,6 +21004,7 @@ function resolveJobPresentation({
|
|
|
19605
21004
|
jobId,
|
|
19606
21005
|
result,
|
|
19607
21006
|
stdout = [],
|
|
21007
|
+
agentMessages = [],
|
|
19608
21008
|
sessionHeap,
|
|
19609
21009
|
allowExplicitArtifacts = true
|
|
19610
21010
|
}) {
|
|
@@ -19621,7 +21021,7 @@ function resolveJobPresentation({
|
|
|
19621
21021
|
[...refs.listNames].map((name) => sessionHeap.listsByName?.[name]).filter((list) => Boolean(list))
|
|
19622
21022
|
);
|
|
19623
21023
|
const referencedEntries = sortEntries(
|
|
19624
|
-
[...refs.entryPaths].map((
|
|
21024
|
+
[...refs.entryPaths].map((path7) => sessionHeap.entriesByPath?.[path7]).filter((entry) => Boolean(entry))
|
|
19625
21025
|
);
|
|
19626
21026
|
const jobLists = getJobRelatedLists(sessionHeap, jobId);
|
|
19627
21027
|
const jobEntries = getJobRelatedEntries(sessionHeap, jobId);
|
|
@@ -19637,7 +21037,7 @@ function resolveJobPresentation({
|
|
|
19637
21037
|
const hasExplicitArtifacts = allowExplicitArtifacts && (explicitEntries.length > 0 || explicitLists.length > 0);
|
|
19638
21038
|
const lists = hasExplicitArtifacts ? explicitLists : jobLists;
|
|
19639
21039
|
const entries = hasExplicitArtifacts ? explicitEntries : changedEntries;
|
|
19640
|
-
const responseText = extractResponseText(result, stdout) || fallbackResponseText(entries, lists);
|
|
21040
|
+
const responseText = extractResponseText(result, stdout) || responseTextFromAgentMessages(agentMessages) || fallbackResponseText(entries, lists);
|
|
19641
21041
|
return {
|
|
19642
21042
|
responseText,
|
|
19643
21043
|
entries,
|
|
@@ -20172,6 +21572,12 @@ var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
|
|
|
20172
21572
|
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS = 1e3;
|
|
20173
21573
|
var LOCAL_CONTROL_REQUEST_RETRY_COUNT = 4;
|
|
20174
21574
|
var LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS = 500;
|
|
21575
|
+
var SESSION_DATA_REQUEST_RETRY_COUNT = 4;
|
|
21576
|
+
var SESSION_DATA_REQUEST_RETRY_DELAY_MS = 500;
|
|
21577
|
+
var EFFECT_HOST_CONNECT_TIMEOUT_MS = 15e3;
|
|
21578
|
+
var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 3e4;
|
|
21579
|
+
var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
|
|
21580
|
+
var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
|
|
20175
21581
|
function planRecordObjectsChunks(records, batchSize) {
|
|
20176
21582
|
const total = records.length;
|
|
20177
21583
|
const size = Math.max(1, Math.min(batchSize, total));
|
|
@@ -20186,6 +21592,19 @@ function planRecordObjectsChunks(records, batchSize) {
|
|
|
20186
21592
|
function sleep(ms) {
|
|
20187
21593
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
20188
21594
|
}
|
|
21595
|
+
function withTimeout(promise, timeoutMs, label) {
|
|
21596
|
+
let timer = null;
|
|
21597
|
+
const timeout = new Promise((_, reject) => {
|
|
21598
|
+
timer = setTimeout(() => {
|
|
21599
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
21600
|
+
}, timeoutMs);
|
|
21601
|
+
});
|
|
21602
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
21603
|
+
if (timer) {
|
|
21604
|
+
clearTimeout(timer);
|
|
21605
|
+
}
|
|
21606
|
+
});
|
|
21607
|
+
}
|
|
20189
21608
|
function isLocalControlUrl(url) {
|
|
20190
21609
|
try {
|
|
20191
21610
|
const parsed = new URL(url);
|
|
@@ -20199,7 +21618,19 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
20199
21618
|
}
|
|
20200
21619
|
function isRetryableRecordObjectsError(error2) {
|
|
20201
21620
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
20202
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
21621
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out|bad gateway|too many requests|gateway timeout|control plane api error \((?:429|500|502|503|504)\)|graphql api error \((?:429|500|502|503|504)\)|failed to record batch/i.test(
|
|
21622
|
+
message
|
|
21623
|
+
);
|
|
21624
|
+
}
|
|
21625
|
+
function isRetryableEffectRegistrationError(error2) {
|
|
21626
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
21627
|
+
return /timed out|websocket disconnected|websocket not connected|rpc timeout|worker restarted mid-request|network connection lost|bad gateway|gateway timeout|too many requests|(?:control plane|granular|graphql) api error \((?:429|500|502|503|504)\)/i.test(
|
|
21628
|
+
message
|
|
21629
|
+
);
|
|
21630
|
+
}
|
|
21631
|
+
function isRetryableSessionDataError(error2) {
|
|
21632
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
21633
|
+
return /network connection lost|worker restarted mid-request|econnreset|socket connection was closed unexpectedly|bad gateway|gateway timeout|service unavailable|session data api error \((?:429|500|502|503|504)\)/i.test(
|
|
20203
21634
|
message
|
|
20204
21635
|
);
|
|
20205
21636
|
}
|
|
@@ -20221,16 +21652,28 @@ function computeEffectRegistrationKey(effect) {
|
|
|
20221
21652
|
effect.versionSelector
|
|
20222
21653
|
)}`;
|
|
20223
21654
|
}
|
|
20224
|
-
function buildEffectHostUrl(apiUrl, sandboxId, effectClientId, clientId) {
|
|
20225
|
-
const
|
|
20226
|
-
|
|
21655
|
+
function buildEffectHostUrl(apiUrl, sandboxId, effectClientId, clientId, effectHostUrl) {
|
|
21656
|
+
const overrideUrl = effectHostUrl || process.env.GRANULAR_EFFECT_HOST_URL || process.env.EFFECT_HOST_URL;
|
|
21657
|
+
const api = new URL(apiUrl);
|
|
21658
|
+
const localRuntimeBase = process.env.RUNTIME_ORCHESTRATOR_URL || (isLocalControlUrl(apiUrl) ? `${api.protocol}//${api.hostname}:8791` : "");
|
|
21659
|
+
const url = new URL(overrideUrl || localRuntimeBase || apiUrl);
|
|
21660
|
+
if (url.protocol === "https:") {
|
|
21661
|
+
url.protocol = "wss:";
|
|
21662
|
+
} else if (url.protocol === "http:") {
|
|
21663
|
+
url.protocol = "ws:";
|
|
21664
|
+
}
|
|
21665
|
+
if (!overrideUrl && isLocalControlUrl(apiUrl) && api.pathname.endsWith("/granular")) {
|
|
21666
|
+
url.pathname = "/granular/orchestrator/effects/connect";
|
|
21667
|
+
} else if (url.pathname.endsWith("/granular/ws/connect")) {
|
|
20227
21668
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
20228
21669
|
} else if (url.pathname.endsWith("/granular")) {
|
|
20229
|
-
url.pathname = `${url.pathname.replace(/\/$/, "")}/effects/connect`;
|
|
21670
|
+
url.pathname = isLocalControlUrl(url.toString()) ? "/granular/orchestrator/effects/connect" : `${url.pathname.replace(/\/$/, "")}/effects/connect`;
|
|
20230
21671
|
} else if (url.pathname.endsWith("/v2/ws/connect")) {
|
|
20231
21672
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
20232
21673
|
} else if (url.pathname.endsWith("/v2/ws")) {
|
|
20233
21674
|
url.pathname = url.pathname.replace(/\/ws$/, "/effects/connect");
|
|
21675
|
+
} else if (url.pathname === "/" && isLocalControlUrl(url.toString()) && (url.port === "8791" || !overrideUrl && Boolean(localRuntimeBase))) {
|
|
21676
|
+
url.pathname = "/granular/orchestrator/effects/connect";
|
|
20234
21677
|
} else if (url.pathname.endsWith("/ws/connect")) {
|
|
20235
21678
|
url.pathname = url.pathname.replace(/\/ws\/connect$/, "/effects/connect");
|
|
20236
21679
|
} else if (url.pathname.endsWith("/ws")) {
|
|
@@ -20265,6 +21708,79 @@ function normalizeHeapSnapshot(raw) {
|
|
|
20265
21708
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
20266
21709
|
};
|
|
20267
21710
|
}
|
|
21711
|
+
function normalizeGraphPathSegment(value) {
|
|
21712
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
21713
|
+
}
|
|
21714
|
+
function extractRecordIdFromGraphPath(path7, className) {
|
|
21715
|
+
const normalizedPrefix = `${normalizeGraphPathSegment(className)}_`;
|
|
21716
|
+
if (path7.startsWith(normalizedPrefix)) {
|
|
21717
|
+
return path7.slice(normalizedPrefix.length);
|
|
21718
|
+
}
|
|
21719
|
+
const legacyPrefix = `${className}_`;
|
|
21720
|
+
if (path7.startsWith(legacyPrefix)) {
|
|
21721
|
+
return path7.slice(legacyPrefix.length);
|
|
21722
|
+
}
|
|
21723
|
+
return path7;
|
|
21724
|
+
}
|
|
21725
|
+
function toRecordSearchResult(className, node) {
|
|
21726
|
+
const path7 = typeof node.path === "string" ? node.path : "";
|
|
21727
|
+
if (!path7) return null;
|
|
21728
|
+
const fields = Array.isArray(node.submodels) ? node.submodels.flatMap(
|
|
21729
|
+
(submodel) => {
|
|
21730
|
+
const name = typeof submodel?.label === "string" && submodel.label.trim() ? submodel.label : typeof submodel?.path === "string" ? submodel.path.split(":").pop() || submodel.path : "";
|
|
21731
|
+
if (!name) return [];
|
|
21732
|
+
if (typeof submodel.string_value === "string") {
|
|
21733
|
+
return [{ name, type: "string", value: submodel.string_value }];
|
|
21734
|
+
}
|
|
21735
|
+
if (typeof submodel.number_value === "number") {
|
|
21736
|
+
return [{ name, type: "number", value: submodel.number_value }];
|
|
21737
|
+
}
|
|
21738
|
+
if (typeof submodel.boolean_value === "boolean") {
|
|
21739
|
+
return [
|
|
21740
|
+
{
|
|
21741
|
+
name,
|
|
21742
|
+
type: "boolean",
|
|
21743
|
+
value: submodel.boolean_value
|
|
21744
|
+
}
|
|
21745
|
+
];
|
|
21746
|
+
}
|
|
21747
|
+
return [];
|
|
21748
|
+
}
|
|
21749
|
+
) : [];
|
|
21750
|
+
return {
|
|
21751
|
+
path: path7,
|
|
21752
|
+
className,
|
|
21753
|
+
id: extractRecordIdFromGraphPath(path7, className),
|
|
21754
|
+
label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path7, className),
|
|
21755
|
+
description: typeof node.description === "string" && node.description.trim() ? node.description : null,
|
|
21756
|
+
fields
|
|
21757
|
+
};
|
|
21758
|
+
}
|
|
21759
|
+
function normalizeRecordSearchText(value) {
|
|
21760
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
21761
|
+
}
|
|
21762
|
+
function rankRecordSearchResult(result, query, index) {
|
|
21763
|
+
const normalizedQuery = normalizeRecordSearchText(query);
|
|
21764
|
+
if (!normalizedQuery) {
|
|
21765
|
+
return index;
|
|
21766
|
+
}
|
|
21767
|
+
const label = normalizeRecordSearchText(result.label || "");
|
|
21768
|
+
const id = normalizeRecordSearchText(result.id || "");
|
|
21769
|
+
const path7 = normalizeRecordSearchText(result.path || "");
|
|
21770
|
+
const className = normalizeRecordSearchText(result.className || "");
|
|
21771
|
+
const searchable = [label, id, path7, className].filter(Boolean);
|
|
21772
|
+
if (label === normalizedQuery) return index;
|
|
21773
|
+
if (id === normalizedQuery || path7 === normalizedQuery) return 100 + index;
|
|
21774
|
+
if (label.startsWith(normalizedQuery)) return 200 + index;
|
|
21775
|
+
if (searchable.some((value) => value.startsWith(normalizedQuery))) {
|
|
21776
|
+
return 300 + index;
|
|
21777
|
+
}
|
|
21778
|
+
if (label.includes(normalizedQuery)) return 400 + index;
|
|
21779
|
+
if (searchable.some((value) => value.includes(normalizedQuery))) {
|
|
21780
|
+
return 500 + index;
|
|
21781
|
+
}
|
|
21782
|
+
return 900 + index;
|
|
21783
|
+
}
|
|
20268
21784
|
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
20269
21785
|
try {
|
|
20270
21786
|
const endpoint = new URL(apiEndpoint);
|
|
@@ -20353,7 +21869,7 @@ function normalizeEnvironmentData(environment) {
|
|
|
20353
21869
|
setup: normalizeEnvironmentSetupSummary(environment.setup)
|
|
20354
21870
|
};
|
|
20355
21871
|
}
|
|
20356
|
-
var Environment = class {
|
|
21872
|
+
var Environment = class _Environment {
|
|
20357
21873
|
granular;
|
|
20358
21874
|
envData;
|
|
20359
21875
|
_apiKey;
|
|
@@ -20528,9 +22044,9 @@ var Environment = class {
|
|
|
20528
22044
|
getRuntimeBaseUrl() {
|
|
20529
22045
|
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
20530
22046
|
}
|
|
20531
|
-
async controlPlaneRequest(
|
|
22047
|
+
async controlPlaneRequest(path7, options = {}) {
|
|
20532
22048
|
const runtimeBase = this.getRuntimeBaseUrl();
|
|
20533
|
-
const url = `${runtimeBase}${
|
|
22049
|
+
const url = `${runtimeBase}${path7}`;
|
|
20534
22050
|
const response = await fetch(url, {
|
|
20535
22051
|
...options,
|
|
20536
22052
|
headers: {
|
|
@@ -20548,28 +22064,30 @@ var Environment = class {
|
|
|
20548
22064
|
return response.json();
|
|
20549
22065
|
}
|
|
20550
22066
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
22067
|
+
static normalizeGraphPathSegment(value) {
|
|
22068
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
22069
|
+
}
|
|
20551
22070
|
/**
|
|
20552
|
-
* Convert a class name +
|
|
20553
|
-
*
|
|
20554
|
-
* Two objects of *different* classes may share the same real-world ID,
|
|
20555
|
-
* so the graph path must incorporate the class to guarantee uniqueness.
|
|
20556
|
-
*
|
|
20557
|
-
* Format: `{className}_{id}` — deterministic, human-readable.
|
|
22071
|
+
* Convert a class name + application record ID into Granular's graph path.
|
|
20558
22072
|
*
|
|
20559
|
-
*
|
|
20560
|
-
*
|
|
20561
|
-
*
|
|
22073
|
+
* This mirrors the record-write path normalization used by the control plane.
|
|
22074
|
+
* Keep the original customer/system ID in `real_id`; graph paths are stable
|
|
22075
|
+
* internal addresses, not the source of truth for business identity.
|
|
20562
22076
|
*/
|
|
20563
22077
|
static toGraphPath(className, id) {
|
|
20564
|
-
return `${className}_${id}`;
|
|
22078
|
+
return `${_Environment.normalizeGraphPathSegment(className)}_${_Environment.normalizeGraphPathSegment(id)}`;
|
|
20565
22079
|
}
|
|
20566
22080
|
/**
|
|
20567
|
-
*
|
|
22081
|
+
* Best-effort extraction of an ID-like suffix from a graph path.
|
|
20568
22082
|
*
|
|
20569
|
-
*
|
|
20570
|
-
*
|
|
22083
|
+
* Prefer the record's `real_id` field whenever exact customer/system IDs
|
|
22084
|
+
* matter, because graph path normalization is intentionally lossy.
|
|
20571
22085
|
*/
|
|
20572
22086
|
static extractIdFromGraphPath(graphPath, className) {
|
|
22087
|
+
const normalizedPrefix = `${_Environment.normalizeGraphPathSegment(className)}_`;
|
|
22088
|
+
if (graphPath.startsWith(normalizedPrefix)) {
|
|
22089
|
+
return graphPath.substring(normalizedPrefix.length);
|
|
22090
|
+
}
|
|
20573
22091
|
const prefix = `${className}_`;
|
|
20574
22092
|
return graphPath.startsWith(prefix) ? graphPath.substring(prefix.length) : graphPath;
|
|
20575
22093
|
}
|
|
@@ -20616,6 +22134,62 @@ var Environment = class {
|
|
|
20616
22134
|
}
|
|
20617
22135
|
return response.json();
|
|
20618
22136
|
}
|
|
22137
|
+
async searchRecords(query, options = {}) {
|
|
22138
|
+
const normalizedQuery = query.replace(/\s+/g, " ").trim();
|
|
22139
|
+
const limit = Math.max(1, Math.min(50, Math.floor(options.limit ?? 12)));
|
|
22140
|
+
const offset = Math.max(0, Math.floor(options.offset ?? 0));
|
|
22141
|
+
const response = await this.graphql(
|
|
22142
|
+
`
|
|
22143
|
+
query RecordMentionSearch(
|
|
22144
|
+
$query: String
|
|
22145
|
+
$limit: Int
|
|
22146
|
+
$offset: Int
|
|
22147
|
+
$classNames: [String!]
|
|
22148
|
+
) {
|
|
22149
|
+
record_search(
|
|
22150
|
+
query: $query
|
|
22151
|
+
limit: $limit
|
|
22152
|
+
offset: $offset
|
|
22153
|
+
class_names: $classNames
|
|
22154
|
+
) {
|
|
22155
|
+
className
|
|
22156
|
+
model {
|
|
22157
|
+
path
|
|
22158
|
+
label
|
|
22159
|
+
description
|
|
22160
|
+
submodels {
|
|
22161
|
+
path
|
|
22162
|
+
label
|
|
22163
|
+
string_value
|
|
22164
|
+
number_value
|
|
22165
|
+
boolean_value
|
|
22166
|
+
}
|
|
22167
|
+
}
|
|
22168
|
+
}
|
|
22169
|
+
}
|
|
22170
|
+
`,
|
|
22171
|
+
{
|
|
22172
|
+
query: normalizedQuery,
|
|
22173
|
+
limit,
|
|
22174
|
+
offset,
|
|
22175
|
+
classNames: options.classNames?.length ? options.classNames : []
|
|
22176
|
+
}
|
|
22177
|
+
);
|
|
22178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22179
|
+
const results = (response.data?.record_search || []).flatMap((entry) => {
|
|
22180
|
+
const className = entry.className?.trim();
|
|
22181
|
+
const item = className && entry.model ? toRecordSearchResult(className, entry.model) : null;
|
|
22182
|
+
if (!item || seen.has(item.path)) {
|
|
22183
|
+
return [];
|
|
22184
|
+
}
|
|
22185
|
+
seen.add(item.path);
|
|
22186
|
+
return [item];
|
|
22187
|
+
});
|
|
22188
|
+
return results.map((result, index) => ({
|
|
22189
|
+
result,
|
|
22190
|
+
rank: rankRecordSearchResult(result, normalizedQuery, index)
|
|
22191
|
+
})).sort((left, right) => left.rank - right.rank).map((item) => item.result).slice(0, limit);
|
|
22192
|
+
}
|
|
20619
22193
|
// ==================== RELATIONSHIP METHODS ====================
|
|
20620
22194
|
/**
|
|
20621
22195
|
* Define a relationship between two model types.
|
|
@@ -21381,7 +22955,8 @@ var Environment = class {
|
|
|
21381
22955
|
body: JSON.stringify({
|
|
21382
22956
|
records,
|
|
21383
22957
|
batchSize: options.batchSize,
|
|
21384
|
-
setupRunId: options.setupRunId
|
|
22958
|
+
setupRunId: options.setupRunId,
|
|
22959
|
+
writeMode: options.writeMode
|
|
21385
22960
|
})
|
|
21386
22961
|
}
|
|
21387
22962
|
);
|
|
@@ -21433,11 +23008,13 @@ var Environment = class {
|
|
|
21433
23008
|
};
|
|
21434
23009
|
var EnvironmentSession = class extends Session {
|
|
21435
23010
|
environment;
|
|
23011
|
+
sessionDataRoutePrefix;
|
|
21436
23012
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
21437
23013
|
graphContainerStatus = null;
|
|
21438
|
-
constructor(client, environment, clientId) {
|
|
23014
|
+
constructor(client, environment, clientId, options = {}) {
|
|
21439
23015
|
super(client, clientId);
|
|
21440
23016
|
this.environment = environment;
|
|
23017
|
+
this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
|
|
21441
23018
|
}
|
|
21442
23019
|
get environmentId() {
|
|
21443
23020
|
return this.environment.environmentId;
|
|
@@ -21482,7 +23059,7 @@ var EnvironmentSession = class extends Session {
|
|
|
21482
23059
|
const doc = this.document;
|
|
21483
23060
|
return normalizeHeapSnapshot(doc?.heap);
|
|
21484
23061
|
}
|
|
21485
|
-
async sessionDataRequest(
|
|
23062
|
+
async sessionDataRequest(path7, query, init2 = {}) {
|
|
21486
23063
|
const searchParams = new URLSearchParams();
|
|
21487
23064
|
for (const [key, value] of Object.entries(query || {})) {
|
|
21488
23065
|
if (value !== null && typeof value !== "undefined" && value !== "") {
|
|
@@ -21490,23 +23067,39 @@ var EnvironmentSession = class extends Session {
|
|
|
21490
23067
|
}
|
|
21491
23068
|
}
|
|
21492
23069
|
const queryString = searchParams.toString();
|
|
21493
|
-
const
|
|
21494
|
-
|
|
21495
|
-
|
|
21496
|
-
|
|
21497
|
-
|
|
21498
|
-
|
|
21499
|
-
|
|
23070
|
+
const url = `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path7}${queryString ? `?${queryString}` : ""}`;
|
|
23071
|
+
const body = typeof init2.body === "undefined" ? void 0 : JSON.stringify(init2.body);
|
|
23072
|
+
for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
|
|
23073
|
+
try {
|
|
23074
|
+
const response = await fetch(url, {
|
|
23075
|
+
method: init2.method || "GET",
|
|
23076
|
+
headers: {
|
|
23077
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
23078
|
+
"Content-Type": "application/json"
|
|
23079
|
+
},
|
|
23080
|
+
...typeof body === "undefined" ? {} : { body }
|
|
23081
|
+
});
|
|
23082
|
+
if (response.ok) {
|
|
23083
|
+
return response.json();
|
|
23084
|
+
}
|
|
23085
|
+
const errorText = await response.text();
|
|
23086
|
+
const error2 = new Error(
|
|
23087
|
+
`Session data API Error (${response.status}): ${errorText}`
|
|
23088
|
+
);
|
|
23089
|
+
if (isLocalControlUrl(url) && isRetryableSessionDataError(error2) && attempt < SESSION_DATA_REQUEST_RETRY_COUNT) {
|
|
23090
|
+
await sleep(SESSION_DATA_REQUEST_RETRY_DELAY_MS * attempt);
|
|
23091
|
+
continue;
|
|
23092
|
+
}
|
|
23093
|
+
throw error2;
|
|
23094
|
+
} catch (error2) {
|
|
23095
|
+
if (isLocalControlUrl(url) && isRetryableSessionDataError(error2) && attempt < SESSION_DATA_REQUEST_RETRY_COUNT) {
|
|
23096
|
+
await sleep(SESSION_DATA_REQUEST_RETRY_DELAY_MS * attempt);
|
|
23097
|
+
continue;
|
|
21500
23098
|
}
|
|
23099
|
+
throw error2;
|
|
21501
23100
|
}
|
|
21502
|
-
);
|
|
21503
|
-
if (!response.ok) {
|
|
21504
|
-
const errorText = await response.text();
|
|
21505
|
-
throw new Error(
|
|
21506
|
-
`Session data API Error (${response.status}): ${errorText}`
|
|
21507
|
-
);
|
|
21508
23101
|
}
|
|
21509
|
-
|
|
23102
|
+
throw new Error(`Session data API Error: exhausted retries for ${url}`);
|
|
21510
23103
|
}
|
|
21511
23104
|
async collectAllSessionItems(listPage) {
|
|
21512
23105
|
const items = [];
|
|
@@ -21552,8 +23145,8 @@ var EnvironmentSession = class extends Session {
|
|
|
21552
23145
|
return {
|
|
21553
23146
|
entries: {
|
|
21554
23147
|
list: (options = {}) => this.sessionDataRequest("/heap/entries", options),
|
|
21555
|
-
get: (
|
|
21556
|
-
`/heap/entries/${encodeURIComponent(
|
|
23148
|
+
get: (path7) => this.sessionDataRequest(
|
|
23149
|
+
`/heap/entries/${encodeURIComponent(path7)}`
|
|
21557
23150
|
)
|
|
21558
23151
|
},
|
|
21559
23152
|
lists: {
|
|
@@ -21564,6 +23157,17 @@ var EnvironmentSession = class extends Session {
|
|
|
21564
23157
|
get: (name) => this.sessionDataRequest(
|
|
21565
23158
|
`/heap/lists/${encodeURIComponent(name)}`
|
|
21566
23159
|
)
|
|
23160
|
+
},
|
|
23161
|
+
variables: {
|
|
23162
|
+
list: (options = {}) => this.sessionDataRequest("/heap/variables", options),
|
|
23163
|
+
get: (name) => this.sessionDataRequest(
|
|
23164
|
+
`/heap/variables/${encodeURIComponent(name)}`
|
|
23165
|
+
),
|
|
23166
|
+
delete: (name) => this.sessionDataRequest(
|
|
23167
|
+
`/heap/variables/${encodeURIComponent(name)}`,
|
|
23168
|
+
void 0,
|
|
23169
|
+
{ method: "DELETE" }
|
|
23170
|
+
)
|
|
21567
23171
|
}
|
|
21568
23172
|
};
|
|
21569
23173
|
}
|
|
@@ -21632,6 +23236,19 @@ var EnvironmentSession = class extends Session {
|
|
|
21632
23236
|
async graphql(query, variables) {
|
|
21633
23237
|
return this.environment.graphql(query, variables);
|
|
21634
23238
|
}
|
|
23239
|
+
async searchRecords(query, options = {}) {
|
|
23240
|
+
return this.environment.searchRecords(query, options);
|
|
23241
|
+
}
|
|
23242
|
+
async mentionRecord(input) {
|
|
23243
|
+
return this.sessionDataRequest(
|
|
23244
|
+
"/records/mention",
|
|
23245
|
+
void 0,
|
|
23246
|
+
{
|
|
23247
|
+
method: "POST",
|
|
23248
|
+
body: input
|
|
23249
|
+
}
|
|
23250
|
+
);
|
|
23251
|
+
}
|
|
21635
23252
|
async defineRelationship(options) {
|
|
21636
23253
|
return this.environment.defineRelationship(options);
|
|
21637
23254
|
}
|
|
@@ -21779,6 +23396,7 @@ var Granular = class _Granular {
|
|
|
21779
23396
|
WebSocketCtor;
|
|
21780
23397
|
onUnexpectedClose;
|
|
21781
23398
|
onReconnectError;
|
|
23399
|
+
effectHostUrl;
|
|
21782
23400
|
debugHttp = process.env.GRANULAR_DEBUG_HTTP === "1";
|
|
21783
23401
|
/** Sandbox-level effect registry: sandboxId → (effectKey@selector → ToolWithHandler) */
|
|
21784
23402
|
sandboxEffects = /* @__PURE__ */ new Map();
|
|
@@ -21807,6 +23425,7 @@ var Granular = class _Granular {
|
|
|
21807
23425
|
this.WebSocketCtor = options.WebSocketCtor;
|
|
21808
23426
|
this.onUnexpectedClose = options.onUnexpectedClose;
|
|
21809
23427
|
this.onReconnectError = options.onReconnectError;
|
|
23428
|
+
this.effectHostUrl = options.effectHostUrl;
|
|
21810
23429
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
21811
23430
|
}
|
|
21812
23431
|
/**
|
|
@@ -21977,6 +23596,30 @@ var Granular = class _Granular {
|
|
|
21977
23596
|
permissions: options.permissions || options.user?.permissions || []
|
|
21978
23597
|
});
|
|
21979
23598
|
}
|
|
23599
|
+
/**
|
|
23600
|
+
* Run a registered environment importer against an environment that was
|
|
23601
|
+
* opened outside this SDK instance, for example by a delegated browser flow.
|
|
23602
|
+
*
|
|
23603
|
+
* This uses the same setup-run and queued record-import plumbing as
|
|
23604
|
+
* `openEnvironment()`: importer stages, expected object counts, and queued
|
|
23605
|
+
* import counters remain visible through `environment.setup` and
|
|
23606
|
+
* `getRecordImportSummary()`.
|
|
23607
|
+
*/
|
|
23608
|
+
async runEnvironmentImporterForEnvironment(environmentId, options = {}) {
|
|
23609
|
+
const environmentData = await this.environments.get(environmentId);
|
|
23610
|
+
const environment = this.bindEnvironmentHandle(environmentData);
|
|
23611
|
+
const requestedOntology = options.ontology || environmentData.ontologyId || environmentData.sandboxId;
|
|
23612
|
+
return this.runEnvironmentImporter(
|
|
23613
|
+
{
|
|
23614
|
+
environment: environmentData,
|
|
23615
|
+
requestedOntology,
|
|
23616
|
+
sandboxId: environmentData.sandboxId,
|
|
23617
|
+
subjectId: environmentData.subjectId,
|
|
23618
|
+
setupTriggerReason: options.reason || "new_environment"
|
|
23619
|
+
},
|
|
23620
|
+
environment
|
|
23621
|
+
);
|
|
23622
|
+
}
|
|
21980
23623
|
resolveRequestedTag(options, methodName) {
|
|
21981
23624
|
const tag2 = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
21982
23625
|
if (!tag2) {
|
|
@@ -22220,15 +23863,25 @@ var Granular = class _Granular {
|
|
|
22220
23863
|
return ontologyImporter;
|
|
22221
23864
|
}
|
|
22222
23865
|
async maybeRunEnvironmentImporter(resolved, environment) {
|
|
22223
|
-
|
|
22224
|
-
|
|
23866
|
+
const setupTriggerReason = resolved.setupTriggerReason;
|
|
23867
|
+
if (!setupTriggerReason) {
|
|
23868
|
+
return null;
|
|
22225
23869
|
}
|
|
23870
|
+
return this.runEnvironmentImporter(
|
|
23871
|
+
{
|
|
23872
|
+
...resolved,
|
|
23873
|
+
setupTriggerReason
|
|
23874
|
+
},
|
|
23875
|
+
environment
|
|
23876
|
+
);
|
|
23877
|
+
}
|
|
23878
|
+
async runEnvironmentImporter(resolved, environment) {
|
|
22226
23879
|
const importer = this.resolveEnvironmentImporter(
|
|
22227
23880
|
resolved.requestedOntology,
|
|
22228
23881
|
resolved.sandboxId
|
|
22229
23882
|
);
|
|
22230
23883
|
if (!importer) {
|
|
22231
|
-
return;
|
|
23884
|
+
return null;
|
|
22232
23885
|
}
|
|
22233
23886
|
const setupRun = await this.request(
|
|
22234
23887
|
`/control/environments/${environment.environmentId}/setup-runs`,
|
|
@@ -22268,16 +23921,24 @@ var Granular = class _Granular {
|
|
|
22268
23921
|
},
|
|
22269
23922
|
importRecords: async (records, options) => environment.enqueueRecordImport(records, {
|
|
22270
23923
|
batchSize: options?.batchSize,
|
|
23924
|
+
writeMode: options?.writeMode,
|
|
22271
23925
|
setupRunId
|
|
22272
23926
|
})
|
|
22273
23927
|
};
|
|
22274
23928
|
try {
|
|
22275
23929
|
await importer(importerContext);
|
|
22276
|
-
|
|
23930
|
+
const completedSetupRun = await this.request(
|
|
23931
|
+
`/control/environment-setup-runs/${setupRunId}`,
|
|
23932
|
+
{
|
|
23933
|
+
method: "PATCH",
|
|
23934
|
+
body: JSON.stringify({ markHookCompleted: true })
|
|
23935
|
+
}
|
|
23936
|
+
);
|
|
22277
23937
|
const refreshedEnvironment = await this.environments.get(
|
|
22278
23938
|
environment.environmentId
|
|
22279
23939
|
);
|
|
22280
23940
|
environment.syncEnvironmentData(refreshedEnvironment);
|
|
23941
|
+
return completedSetupRun;
|
|
22281
23942
|
} catch (error2) {
|
|
22282
23943
|
await updateSetupRun({
|
|
22283
23944
|
status: "failed",
|
|
@@ -22325,27 +23986,45 @@ var Granular = class _Granular {
|
|
|
22325
23986
|
return effects2;
|
|
22326
23987
|
}
|
|
22327
23988
|
serializeEffect(effect) {
|
|
22328
|
-
|
|
23989
|
+
const serialized = {
|
|
22329
23990
|
effectKey: computeEffectKey2(effect),
|
|
22330
23991
|
name: effect.name,
|
|
22331
23992
|
description: effect.description,
|
|
22332
23993
|
inputSchema: effect.inputSchema,
|
|
22333
|
-
outputSchema: effect.outputSchema,
|
|
22334
23994
|
stability: effect.stability || "stable",
|
|
22335
|
-
provenance: effect.provenance || { source: "custom" }
|
|
22336
|
-
tags: effect.tags,
|
|
22337
|
-
className: effect.className,
|
|
22338
|
-
static: effect.static,
|
|
22339
|
-
versionSelector: effect.versionSelector
|
|
23995
|
+
provenance: effect.provenance || { source: "custom" }
|
|
22340
23996
|
};
|
|
23997
|
+
if (effect.outputSchema !== void 0) {
|
|
23998
|
+
serialized.outputSchema = effect.outputSchema;
|
|
23999
|
+
}
|
|
24000
|
+
if (effect.tags !== void 0) {
|
|
24001
|
+
serialized.tags = effect.tags;
|
|
24002
|
+
}
|
|
24003
|
+
if (effect.className !== void 0) {
|
|
24004
|
+
serialized.className = effect.className;
|
|
24005
|
+
}
|
|
24006
|
+
if (effect.static !== void 0) {
|
|
24007
|
+
serialized.static = effect.static;
|
|
24008
|
+
}
|
|
24009
|
+
if (effect.versionSelector !== void 0) {
|
|
24010
|
+
serialized.versionSelector = effect.versionSelector;
|
|
24011
|
+
}
|
|
24012
|
+
if (effect.metamodels !== void 0) {
|
|
24013
|
+
serialized.metamodels = effect.metamodels;
|
|
24014
|
+
}
|
|
24015
|
+
return serialized;
|
|
22341
24016
|
}
|
|
22342
24017
|
async publishSandboxEffectCatalog(host) {
|
|
22343
24018
|
const effects2 = Array.from(
|
|
22344
24019
|
this.getSandboxEffectMap(host.sandboxId).values()
|
|
22345
24020
|
).map((effect) => this.serializeEffect(effect));
|
|
22346
|
-
const result = await
|
|
22347
|
-
effects
|
|
22348
|
-
|
|
24021
|
+
const result = await withTimeout(
|
|
24022
|
+
host.wsClient.call("effects.publishCatalog", {
|
|
24023
|
+
effects: effects2
|
|
24024
|
+
}),
|
|
24025
|
+
EFFECT_CATALOG_SYNC_TIMEOUT_MS,
|
|
24026
|
+
`effects.publishCatalog for sandbox ${host.sandboxId}`
|
|
24027
|
+
);
|
|
22349
24028
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
22350
24029
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
22351
24030
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
@@ -22364,8 +24043,26 @@ var Granular = class _Granular {
|
|
|
22364
24043
|
}
|
|
22365
24044
|
}
|
|
22366
24045
|
async syncSandboxEffectCatalog(sandboxId) {
|
|
22367
|
-
|
|
22368
|
-
|
|
24046
|
+
let lastError;
|
|
24047
|
+
for (let attempt = 1; attempt <= EFFECT_CATALOG_SYNC_RETRY_COUNT; attempt += 1) {
|
|
24048
|
+
try {
|
|
24049
|
+
const host = await this.ensureSandboxEffectHost(sandboxId);
|
|
24050
|
+
await this.publishSandboxEffectCatalog(host);
|
|
24051
|
+
return;
|
|
24052
|
+
} catch (error2) {
|
|
24053
|
+
lastError = error2;
|
|
24054
|
+
this.disconnectSandboxEffectHost(sandboxId);
|
|
24055
|
+
if (attempt === EFFECT_CATALOG_SYNC_RETRY_COUNT || !isRetryableEffectRegistrationError(error2)) {
|
|
24056
|
+
throw error2;
|
|
24057
|
+
}
|
|
24058
|
+
console.warn(
|
|
24059
|
+
`[Granular] Retrying effect registration for sandbox ${sandboxId} after transient failure (${attempt}/${EFFECT_CATALOG_SYNC_RETRY_COUNT - 1} retries used):`,
|
|
24060
|
+
error2
|
|
24061
|
+
);
|
|
24062
|
+
await sleep(EFFECT_CATALOG_SYNC_RETRY_DELAY_MS * attempt);
|
|
24063
|
+
}
|
|
24064
|
+
}
|
|
24065
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
22369
24066
|
}
|
|
22370
24067
|
recoverEffectHost(host, error2) {
|
|
22371
24068
|
if (host.recovering) {
|
|
@@ -22458,7 +24155,8 @@ var Granular = class _Granular {
|
|
|
22458
24155
|
this.apiUrl,
|
|
22459
24156
|
sandboxId,
|
|
22460
24157
|
effectClientId,
|
|
22461
|
-
clientId
|
|
24158
|
+
clientId,
|
|
24159
|
+
this.effectHostUrl
|
|
22462
24160
|
),
|
|
22463
24161
|
sessionId: `effect-host:${effectClientId}`,
|
|
22464
24162
|
token: this.apiKey,
|
|
@@ -22494,7 +24192,11 @@ var Granular = class _Granular {
|
|
|
22494
24192
|
wsClient.on("disconnect", () => {
|
|
22495
24193
|
this.stopEffectHostHeartbeat(host);
|
|
22496
24194
|
});
|
|
22497
|
-
await
|
|
24195
|
+
await withTimeout(
|
|
24196
|
+
wsClient.connect(),
|
|
24197
|
+
EFFECT_HOST_CONNECT_TIMEOUT_MS,
|
|
24198
|
+
`effect host WebSocket connect for sandbox ${sandboxId}`
|
|
24199
|
+
);
|
|
22498
24200
|
await this.synchronizeEffectHost(host);
|
|
22499
24201
|
this.sandboxEffectHosts.set(sandboxId, host);
|
|
22500
24202
|
return host;
|
|
@@ -22617,7 +24319,7 @@ var Granular = class _Granular {
|
|
|
22617
24319
|
/**
|
|
22618
24320
|
* Ensure a permission profile exists for a sandbox, creating it if needed.
|
|
22619
24321
|
* If profileName matches an existing profile name, returns its ID.
|
|
22620
|
-
* Otherwise, creates a
|
|
24322
|
+
* Otherwise, creates a v1 source-profile file shape with an allow default.
|
|
22621
24323
|
*/
|
|
22622
24324
|
async ensurePermissionProfile(sandboxId, profileName) {
|
|
22623
24325
|
try {
|
|
@@ -22631,8 +24333,11 @@ var Granular = class _Granular {
|
|
|
22631
24333
|
const created = await this.permissionProfiles.create(sandboxId, {
|
|
22632
24334
|
name: profileName,
|
|
22633
24335
|
rules: {
|
|
22634
|
-
|
|
22635
|
-
|
|
24336
|
+
schemaVersion: 1,
|
|
24337
|
+
name: profileName,
|
|
24338
|
+
description: profileName === "allow-all" ? "Every declared action is visible unless a manifest policy denies it." : `Generated permission profile ${profileName}`,
|
|
24339
|
+
defaults: { actionPolicy: "allow" },
|
|
24340
|
+
actions: []
|
|
22636
24341
|
}
|
|
22637
24342
|
});
|
|
22638
24343
|
return created.permissionProfileId;
|
|
@@ -22705,33 +24410,63 @@ var Granular = class _Granular {
|
|
|
22705
24410
|
* Permission Profile management for sandboxes
|
|
22706
24411
|
*/
|
|
22707
24412
|
get permissionProfiles() {
|
|
24413
|
+
const profileSourceFromRecord = (record) => {
|
|
24414
|
+
const profile = record.profile || record.rules || {};
|
|
24415
|
+
return {
|
|
24416
|
+
...profile,
|
|
24417
|
+
schemaVersion: profile.schemaVersion || 1,
|
|
24418
|
+
name: profile.name || record.name,
|
|
24419
|
+
description: profile.description || record.description
|
|
24420
|
+
};
|
|
24421
|
+
};
|
|
22708
24422
|
return {
|
|
22709
24423
|
list: async (sandboxId) => {
|
|
22710
24424
|
const result = await this.request(
|
|
22711
|
-
`/control/sandboxes/${sandboxId}/permission-
|
|
24425
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`
|
|
22712
24426
|
);
|
|
22713
24427
|
return result.items;
|
|
22714
24428
|
},
|
|
22715
24429
|
get: async (sandboxId, profileId) => {
|
|
22716
|
-
|
|
22717
|
-
`/control/sandboxes/${sandboxId}/permission-
|
|
24430
|
+
const result = await this.request(
|
|
24431
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`
|
|
24432
|
+
);
|
|
24433
|
+
const profile = result.items.find(
|
|
24434
|
+
(item) => item.permissionProfileId === profileId || item.name === profileId
|
|
22718
24435
|
);
|
|
24436
|
+
if (!profile) {
|
|
24437
|
+
throw new Error(`Permission profile source not found: ${profileId}`);
|
|
24438
|
+
}
|
|
24439
|
+
return profile;
|
|
22719
24440
|
},
|
|
22720
24441
|
create: async (sandboxId, data) => {
|
|
22721
|
-
|
|
22722
|
-
|
|
24442
|
+
const profile = {
|
|
24443
|
+
...data.rules,
|
|
24444
|
+
schemaVersion: 1,
|
|
24445
|
+
name: data.name
|
|
24446
|
+
};
|
|
24447
|
+
const existingProfiles = await this.permissionProfiles.list(sandboxId);
|
|
24448
|
+
const profiles = [
|
|
24449
|
+
...existingProfiles.filter((existing) => existing.name !== data.name).map((existing) => profileSourceFromRecord(existing)),
|
|
24450
|
+
profile
|
|
24451
|
+
];
|
|
24452
|
+
const result = await this.request(
|
|
24453
|
+
`/control/sandboxes/${sandboxId}/permission-profile-sources`,
|
|
22723
24454
|
{
|
|
22724
|
-
method: "
|
|
22725
|
-
body: JSON.stringify(
|
|
24455
|
+
method: "PUT",
|
|
24456
|
+
body: JSON.stringify({ profiles })
|
|
22726
24457
|
}
|
|
22727
24458
|
);
|
|
24459
|
+
const synced = result.items.find((item) => item.name === data.name) || result.items[0];
|
|
24460
|
+
if (!synced) {
|
|
24461
|
+
throw new Error(
|
|
24462
|
+
`Permission profile source sync did not return ${data.name}`
|
|
24463
|
+
);
|
|
24464
|
+
}
|
|
24465
|
+
return synced;
|
|
22728
24466
|
},
|
|
22729
|
-
delete: async (
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
{
|
|
22733
|
-
method: "DELETE"
|
|
22734
|
-
}
|
|
24467
|
+
delete: async (_sandboxId, _profileId) => {
|
|
24468
|
+
throw new Error(
|
|
24469
|
+
"Permission profile sources are updated by syncing the desired source set."
|
|
22735
24470
|
);
|
|
22736
24471
|
}
|
|
22737
24472
|
};
|
|
@@ -22944,8 +24679,8 @@ var Granular = class _Granular {
|
|
|
22944
24679
|
/**
|
|
22945
24680
|
* Make an authenticated API request
|
|
22946
24681
|
*/
|
|
22947
|
-
async request(
|
|
22948
|
-
const url = `${this.httpUrl}${
|
|
24682
|
+
async request(path7, options = {}) {
|
|
24683
|
+
const url = `${this.httpUrl}${path7}`;
|
|
22949
24684
|
if (this.debugHttp) {
|
|
22950
24685
|
console.log(`[SDK] Requesting: ${url}`);
|
|
22951
24686
|
}
|
|
@@ -22989,7 +24724,7 @@ var Granular = class _Granular {
|
|
|
22989
24724
|
// src/cli/commands/runtime-shared.ts
|
|
22990
24725
|
function normalizePermissions(raw) {
|
|
22991
24726
|
const values = raw?.split(",").map((value) => value.trim()).filter(Boolean);
|
|
22992
|
-
return values && values.length > 0 ? values : ["
|
|
24727
|
+
return values && values.length > 0 ? values : ["allow-all"];
|
|
22993
24728
|
}
|
|
22994
24729
|
function createGranularClient() {
|
|
22995
24730
|
const config = resolveConfig({ requireApiKey: true });
|
|
@@ -23049,7 +24784,7 @@ async function resolveEnvironmentData(granular, options) {
|
|
|
23049
24784
|
ontology: ontologyId,
|
|
23050
24785
|
environment: environmentName,
|
|
23051
24786
|
userId: options.userId ?? "granular-cli",
|
|
23052
|
-
permissions: options.permissions ?? ["
|
|
24787
|
+
permissions: options.permissions ?? ["allow-all"]
|
|
23053
24788
|
});
|
|
23054
24789
|
return await granular.environments.get(connection.environmentId);
|
|
23055
24790
|
}
|
|
@@ -23089,7 +24824,7 @@ async function connectRuntime(options) {
|
|
|
23089
24824
|
ontology: ontologyId,
|
|
23090
24825
|
environment: options.environment ?? "dev",
|
|
23091
24826
|
userId: options.userId ?? "granular-cli",
|
|
23092
|
-
permissions: options.permissions ?? ["
|
|
24827
|
+
permissions: options.permissions ?? ["allow-all"]
|
|
23093
24828
|
});
|
|
23094
24829
|
const environment = await environmentHandle.createSession();
|
|
23095
24830
|
return {
|
|
@@ -23250,13 +24985,13 @@ async function sessionCreateCommand(options) {
|
|
|
23250
24985
|
printHeader();
|
|
23251
24986
|
}
|
|
23252
24987
|
const { granular } = createGranularClient();
|
|
23253
|
-
const
|
|
24988
|
+
const permissions2 = normalizePermissions(options.permissions);
|
|
23254
24989
|
const envData = await resolveEnvironmentData(granular, {
|
|
23255
24990
|
ontology: options.ontology,
|
|
23256
24991
|
environment: requestedEnvironment,
|
|
23257
24992
|
environmentId: options.environmentId,
|
|
23258
24993
|
userId: options.user,
|
|
23259
|
-
permissions,
|
|
24994
|
+
permissions: permissions2,
|
|
23260
24995
|
createIfMissing: true
|
|
23261
24996
|
});
|
|
23262
24997
|
const environment = await granular.createSession({
|
|
@@ -23581,6 +25316,101 @@ async function jobRunCommand(options) {
|
|
|
23581
25316
|
);
|
|
23582
25317
|
}
|
|
23583
25318
|
|
|
25319
|
+
// src/cli/commands/permissions.ts
|
|
25320
|
+
function parseJsonObject(value) {
|
|
25321
|
+
if (!value) return {};
|
|
25322
|
+
const parsed = JSON.parse(value);
|
|
25323
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
25324
|
+
throw new Error("Expected a JSON object");
|
|
25325
|
+
}
|
|
25326
|
+
return parsed;
|
|
25327
|
+
}
|
|
25328
|
+
function resolveEffectKey(manifest, action, on) {
|
|
25329
|
+
const effects2 = extractManifestEffects(manifest);
|
|
25330
|
+
const matches = effects2.filter((effect) => {
|
|
25331
|
+
if (effect.name !== action) return false;
|
|
25332
|
+
return on ? effect.attachedClass === on : !effect.attachedClass;
|
|
25333
|
+
});
|
|
25334
|
+
if (matches.length === 1) return matches[0].effectKey;
|
|
25335
|
+
if (matches.length === 0) {
|
|
25336
|
+
throw new Error(
|
|
25337
|
+
on ? `Action "${action}" on "${on}" was not found.` : `Global action "${action}" was not found.`
|
|
25338
|
+
);
|
|
25339
|
+
}
|
|
25340
|
+
throw new Error(`Action "${action}" is ambiguous. Add --on.`);
|
|
25341
|
+
}
|
|
25342
|
+
async function permissionsValidateCommand() {
|
|
25343
|
+
const config = resolveConfig({ requireProject: true });
|
|
25344
|
+
const manifest = config.project.manifest;
|
|
25345
|
+
const profiles = readPermissionProfileFiles();
|
|
25346
|
+
const result = await compilePolicyBundle({
|
|
25347
|
+
sandboxId: config.sandboxId,
|
|
25348
|
+
effects: extractManifestEffects(manifest),
|
|
25349
|
+
profileSources: profiles
|
|
25350
|
+
});
|
|
25351
|
+
if (result.errors.length > 0) {
|
|
25352
|
+
error("Permission policy validation failed:");
|
|
25353
|
+
for (const error2 of result.errors) error(` ${error2}`);
|
|
25354
|
+
process.exit(1);
|
|
25355
|
+
}
|
|
25356
|
+
success(
|
|
25357
|
+
`Validated ${Object.keys(result.bundle.profiles).length} permission profile(s).`
|
|
25358
|
+
);
|
|
25359
|
+
if (result.warnings.length > 0) {
|
|
25360
|
+
for (const warning of result.warnings) warn(warning);
|
|
25361
|
+
}
|
|
25362
|
+
}
|
|
25363
|
+
async function permissionsPreviewCommand(options) {
|
|
25364
|
+
const config = resolveConfig({
|
|
25365
|
+
requireApiKey: Boolean(options.versionId),
|
|
25366
|
+
requireProject: true
|
|
25367
|
+
});
|
|
25368
|
+
if (!options.profile || !options.action) {
|
|
25369
|
+
throw new Error("permissions preview requires --profile and --action");
|
|
25370
|
+
}
|
|
25371
|
+
const requestInput = parseJsonObject(options.input);
|
|
25372
|
+
const objectContext = parseJsonObject(options.object);
|
|
25373
|
+
const stateMachines = parseJsonObject(options.stateMachines);
|
|
25374
|
+
const effectKey = resolveEffectKey(
|
|
25375
|
+
config.project.manifest,
|
|
25376
|
+
options.action,
|
|
25377
|
+
options.on
|
|
25378
|
+
);
|
|
25379
|
+
if (options.versionId) {
|
|
25380
|
+
const api = new ApiClient(config.apiKey, config.apiUrl);
|
|
25381
|
+
const decision2 = await api.evaluateVersionPolicy(options.versionId, {
|
|
25382
|
+
permissionProfileName: options.profile,
|
|
25383
|
+
effectKey,
|
|
25384
|
+
input: requestInput,
|
|
25385
|
+
object: objectContext,
|
|
25386
|
+
stateMachines
|
|
25387
|
+
});
|
|
25388
|
+
keyValue({
|
|
25389
|
+
Decision: decision2.outcome,
|
|
25390
|
+
Matched: (decision2.matches || []).map((match) => match.reason || match.summary || match.id).join("; ") || "none"
|
|
25391
|
+
});
|
|
25392
|
+
return;
|
|
25393
|
+
}
|
|
25394
|
+
const result = await compilePolicyBundle({
|
|
25395
|
+
sandboxId: config.sandboxId,
|
|
25396
|
+
effects: extractManifestEffects(config.project.manifest),
|
|
25397
|
+
profileSources: readPermissionProfileFiles()
|
|
25398
|
+
});
|
|
25399
|
+
if (result.errors.length > 0) {
|
|
25400
|
+
throw new Error(result.errors.join("; "));
|
|
25401
|
+
}
|
|
25402
|
+
const decision = evaluateCompiledPolicyBundle({
|
|
25403
|
+
bundle: result.bundle,
|
|
25404
|
+
profileName: options.profile,
|
|
25405
|
+
effectKey,
|
|
25406
|
+
context: { input: requestInput, object: objectContext, stateMachines }
|
|
25407
|
+
});
|
|
25408
|
+
keyValue({
|
|
25409
|
+
Decision: decision.outcome,
|
|
25410
|
+
Matched: decision.matches.map((match) => match.reason || match.summary || match.id).join("; ") || "none"
|
|
25411
|
+
});
|
|
25412
|
+
}
|
|
25413
|
+
|
|
23584
25414
|
// src/cli/commands/version.ts
|
|
23585
25415
|
function formatVersionLabel(versionId, versionNumber) {
|
|
23586
25416
|
return versionNumber != null ? `v${versionNumber}` : versionId;
|
|
@@ -23822,67 +25652,130 @@ program2.hook("preAction", () => {
|
|
|
23822
25652
|
process.env.GRANULAR_ENDPOINT_MODE = mode;
|
|
23823
25653
|
}
|
|
23824
25654
|
});
|
|
23825
|
-
program2.command("init [project-name]").description("Initialize a new Granular project").option("--skip-build", "Skip the initial build step").option(
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
|
|
23831
|
-
|
|
23832
|
-
|
|
23833
|
-
|
|
23834
|
-
|
|
23835
|
-
|
|
23836
|
-
|
|
23837
|
-
|
|
23838
|
-
|
|
23839
|
-
|
|
23840
|
-
|
|
23841
|
-
|
|
23842
|
-
|
|
23843
|
-
|
|
23844
|
-
|
|
25655
|
+
program2.command("init [project-name]").description("Initialize a new Granular project").option("--skip-build", "Skip the initial build step").option(
|
|
25656
|
+
"--template <template>",
|
|
25657
|
+
"Starter ontology template: library|support|delivery"
|
|
25658
|
+
).option(
|
|
25659
|
+
"--agent-docs",
|
|
25660
|
+
"Add AGENTS.md and docs/granular-manifest.md (for AI coding agents)"
|
|
25661
|
+
).option("--no-agent-docs", "Skip agent documentation files").option("--cursor-hooks", "Emit optional Cursor hook files under .cursor/").option(
|
|
25662
|
+
"--cursor-subagents",
|
|
25663
|
+
"Emit optional Cursor subagent files under .cursor/"
|
|
25664
|
+
).option("--cursor-all", "Emit both Cursor hooks and Cursor subagents").option(
|
|
25665
|
+
"--control-plane-session <sessionId>",
|
|
25666
|
+
"Attach the init flow to a control-plane onboarding session"
|
|
25667
|
+
).option("--project-mode <mode>", "Onboarding context: new|existing").option(
|
|
25668
|
+
"--auth-strategy <strategy>",
|
|
25669
|
+
"Onboarding context: just-me|granular-auth|existing-auth"
|
|
25670
|
+
).option("--ide <ide>", "Onboarding context: cursor|codex|claude-code").option("--data-strategy <strategy>", "Onboarding context: demo|real").option(
|
|
25671
|
+
"--product-summary <text>",
|
|
25672
|
+
"Onboarding context: product brief from the control plane"
|
|
25673
|
+
).option(
|
|
25674
|
+
"--roles-summary <text>",
|
|
25675
|
+
"Onboarding context: roles and permission notes from the control plane"
|
|
25676
|
+
).action(
|
|
25677
|
+
async (projectName, opts) => {
|
|
25678
|
+
try {
|
|
25679
|
+
await initCommand(projectName, {
|
|
25680
|
+
skipBuild: opts.skipBuild,
|
|
25681
|
+
template: opts.template,
|
|
25682
|
+
agentDocs: opts.agentDocs,
|
|
25683
|
+
noAgentDocs: opts.noAgentDocs,
|
|
25684
|
+
cursorHooks: opts.cursorHooks || opts.cursorAll,
|
|
25685
|
+
cursorSubagents: opts.cursorSubagents || opts.cursorAll,
|
|
25686
|
+
controlPlaneSession: opts.controlPlaneSession,
|
|
25687
|
+
projectMode: opts.projectMode,
|
|
25688
|
+
authStrategy: opts.authStrategy,
|
|
25689
|
+
ide: opts.ide,
|
|
25690
|
+
dataStrategy: opts.dataStrategy,
|
|
25691
|
+
productSummary: opts.productSummary,
|
|
25692
|
+
rolesSummary: opts.rolesSummary
|
|
25693
|
+
});
|
|
25694
|
+
} catch (err) {
|
|
25695
|
+
error(err.message);
|
|
25696
|
+
process.exit(1);
|
|
25697
|
+
}
|
|
23845
25698
|
}
|
|
23846
|
-
|
|
23847
|
-
program2.command("login").description("Authenticate with Granular (browser by default)").option("--manual", "Prompt for API key instead of browser login").option("--api-key <key>", "Use a provided API key directly").option(
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
25699
|
+
);
|
|
25700
|
+
program2.command("login").description("Authenticate with Granular (browser by default)").option("--manual", "Prompt for API key instead of browser login").option("--api-key <key>", "Use a provided API key directly").option(
|
|
25701
|
+
"--timeout <seconds>",
|
|
25702
|
+
"Browser auth timeout in seconds",
|
|
25703
|
+
(value) => {
|
|
25704
|
+
const n = Number.parseInt(value, 10);
|
|
25705
|
+
return Number.isFinite(n) ? n : 180;
|
|
25706
|
+
}
|
|
25707
|
+
).action(
|
|
25708
|
+
async (opts) => {
|
|
25709
|
+
try {
|
|
25710
|
+
await loginCommand({
|
|
25711
|
+
manual: opts.manual,
|
|
25712
|
+
apiKey: opts.apiKey,
|
|
25713
|
+
timeout: opts.timeout
|
|
25714
|
+
});
|
|
25715
|
+
} catch (err) {
|
|
25716
|
+
error(err.message);
|
|
25717
|
+
process.exit(1);
|
|
25718
|
+
}
|
|
25719
|
+
}
|
|
25720
|
+
);
|
|
25721
|
+
program2.command("pull <version-id>").description("Pull the manifest for a specific ontology version").action(async (versionId) => {
|
|
23851
25722
|
try {
|
|
23852
|
-
await
|
|
23853
|
-
manual: opts.manual,
|
|
23854
|
-
apiKey: opts.apiKey,
|
|
23855
|
-
timeout: opts.timeout
|
|
23856
|
-
});
|
|
25723
|
+
await pullCommand(versionId);
|
|
23857
25724
|
} catch (err) {
|
|
23858
25725
|
error(err.message);
|
|
23859
25726
|
process.exit(1);
|
|
23860
25727
|
}
|
|
23861
25728
|
});
|
|
23862
|
-
program2.command("
|
|
25729
|
+
program2.command("build").description(
|
|
25730
|
+
"Create or reuse the ontology version for your current manifest, then run its build"
|
|
25731
|
+
).action(async () => {
|
|
23863
25732
|
try {
|
|
23864
|
-
await
|
|
25733
|
+
await buildCommand();
|
|
23865
25734
|
} catch (err) {
|
|
23866
25735
|
error(err.message);
|
|
23867
25736
|
process.exit(1);
|
|
23868
25737
|
}
|
|
23869
25738
|
});
|
|
23870
|
-
program2.command("
|
|
25739
|
+
program2.command("deploy").description(
|
|
25740
|
+
"Push the current revision to dev; use --prod to move prod there too"
|
|
25741
|
+
).option("--prod", "Also point prod to the current version").action(async (opts) => {
|
|
23871
25742
|
try {
|
|
23872
|
-
await
|
|
25743
|
+
await deployCommand({ prod: opts.prod });
|
|
23873
25744
|
} catch (err) {
|
|
23874
25745
|
error(err.message);
|
|
23875
25746
|
process.exit(1);
|
|
23876
25747
|
}
|
|
23877
25748
|
});
|
|
23878
|
-
program2.command("
|
|
25749
|
+
var permissions = program2.command("permissions").description("Validate and preview permission profile files");
|
|
25750
|
+
permissions.command("validate").description("Validate permissions/*.json against granular.json").action(async () => {
|
|
23879
25751
|
try {
|
|
23880
|
-
await
|
|
25752
|
+
await permissionsValidateCommand();
|
|
23881
25753
|
} catch (err) {
|
|
23882
25754
|
error(err.message);
|
|
23883
25755
|
process.exit(1);
|
|
23884
25756
|
}
|
|
23885
25757
|
});
|
|
25758
|
+
permissions.command("preview").description("Preview a policy decision for a profile and action").requiredOption("--profile <profile>", "Permission profile name").requiredOption("--action <action>", "Action name").option("--on <className>", "Class name for instance/static actions").option("--input <json>", "Action input JSON object", "{}").option(
|
|
25759
|
+
"--object <json>",
|
|
25760
|
+
"Target object fields JSON object for object-based conditions",
|
|
25761
|
+
"{}"
|
|
25762
|
+
).option(
|
|
25763
|
+
"--state-machines <json>",
|
|
25764
|
+
'Current state-machine states JSON object, e.g. {"lifecycle":"active"}',
|
|
25765
|
+
"{}"
|
|
25766
|
+
).option(
|
|
25767
|
+
"--version-id <versionId>",
|
|
25768
|
+
"Evaluate against a compiled ontology version"
|
|
25769
|
+
).action(
|
|
25770
|
+
async (opts) => {
|
|
25771
|
+
try {
|
|
25772
|
+
await permissionsPreviewCommand(opts);
|
|
25773
|
+
} catch (err) {
|
|
25774
|
+
error(err.message);
|
|
25775
|
+
process.exit(1);
|
|
25776
|
+
}
|
|
25777
|
+
}
|
|
25778
|
+
);
|
|
23886
25779
|
program2.command("status").description("Show ontology versions, dev/prod, and manifest status").option("--verbose", "Include endpoint/config details helpful for debugging").option("--json", "Print machine-readable JSON").action(async (options) => {
|
|
23887
25780
|
try {
|
|
23888
25781
|
await statusCommand(options);
|
|
@@ -23900,14 +25793,16 @@ version.command("current").description("Show the latest/current ontology version
|
|
|
23900
25793
|
process.exit(1);
|
|
23901
25794
|
}
|
|
23902
25795
|
});
|
|
23903
|
-
version.command("diff <version-id> [against-version-id]").description("Show the semantic diff between one version and another").option("--json", "Print machine-readable JSON").action(
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
25796
|
+
version.command("diff <version-id> [against-version-id]").description("Show the semantic diff between one version and another").option("--json", "Print machine-readable JSON").action(
|
|
25797
|
+
async (versionId, againstVersionId, options) => {
|
|
25798
|
+
try {
|
|
25799
|
+
await versionDiffCommand(versionId, againstVersionId, options);
|
|
25800
|
+
} catch (err) {
|
|
25801
|
+
error(err.message);
|
|
25802
|
+
process.exit(1);
|
|
25803
|
+
}
|
|
23909
25804
|
}
|
|
23910
|
-
|
|
25805
|
+
);
|
|
23911
25806
|
var tag = program2.command("tag").description("Inspect and move release tags such as dev and prod");
|
|
23912
25807
|
tag.command("list").description("List tags for the current ontology").action(async () => {
|
|
23913
25808
|
try {
|
|
@@ -23966,7 +25861,9 @@ program2.command("dev").description("Start development mode (watch + auto-rebuil
|
|
|
23966
25861
|
process.exit(1);
|
|
23967
25862
|
}
|
|
23968
25863
|
});
|
|
23969
|
-
program2.command("document").description(
|
|
25864
|
+
program2.command("document").description(
|
|
25865
|
+
"Generate GRANULAR_SANDBOX.md (agent reference) from granular.json"
|
|
25866
|
+
).action(async () => {
|
|
23970
25867
|
try {
|
|
23971
25868
|
await documentCommand();
|
|
23972
25869
|
} catch (err) {
|
|
@@ -23974,83 +25871,232 @@ program2.command("document").description("Generate GRANULAR_SANDBOX.md (agent re
|
|
|
23974
25871
|
process.exit(1);
|
|
23975
25872
|
}
|
|
23976
25873
|
});
|
|
23977
|
-
program2.command("simulate [ontology-id]").description(
|
|
23978
|
-
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
25874
|
+
program2.command("simulate [ontology-id]").description(
|
|
25875
|
+
"Open the Granular simulator in the browser for the current (or given) ontology"
|
|
25876
|
+
).option(
|
|
25877
|
+
"--subject-id <subjectId>",
|
|
25878
|
+
"Open the simulator preloaded for a specific subject"
|
|
25879
|
+
).option(
|
|
25880
|
+
"--environment <environment>",
|
|
25881
|
+
"Add an environment query parameter to the simulator URL"
|
|
25882
|
+
).option(
|
|
25883
|
+
"--session <sessionId>",
|
|
25884
|
+
"Add a session query parameter to the simulator URL"
|
|
25885
|
+
).option("--user <userId>", "Add a user query parameter to the simulator URL").option("--print-url", "Print the simulator URL without opening a browser").action(
|
|
25886
|
+
async (sandboxId, options) => {
|
|
25887
|
+
try {
|
|
25888
|
+
await simulateCommand(sandboxId, options);
|
|
25889
|
+
} catch (err) {
|
|
25890
|
+
error(err.message);
|
|
25891
|
+
process.exit(1);
|
|
25892
|
+
}
|
|
23983
25893
|
}
|
|
23984
|
-
|
|
25894
|
+
);
|
|
23985
25895
|
var connect = program2.command("connect").description("Run connectivity checks against a live Granular ontology");
|
|
23986
|
-
connect.command("test").description("Create a real session and verify connect/disconnect works").option(
|
|
23987
|
-
|
|
23988
|
-
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
25896
|
+
connect.command("test").description("Create a real session and verify connect/disconnect works").option(
|
|
25897
|
+
"--ontology <ontologyId>",
|
|
25898
|
+
"Override the ontology id from .granularrc"
|
|
25899
|
+
).option(
|
|
25900
|
+
"--environment <environment>",
|
|
25901
|
+
"Environment slot to connect to",
|
|
25902
|
+
"dev"
|
|
25903
|
+
).option("--user <userId>", "External user id to connect as").option(
|
|
25904
|
+
"--permissions <list>",
|
|
25905
|
+
"Comma-separated permission profile to ensure",
|
|
25906
|
+
"allow-all"
|
|
25907
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
25908
|
+
async (options) => {
|
|
25909
|
+
try {
|
|
25910
|
+
await connectTestCommand(options);
|
|
25911
|
+
} catch (err) {
|
|
25912
|
+
error(err.message);
|
|
25913
|
+
process.exit(1);
|
|
25914
|
+
}
|
|
23992
25915
|
}
|
|
23993
|
-
|
|
23994
|
-
var session = program2.command("session").description(
|
|
25916
|
+
);
|
|
25917
|
+
var session = program2.command("session").description(
|
|
25918
|
+
"Inspect live Granular sessions and their Automerge-backed state"
|
|
25919
|
+
);
|
|
23995
25920
|
for (const [name, description, handler] of [
|
|
23996
25921
|
["heap", "Print the normalized session heap", sessionHeapCommand],
|
|
23997
25922
|
["doc", "Print the full session document", sessionDocCommand]
|
|
23998
25923
|
]) {
|
|
23999
|
-
session.command(name).description(description).option(
|
|
25924
|
+
session.command(name).description(description).option(
|
|
25925
|
+
"--ontology <ontologyId>",
|
|
25926
|
+
"Override the ontology id from .granularrc"
|
|
25927
|
+
).option(
|
|
25928
|
+
"--environment <environment>",
|
|
25929
|
+
"Environment slot to connect to when no session id is provided",
|
|
25930
|
+
"dev"
|
|
25931
|
+
).option(
|
|
25932
|
+
"--user <userId>",
|
|
25933
|
+
"External user id to connect as when no session id is provided"
|
|
25934
|
+
).option(
|
|
25935
|
+
"--permissions <list>",
|
|
25936
|
+
"Comma-separated permission profile to ensure",
|
|
25937
|
+
"allow-all"
|
|
25938
|
+
).option(
|
|
25939
|
+
"--session <sessionId>",
|
|
25940
|
+
"Connect to an existing session instead of creating a fresh one"
|
|
25941
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
25942
|
+
async (options) => {
|
|
25943
|
+
try {
|
|
25944
|
+
await handler(options);
|
|
25945
|
+
} catch (err) {
|
|
25946
|
+
error(err.message);
|
|
25947
|
+
process.exit(1);
|
|
25948
|
+
}
|
|
25949
|
+
}
|
|
25950
|
+
);
|
|
25951
|
+
}
|
|
25952
|
+
session.command("create").description(
|
|
25953
|
+
"Create a new session for an existing environment or environment slot"
|
|
25954
|
+
).option(
|
|
25955
|
+
"--ontology <ontologyId>",
|
|
25956
|
+
"Override the ontology id from .granularrc"
|
|
25957
|
+
).option(
|
|
25958
|
+
"--environment <environment>",
|
|
25959
|
+
"Environment slot name to resolve when --environment-id is not provided",
|
|
25960
|
+
"dev"
|
|
25961
|
+
).option(
|
|
25962
|
+
"--environment-id <environmentId>",
|
|
25963
|
+
"Create a session for this exact environment id"
|
|
25964
|
+
).option(
|
|
25965
|
+
"--user <userId>",
|
|
25966
|
+
"External user id to connect as when a named environment needs to be created"
|
|
25967
|
+
).option(
|
|
25968
|
+
"--permissions <list>",
|
|
25969
|
+
"Comma-separated permission profile to ensure when a named environment needs to be created",
|
|
25970
|
+
"allow-all"
|
|
25971
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
25972
|
+
async (options) => {
|
|
24000
25973
|
try {
|
|
24001
|
-
await
|
|
25974
|
+
await sessionCreateCommand(options);
|
|
24002
25975
|
} catch (err) {
|
|
24003
25976
|
error(err.message);
|
|
24004
25977
|
process.exit(1);
|
|
24005
25978
|
}
|
|
24006
|
-
});
|
|
24007
|
-
}
|
|
24008
|
-
session.command("create").description("Create a new session for an existing environment or environment slot").option("--ontology <ontologyId>", "Override the ontology id from .granularrc").option("--environment <environment>", "Environment slot name to resolve when --environment-id is not provided", "dev").option("--environment-id <environmentId>", "Create a session for this exact environment id").option("--user <userId>", "External user id to connect as when a named environment needs to be created").option("--permissions <list>", "Comma-separated permission profiles to ensure when a named environment needs to be created", "default").option("--json", "Print machine-readable JSON").action(async (options) => {
|
|
24009
|
-
try {
|
|
24010
|
-
await sessionCreateCommand(options);
|
|
24011
|
-
} catch (err) {
|
|
24012
|
-
error(err.message);
|
|
24013
|
-
process.exit(1);
|
|
24014
25979
|
}
|
|
24015
|
-
|
|
24016
|
-
session.command("list").description("List indexed sessions for an environment").option(
|
|
24017
|
-
|
|
24018
|
-
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
|
|
25980
|
+
);
|
|
25981
|
+
session.command("list").description("List indexed sessions for an environment").option(
|
|
25982
|
+
"--ontology <ontologyId>",
|
|
25983
|
+
"Override the ontology id from .granularrc"
|
|
25984
|
+
).option(
|
|
25985
|
+
"--environment <environment>",
|
|
25986
|
+
"Environment slot name to resolve when --environment-id is not provided",
|
|
25987
|
+
"dev"
|
|
25988
|
+
).option(
|
|
25989
|
+
"--environment-id <environmentId>",
|
|
25990
|
+
"List sessions for this exact environment id"
|
|
25991
|
+
).option(
|
|
25992
|
+
"--status <status>",
|
|
25993
|
+
"Session status filter: active|closed|all",
|
|
25994
|
+
"active"
|
|
25995
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
25996
|
+
async (options) => {
|
|
25997
|
+
try {
|
|
25998
|
+
await sessionListCommand(options);
|
|
25999
|
+
} catch (err) {
|
|
26000
|
+
error(err.message);
|
|
26001
|
+
process.exit(1);
|
|
26002
|
+
}
|
|
24022
26003
|
}
|
|
24023
|
-
|
|
26004
|
+
);
|
|
24024
26005
|
var job = program2.command("job").description("Run real Granular runtime jobs from the CLI");
|
|
24025
|
-
job.command("run").description(
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
26006
|
+
job.command("run").description(
|
|
26007
|
+
"Submit a job from a file or inline code and wait for the result"
|
|
26008
|
+
).option(
|
|
26009
|
+
"--ontology <ontologyId>",
|
|
26010
|
+
"Override the ontology id from .granularrc"
|
|
26011
|
+
).option(
|
|
26012
|
+
"--environment <environment>",
|
|
26013
|
+
"Environment slot to connect to when no session id is provided",
|
|
26014
|
+
"dev"
|
|
26015
|
+
).option(
|
|
26016
|
+
"--user <userId>",
|
|
26017
|
+
"External user id to connect as when no session id is provided"
|
|
26018
|
+
).option(
|
|
26019
|
+
"--permissions <list>",
|
|
26020
|
+
"Comma-separated permission profile to ensure",
|
|
26021
|
+
"allow-all"
|
|
26022
|
+
).option(
|
|
26023
|
+
"--session <sessionId>",
|
|
26024
|
+
"Connect to an existing session instead of creating a fresh one"
|
|
26025
|
+
).option("--file <path>", "Read the job source from a file").option("--inline <code>", "Provide the job source inline").option("--json", "Print machine-readable JSON").action(
|
|
26026
|
+
async (options) => {
|
|
26027
|
+
try {
|
|
26028
|
+
await jobRunCommand(options);
|
|
26029
|
+
} catch (err) {
|
|
26030
|
+
error(err.message);
|
|
26031
|
+
process.exit(1);
|
|
26032
|
+
}
|
|
24031
26033
|
}
|
|
24032
|
-
|
|
24033
|
-
program2.command("graphql").description(
|
|
24034
|
-
|
|
24035
|
-
|
|
24036
|
-
|
|
24037
|
-
|
|
24038
|
-
|
|
26034
|
+
);
|
|
26035
|
+
program2.command("graphql").description(
|
|
26036
|
+
"Execute a real GraphQL query against the current Granular environment"
|
|
26037
|
+
).requiredOption("--query <query>", "GraphQL query or mutation string").option("--variables <json>", "JSON object of GraphQL variables").option(
|
|
26038
|
+
"--ontology <ontologyId>",
|
|
26039
|
+
"Override the ontology id from .granularrc"
|
|
26040
|
+
).option(
|
|
26041
|
+
"--environment <environment>",
|
|
26042
|
+
"Environment slot to connect to when no session id is provided",
|
|
26043
|
+
"dev"
|
|
26044
|
+
).option(
|
|
26045
|
+
"--user <userId>",
|
|
26046
|
+
"External user id to connect as when no session id is provided"
|
|
26047
|
+
).option(
|
|
26048
|
+
"--permissions <list>",
|
|
26049
|
+
"Comma-separated permission profile to ensure",
|
|
26050
|
+
"allow-all"
|
|
26051
|
+
).option(
|
|
26052
|
+
"--session <sessionId>",
|
|
26053
|
+
"Connect to an existing session instead of creating a fresh one"
|
|
26054
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
26055
|
+
async (options) => {
|
|
26056
|
+
try {
|
|
26057
|
+
await graphqlCommand(options);
|
|
26058
|
+
} catch (err) {
|
|
26059
|
+
error(err.message);
|
|
26060
|
+
process.exit(1);
|
|
26061
|
+
}
|
|
24039
26062
|
}
|
|
24040
|
-
|
|
26063
|
+
);
|
|
24041
26064
|
var effects = program2.command("effects").description("Inspect declared and live Granular effects for an environment");
|
|
24042
26065
|
for (const [name, description, handler] of [
|
|
24043
26066
|
["list", "List the current effect catalog", effectsListCommand],
|
|
24044
|
-
[
|
|
26067
|
+
[
|
|
26068
|
+
"diff",
|
|
26069
|
+
"Compare declared effects to live ready handlers",
|
|
26070
|
+
effectsDiffCommand
|
|
26071
|
+
]
|
|
24045
26072
|
]) {
|
|
24046
|
-
effects.command(name).description(description).option(
|
|
24047
|
-
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
26073
|
+
effects.command(name).description(description).option(
|
|
26074
|
+
"--ontology <ontologyId>",
|
|
26075
|
+
"Override the ontology id from .granularrc"
|
|
26076
|
+
).option(
|
|
26077
|
+
"--environment <environment>",
|
|
26078
|
+
"Environment slot to connect to when no session id is provided",
|
|
26079
|
+
"dev"
|
|
26080
|
+
).option(
|
|
26081
|
+
"--user <userId>",
|
|
26082
|
+
"External user id to connect as when no session id is provided"
|
|
26083
|
+
).option(
|
|
26084
|
+
"--permissions <list>",
|
|
26085
|
+
"Comma-separated permission profile to ensure",
|
|
26086
|
+
"allow-all"
|
|
26087
|
+
).option(
|
|
26088
|
+
"--session <sessionId>",
|
|
26089
|
+
"Connect to an existing session instead of creating a fresh one"
|
|
26090
|
+
).option("--json", "Print machine-readable JSON").action(
|
|
26091
|
+
async (options) => {
|
|
26092
|
+
try {
|
|
26093
|
+
await handler(options);
|
|
26094
|
+
} catch (err) {
|
|
26095
|
+
error(err.message);
|
|
26096
|
+
process.exit(1);
|
|
26097
|
+
}
|
|
24052
26098
|
}
|
|
24053
|
-
|
|
26099
|
+
);
|
|
24054
26100
|
}
|
|
24055
26101
|
void program2.parseAsync(process.argv).then(() => {
|
|
24056
26102
|
process.exit(0);
|