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.
Files changed (2) hide show
  1. package/bin/gybackend.js +224 -0
  2. 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.6",
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",