rterm-backend 2.7.6 → 2.7.8
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/bin/gybackend.js +224 -0
- package/package.json +1 -1
package/bin/gybackend.js
CHANGED
|
@@ -382290,6 +382290,184 @@ var MonitorStatusService = class {
|
|
|
382290
382290
|
}
|
|
382291
382291
|
};
|
|
382292
382292
|
|
|
382293
|
+
// ../../packages/backend/src/services/governance/agtPolicyEngine.ts
|
|
382294
|
+
var AgtPolicyEngine = class {
|
|
382295
|
+
constructor(deps) {
|
|
382296
|
+
this.deps = deps;
|
|
382297
|
+
this.agentIdentity = deps.agentIdentity ?? "rterm-agent";
|
|
382298
|
+
this.sponsoringPrincipal = deps.sponsoringPrincipal ?? "unknown";
|
|
382299
|
+
}
|
|
382300
|
+
policy = null;
|
|
382301
|
+
agentIdentity;
|
|
382302
|
+
sponsoringPrincipal;
|
|
382303
|
+
/** Load the policy document. Must be called before evaluate(). */
|
|
382304
|
+
async load() {
|
|
382305
|
+
this.policy = await this.deps.loadPolicy();
|
|
382306
|
+
}
|
|
382307
|
+
/** Evaluate an action against the policy. Returns the decision. */
|
|
382308
|
+
evaluate(action, target) {
|
|
382309
|
+
if (!this.policy) {
|
|
382310
|
+
throw new Error("Policy not loaded. Call load() first.");
|
|
382311
|
+
}
|
|
382312
|
+
const actionLower = action.toLowerCase();
|
|
382313
|
+
const targetLower = (target ?? "").toLowerCase();
|
|
382314
|
+
for (const rule of this.policy.rules) {
|
|
382315
|
+
const actionMatch = this.matchPattern(actionLower, rule.actionPattern.toLowerCase());
|
|
382316
|
+
const targetMatch = !rule.targetPattern || this.matchPattern(targetLower, rule.targetPattern.toLowerCase());
|
|
382317
|
+
if (actionMatch && targetMatch) {
|
|
382318
|
+
return {
|
|
382319
|
+
decision: rule.decision,
|
|
382320
|
+
rule: rule.name,
|
|
382321
|
+
reason: rule.reason ?? `matched rule '${rule.name}'`,
|
|
382322
|
+
policyVersion: this.policy.version,
|
|
382323
|
+
action,
|
|
382324
|
+
target
|
|
382325
|
+
};
|
|
382326
|
+
}
|
|
382327
|
+
}
|
|
382328
|
+
return {
|
|
382329
|
+
decision: this.policy.defaultDecision,
|
|
382330
|
+
rule: "default",
|
|
382331
|
+
reason: `no rule matched, using default '${this.policy.defaultDecision}'`,
|
|
382332
|
+
policyVersion: this.policy.version,
|
|
382333
|
+
action,
|
|
382334
|
+
target
|
|
382335
|
+
};
|
|
382336
|
+
}
|
|
382337
|
+
/** Get the current policy document. */
|
|
382338
|
+
getPolicy() {
|
|
382339
|
+
return this.policy;
|
|
382340
|
+
}
|
|
382341
|
+
/** Get the agent identity. */
|
|
382342
|
+
getAgentIdentity() {
|
|
382343
|
+
return this.agentIdentity;
|
|
382344
|
+
}
|
|
382345
|
+
/** Get the sponsoring principal. */
|
|
382346
|
+
getSponsoringPrincipal() {
|
|
382347
|
+
return this.sponsoringPrincipal;
|
|
382348
|
+
}
|
|
382349
|
+
/** Simple glob-style pattern matching: * matches any suffix. The value matches
|
|
382350
|
+
* if it equals the pattern or starts with the pattern (for action patterns
|
|
382351
|
+
* like "read" to match "read /etc/passwd"). */
|
|
382352
|
+
matchPattern(value, pattern) {
|
|
382353
|
+
if (pattern === "*") return true;
|
|
382354
|
+
if (pattern.endsWith("*")) {
|
|
382355
|
+
return value.startsWith(pattern.slice(0, -1));
|
|
382356
|
+
}
|
|
382357
|
+
return value === pattern || value.startsWith(pattern + " ");
|
|
382358
|
+
}
|
|
382359
|
+
};
|
|
382360
|
+
function parsePolicyYaml(yaml) {
|
|
382361
|
+
const lines = yaml.split(/\r?\n/);
|
|
382362
|
+
const doc = { name: "", version: "1.0", defaultDecision: "deny", rules: [] };
|
|
382363
|
+
let currentRule = null;
|
|
382364
|
+
for (const line of lines) {
|
|
382365
|
+
const l = line.trim();
|
|
382366
|
+
if (!l || l.startsWith("#")) continue;
|
|
382367
|
+
if (l.startsWith("name:")) {
|
|
382368
|
+
if (currentRule?.name) {
|
|
382369
|
+
doc.rules.push(currentRule);
|
|
382370
|
+
currentRule = null;
|
|
382371
|
+
}
|
|
382372
|
+
doc.name = l.split(":")[1].trim();
|
|
382373
|
+
} else if (l.startsWith("version:")) {
|
|
382374
|
+
doc.version = l.split(":")[1].trim();
|
|
382375
|
+
} else if (l.startsWith("defaultDecision:")) {
|
|
382376
|
+
doc.defaultDecision = l.split(":")[1].trim();
|
|
382377
|
+
} else if (l.startsWith("- name:")) {
|
|
382378
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382379
|
+
currentRule = { name: l.split(":")[1].trim() };
|
|
382380
|
+
} else if (l.startsWith("actionPattern:") && currentRule) {
|
|
382381
|
+
currentRule.actionPattern = l.split(":")[1].trim();
|
|
382382
|
+
} else if (l.startsWith("targetPattern:") && currentRule) {
|
|
382383
|
+
currentRule.targetPattern = l.split(":")[1].trim();
|
|
382384
|
+
} else if (l.startsWith("decision:") && currentRule) {
|
|
382385
|
+
currentRule.decision = l.split(":")[1].trim();
|
|
382386
|
+
} else if (l.startsWith("reason:") && currentRule) {
|
|
382387
|
+
currentRule.reason = l.split(":").slice(1).join(":").trim();
|
|
382388
|
+
}
|
|
382389
|
+
}
|
|
382390
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382391
|
+
return doc;
|
|
382392
|
+
}
|
|
382393
|
+
|
|
382394
|
+
// ../../packages/backend/src/services/review/reviewService.ts
|
|
382395
|
+
var ReviewService = class {
|
|
382396
|
+
constructor(deps) {
|
|
382397
|
+
this.deps = deps;
|
|
382398
|
+
this.reviewerId = deps.reviewerId ?? "review-model";
|
|
382399
|
+
this.reviewMode = deps.reviewMode ?? "strict";
|
|
382400
|
+
}
|
|
382401
|
+
reviewerId;
|
|
382402
|
+
reviewMode;
|
|
382403
|
+
/** Review an action. Returns the review result. */
|
|
382404
|
+
async review(action) {
|
|
382405
|
+
if (this.reviewMode === "auto-approve" && this.isLowRisk(action)) {
|
|
382406
|
+
return {
|
|
382407
|
+
verdict: "approved",
|
|
382408
|
+
issues: [],
|
|
382409
|
+
confidence: 1,
|
|
382410
|
+
reasoning: "auto-approved (low-risk action)",
|
|
382411
|
+
reviewerId: this.reviewerId,
|
|
382412
|
+
skipped: true
|
|
382413
|
+
};
|
|
382414
|
+
}
|
|
382415
|
+
const prompt = this.buildReviewPrompt(action);
|
|
382416
|
+
const result = await this.deps.runReviewModel(prompt);
|
|
382417
|
+
let verdict = result.verdict;
|
|
382418
|
+
if (this.reviewMode === "advisory" && verdict === "escalate") {
|
|
382419
|
+
verdict = "needs_revision";
|
|
382420
|
+
}
|
|
382421
|
+
return {
|
|
382422
|
+
verdict,
|
|
382423
|
+
issues: result.issues ?? [],
|
|
382424
|
+
confidence: result.confidence ?? 0.5,
|
|
382425
|
+
reasoning: result.reasoning ?? "",
|
|
382426
|
+
reviewerId: this.reviewerId,
|
|
382427
|
+
skipped: false
|
|
382428
|
+
};
|
|
382429
|
+
}
|
|
382430
|
+
/** Check if an action is low-risk (for auto-approve mode). */
|
|
382431
|
+
isLowRisk(action) {
|
|
382432
|
+
const type2 = action.type.toLowerCase();
|
|
382433
|
+
const target = (action.target ?? "").toLowerCase();
|
|
382434
|
+
if (["read", "status", "list", "show", "get", "describe", "check"].some((w) => type2.includes(w))) return true;
|
|
382435
|
+
if (target.includes("dev") || target.includes("staging") || target.includes("test")) return true;
|
|
382436
|
+
return false;
|
|
382437
|
+
}
|
|
382438
|
+
/** Build the review prompt for the review model. */
|
|
382439
|
+
buildReviewPrompt(action) {
|
|
382440
|
+
return `You are a review model. Independently verify the following action for correctness, completeness, safety, compliance, and accuracy.
|
|
382441
|
+
|
|
382442
|
+
Action type: ${action.type}
|
|
382443
|
+
Target: ${action.target ?? "unknown"}
|
|
382444
|
+
Command: ${action.command ?? "none"}
|
|
382445
|
+
User request: ${action.userRequest ?? "unknown"}
|
|
382446
|
+
|
|
382447
|
+
Evaluate the action on 5 dimensions:
|
|
382448
|
+
1. Correctness: Is this the right action for the user's intent?
|
|
382449
|
+
2. Completeness: Does this action fully address the user's request?
|
|
382450
|
+
3. Safety: Is this action safe (not destructive, not harmful)?
|
|
382451
|
+
4. Compliance: Does this action comply with policy?
|
|
382452
|
+
5. Accuracy: Is the information in this action accurate?
|
|
382453
|
+
|
|
382454
|
+
Return your verdict: approved, needs_revision, or escalate, with issues and reasoning.`;
|
|
382455
|
+
}
|
|
382456
|
+
};
|
|
382457
|
+
function createSkippedReviewResult() {
|
|
382458
|
+
return {
|
|
382459
|
+
verdict: "approved",
|
|
382460
|
+
issues: [],
|
|
382461
|
+
confidence: 1,
|
|
382462
|
+
reasoning: "review skipped (no reviewModelId specified)",
|
|
382463
|
+
reviewerId: "none",
|
|
382464
|
+
skipped: true
|
|
382465
|
+
};
|
|
382466
|
+
}
|
|
382467
|
+
function shouldSkipReview(reviewModelId) {
|
|
382468
|
+
return !reviewModelId || reviewModelId.trim() === "";
|
|
382469
|
+
}
|
|
382470
|
+
|
|
382293
382471
|
// ../../packages/backend/src/services/observability.ts
|
|
382294
382472
|
function createObservability(deps) {
|
|
382295
382473
|
const log = deps.onLog ?? (() => {
|
|
@@ -382369,6 +382547,50 @@ function createObservability(deps) {
|
|
|
382369
382547
|
const auditLedger = new AuditLedger({});
|
|
382370
382548
|
const evidenceSealer = new EvidenceSealer({});
|
|
382371
382549
|
const monitorStatus = new MonitorStatusService(deps.resourceMonitorService, deps.terminalService);
|
|
382550
|
+
const policyEngine = new AgtPolicyEngine({
|
|
382551
|
+
loadPolicy: async () => {
|
|
382552
|
+
const req = createRequire2(import.meta.url);
|
|
382553
|
+
const fs22 = req("node:fs");
|
|
382554
|
+
const path31 = req("node:path");
|
|
382555
|
+
const policyPath = path31.join(".", "policy.yaml");
|
|
382556
|
+
if (fs22.existsSync(policyPath)) {
|
|
382557
|
+
return parsePolicyYaml(fs22.readFileSync(policyPath, "utf8"));
|
|
382558
|
+
}
|
|
382559
|
+
return {
|
|
382560
|
+
name: "rterm-default",
|
|
382561
|
+
version: "1.0",
|
|
382562
|
+
defaultDecision: "deny",
|
|
382563
|
+
rules: [
|
|
382564
|
+
{ name: "allow-read", actionPattern: "read", decision: "allow" },
|
|
382565
|
+
{ name: "allow-status", actionPattern: "status", decision: "allow" },
|
|
382566
|
+
{ name: "allow-list", actionPattern: "list", decision: "allow" },
|
|
382567
|
+
{ name: "deny-delete", actionPattern: "delete", decision: "deny" },
|
|
382568
|
+
{ name: "deny-drop", actionPattern: "drop", decision: "deny" },
|
|
382569
|
+
{ name: "deny-format", actionPattern: "format", decision: "deny" },
|
|
382570
|
+
{ name: "escalate-restart-prod", actionPattern: "restart", targetPattern: "prod-*", decision: "escalate" },
|
|
382571
|
+
{ name: "escalate-patch-prod", actionPattern: "patch", targetPattern: "prod-*", decision: "escalate" },
|
|
382572
|
+
{ name: "escalate-deploy-prod", actionPattern: "deploy", targetPattern: "prod-*", decision: "escalate" },
|
|
382573
|
+
{ name: "allow-restart", actionPattern: "restart", decision: "allow" },
|
|
382574
|
+
{ name: "allow-patch", actionPattern: "patch", decision: "allow" },
|
|
382575
|
+
{ name: "allow-deploy", actionPattern: "deploy", decision: "allow" }
|
|
382576
|
+
]
|
|
382577
|
+
};
|
|
382578
|
+
},
|
|
382579
|
+
agentIdentity: `rterm-agent-v${process.env.GYBACKEND_VERSION ?? "2.7.7"}`,
|
|
382580
|
+
sponsoringPrincipal: process.env.USER ?? "unknown"
|
|
382581
|
+
});
|
|
382582
|
+
void policyEngine.load().catch(() => {
|
|
382583
|
+
});
|
|
382584
|
+
const reviewService = new ReviewService({
|
|
382585
|
+
runReviewModel: deps.runReviewModel ?? (async (_prompt) => ({
|
|
382586
|
+
verdict: "approved",
|
|
382587
|
+
issues: [],
|
|
382588
|
+
reasoning: "no review model configured",
|
|
382589
|
+
confidence: 1
|
|
382590
|
+
})),
|
|
382591
|
+
reviewerId: deps.reviewerId ?? "review-model",
|
|
382592
|
+
reviewMode: deps.reviewMode ?? "strict"
|
|
382593
|
+
});
|
|
382372
382594
|
const pluginScanRoot = (process.env.GYBACKEND_DATA_DIR ?? "./.gybackend-data") + "/plugins";
|
|
382373
382595
|
const bundlePluginRoot = new URL("../../plugins/", import.meta.url).pathname;
|
|
382374
382596
|
const scanRoots = [pluginScanRoot, "./plugins"];
|
|
@@ -382442,6 +382664,8 @@ function createObservability(deps) {
|
|
|
382442
382664
|
aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
|
|
382443
382665
|
audit: { ledger: auditLedger, sealer: evidenceSealer },
|
|
382444
382666
|
monitorStatus,
|
|
382667
|
+
governance: { policyEngine },
|
|
382668
|
+
review: { service: reviewService, shouldSkipReview, createSkippedReviewResult },
|
|
382445
382669
|
pluginRegistry
|
|
382446
382670
|
};
|
|
382447
382671
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.8",
|
|
4
4
|
"description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system) and drive it over a WebSocket JSON-RPC gateway. No desktop UI required.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|