pi-harness-runtime 0.9.14 → 0.9.16
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/harness/auto-quota-resume.ts +129 -0
- package/harness/e2e/minimax-quota-scraper.ts +10 -4
- package/harness/index.ts +8 -0
- package/harness/loop-runtime.ts +57 -0
- package/index.ts +47 -11
- package/mirror.ts +40 -18
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/README.md +48 -0
- package/packages/autonomous-runtime/dist/inbox.d.ts +75 -0
- package/packages/autonomous-runtime/dist/inbox.d.ts.map +1 -0
- package/packages/autonomous-runtime/dist/inbox.js +212 -0
- package/packages/autonomous-runtime/dist/inbox.js.map +1 -0
- package/packages/autonomous-runtime/dist/index.d.ts +17 -0
- package/packages/autonomous-runtime/dist/index.d.ts.map +1 -0
- package/packages/autonomous-runtime/dist/index.js +17 -0
- package/packages/autonomous-runtime/dist/index.js.map +1 -0
- package/packages/autonomous-runtime/dist/lease.d.ts +78 -0
- package/packages/autonomous-runtime/dist/lease.d.ts.map +1 -0
- package/packages/autonomous-runtime/dist/lease.js +273 -0
- package/packages/autonomous-runtime/dist/lease.js.map +1 -0
- package/packages/autonomous-runtime/dist/types.d.ts +160 -0
- package/packages/autonomous-runtime/dist/types.d.ts.map +1 -0
- package/packages/autonomous-runtime/dist/types.js +31 -0
- package/packages/autonomous-runtime/dist/types.js.map +1 -0
- package/packages/autonomous-runtime/dist/worker.d.ts +63 -0
- package/packages/autonomous-runtime/dist/worker.d.ts.map +1 -0
- package/packages/autonomous-runtime/dist/worker.js +133 -0
- package/packages/autonomous-runtime/dist/worker.js.map +1 -0
- package/packages/autonomous-runtime/package.json +34 -0
- package/packages/autonomous-runtime/src/inbox.ts +266 -0
- package/packages/autonomous-runtime/src/index.ts +22 -0
- package/packages/autonomous-runtime/src/lease.ts +318 -0
- package/packages/autonomous-runtime/src/types.ts +257 -0
- package/packages/autonomous-runtime/test/autonomous-runtime.test.ts +194 -0
- package/packages/autonomous-runtime/test/inbox.test.mjs +266 -0
- package/packages/autonomous-runtime/test/inbox.test.ts +80 -0
- package/packages/autonomous-runtime/test/types.test.ts +68 -0
- package/packages/autonomous-runtime/tsconfig.json +22 -0
- package/packages/autonomous-runtime/tsconfig.tsbuildinfo +1 -0
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/README.md +45 -0
- package/packages/privilege-broker/config/privileges.yaml.example +172 -0
- package/packages/privilege-broker/dist/approval.d.ts +23 -0
- package/packages/privilege-broker/dist/approval.d.ts.map +1 -0
- package/packages/privilege-broker/dist/approval.js +45 -0
- package/packages/privilege-broker/dist/approval.js.map +1 -0
- package/packages/privilege-broker/dist/audit.d.ts +38 -0
- package/packages/privilege-broker/dist/audit.d.ts.map +1 -0
- package/packages/privilege-broker/dist/audit.js +125 -0
- package/packages/privilege-broker/dist/audit.js.map +1 -0
- package/packages/privilege-broker/dist/executor.d.ts +25 -0
- package/packages/privilege-broker/dist/executor.d.ts.map +1 -0
- package/packages/privilege-broker/dist/executor.js +83 -0
- package/packages/privilege-broker/dist/executor.js.map +1 -0
- package/packages/privilege-broker/dist/index.d.ts +30 -0
- package/packages/privilege-broker/dist/index.d.ts.map +1 -0
- package/packages/privilege-broker/dist/index.js +5 -0
- package/packages/privilege-broker/dist/index.js.map +1 -0
- package/packages/privilege-broker/dist/registry.d.ts +15 -0
- package/packages/privilege-broker/dist/registry.d.ts.map +1 -0
- package/packages/privilege-broker/dist/registry.js +76 -0
- package/packages/privilege-broker/dist/registry.js.map +1 -0
- package/packages/privilege-broker/dist/types.d.ts +77 -0
- package/packages/privilege-broker/dist/types.d.ts.map +1 -0
- package/packages/privilege-broker/dist/types.js +11 -0
- package/packages/privilege-broker/dist/types.js.map +1 -0
- package/packages/privilege-broker/package.json +35 -0
- package/packages/privilege-broker/src/approval.ts +70 -0
- package/packages/privilege-broker/src/audit.ts +142 -0
- package/packages/privilege-broker/src/executor.ts +110 -0
- package/packages/privilege-broker/src/index.ts +46 -0
- package/packages/privilege-broker/src/registry.ts +98 -0
- package/packages/privilege-broker/src/types.ts +105 -0
- package/packages/privilege-broker/tsconfig.json +22 -0
- package/packages/privilege-broker/tsconfig.tsbuildinfo +1 -0
- package/packages/project-analyzer/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/dist/provider-id.d.ts +2 -0
- package/packages/providers/dist/provider-id.d.ts.map +1 -1
- package/packages/providers/dist/provider-id.js +9 -0
- package/packages/providers/dist/provider-id.js.map +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/providers/src/provider-id.ts +12 -0
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/README.md +36 -0
- package/packages/scheduler-adapter/dist/cron.d.ts +21 -0
- package/packages/scheduler-adapter/dist/cron.d.ts.map +1 -0
- package/packages/scheduler-adapter/dist/cron.js +116 -0
- package/packages/scheduler-adapter/dist/cron.js.map +1 -0
- package/packages/scheduler-adapter/dist/index.d.ts +9 -0
- package/packages/scheduler-adapter/dist/index.d.ts.map +1 -0
- package/packages/scheduler-adapter/dist/index.js +8 -0
- package/packages/scheduler-adapter/dist/index.js.map +1 -0
- package/packages/scheduler-adapter/dist/interface.d.ts +61 -0
- package/packages/scheduler-adapter/dist/interface.d.ts.map +1 -0
- package/packages/scheduler-adapter/dist/interface.js +2 -0
- package/packages/scheduler-adapter/dist/interface.js.map +1 -0
- package/packages/scheduler-adapter/dist/internal.d.ts +30 -0
- package/packages/scheduler-adapter/dist/internal.d.ts.map +1 -0
- package/packages/scheduler-adapter/dist/internal.js +82 -0
- package/packages/scheduler-adapter/dist/internal.js.map +1 -0
- package/packages/scheduler-adapter/dist/systemd.d.ts +31 -0
- package/packages/scheduler-adapter/dist/systemd.d.ts.map +1 -0
- package/packages/scheduler-adapter/dist/systemd.js +203 -0
- package/packages/scheduler-adapter/dist/systemd.js.map +1 -0
- package/packages/scheduler-adapter/package.json +32 -0
- package/packages/scheduler-adapter/src/cron.ts +123 -0
- package/packages/scheduler-adapter/src/index.ts +9 -0
- package/packages/scheduler-adapter/src/interface.ts +61 -0
- package/packages/scheduler-adapter/src/internal.ts +99 -0
- package/packages/scheduler-adapter/src/systemd.ts +221 -0
- package/packages/scheduler-adapter/tsconfig.json +22 -0
- package/packages/scheduler-adapter/tsconfig.tsbuildinfo +1 -0
- package/packages/session/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/dist/runtime-types.d.ts +6 -0
- package/packages/types/dist/runtime-types.d.ts.map +1 -1
- package/packages/types/dist/runtime-types.js +2 -1
- package/packages/types/dist/runtime-types.js.map +1 -1
- package/packages/types/package.json +1 -1
- package/packages/types/src/ai-providers.ts +73 -0
- package/packages/types/src/runtime-types.ts +15 -0
- package/packages/types/tsconfig.tsbuildinfo +1 -1
- package/packages/worktree/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Logger — RFC-0101 §7
|
|
3
|
+
*
|
|
4
|
+
* Logs every capability grant/denial with actor, target, reason, and outcome.
|
|
5
|
+
* Built-in implementations: Console, File, NoOp.
|
|
6
|
+
* Replace with your preferred audit backend (syslog, PostgreSQL, Loki, etc.)
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
} from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import type { AuditEntry, AuditLogger } from "./types.js";
|
|
17
|
+
|
|
18
|
+
/** No-op logger — discards all events. */
|
|
19
|
+
export class NoOpAuditLogger implements AuditLogger {
|
|
20
|
+
log(_entry: AuditEntry): void {
|
|
21
|
+
// no-op
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Console logger — writes human-readable lines to stdout/stderr. */
|
|
26
|
+
export class ConsoleAuditLogger implements AuditLogger {
|
|
27
|
+
log(entry: AuditEntry): void {
|
|
28
|
+
const prefix =
|
|
29
|
+
entry.outcome === "granted"
|
|
30
|
+
? "✅"
|
|
31
|
+
: entry.outcome === "denied"
|
|
32
|
+
? "❌"
|
|
33
|
+
: "⚠️";
|
|
34
|
+
const line = [
|
|
35
|
+
prefix,
|
|
36
|
+
`${entry.outcome.toUpperCase()}`,
|
|
37
|
+
`actor=${entry.actor}`,
|
|
38
|
+
`capability=${entry.capability}`,
|
|
39
|
+
`reason=${entry.reason}`,
|
|
40
|
+
entry.error ? `error=${entry.error}` : "",
|
|
41
|
+
]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join(" ");
|
|
44
|
+
if (entry.outcome === "error") {
|
|
45
|
+
console.error(`[AUDIT] ${line}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`[AUDIT] ${line}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* JSON Lines file logger — appends one JSON object per event.
|
|
54
|
+
* Auto-creates parent directory. Auto-rotates when file exceeds maxSizeBytes.
|
|
55
|
+
*/
|
|
56
|
+
export class FileAuditLogger implements AuditLogger {
|
|
57
|
+
private readonly logPath: string;
|
|
58
|
+
private readonly maxSizeBytes: number;
|
|
59
|
+
private readonly maxRotations: number;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
options: {
|
|
63
|
+
logPath?: string;
|
|
64
|
+
maxSizeBytes?: number;
|
|
65
|
+
maxRotations?: number;
|
|
66
|
+
} = {},
|
|
67
|
+
) {
|
|
68
|
+
const home = process.env["HOME"] ?? "/tmp";
|
|
69
|
+
this.logPath =
|
|
70
|
+
options.logPath ?? join(home, ".pi", "harness", "broker-audit.logl");
|
|
71
|
+
this.maxSizeBytes = options.maxSizeBytes ?? 10 * 1024 * 1024; // 10 MB
|
|
72
|
+
this.maxRotations = options.maxRotations ?? 5;
|
|
73
|
+
this._ensureDir();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log(entry: AuditEntry): void {
|
|
77
|
+
this._rotateIfNeeded();
|
|
78
|
+
const line = JSON.stringify(entry) + "\n";
|
|
79
|
+
writeFileSync(this.logPath, line, { flag: "a" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private _ensureDir(): void {
|
|
83
|
+
const dir = dirname(this.logPath);
|
|
84
|
+
if (!existsSync(dir)) {
|
|
85
|
+
mkdirSync(dir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private _rotateIfNeeded(): void {
|
|
90
|
+
if (!existsSync(this.logPath)) return;
|
|
91
|
+
try {
|
|
92
|
+
const { statSync } = require("node:fs");
|
|
93
|
+
const stat = statSync(this.logPath);
|
|
94
|
+
if (stat.size < this.maxSizeBytes) return;
|
|
95
|
+
|
|
96
|
+
// Rotate: broker-audit.log → broker-audit.log.1 → ...
|
|
97
|
+
const { renameSync, existsSync: exists } = require("node:fs");
|
|
98
|
+
for (let i = this.maxRotations - 1; i >= 1; i--) {
|
|
99
|
+
const from = `${this.logPath}.${i}`;
|
|
100
|
+
const to = `${this.logPath}.${i + 1}`;
|
|
101
|
+
if (exists(to)) unlinkSync(to);
|
|
102
|
+
if (exists(from)) renameSync(from, to);
|
|
103
|
+
}
|
|
104
|
+
renameSync(this.logPath, `${this.logPath}.1`);
|
|
105
|
+
} catch {
|
|
106
|
+
// If rotation fails, continue writing to the existing file
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read recent audit entries.
|
|
112
|
+
* Returns newest entries last.
|
|
113
|
+
*/
|
|
114
|
+
query(options?: {
|
|
115
|
+
workerId?: string;
|
|
116
|
+
taskId?: string;
|
|
117
|
+
capability?: string;
|
|
118
|
+
success?: boolean;
|
|
119
|
+
limit?: number;
|
|
120
|
+
}): AuditEntry[] {
|
|
121
|
+
if (!existsSync(this.logPath)) return [];
|
|
122
|
+
const raw = readFileSync(this.logPath, "utf8");
|
|
123
|
+
const entries: AuditEntry[] = [];
|
|
124
|
+
for (const line of raw.split("\n")) {
|
|
125
|
+
if (!line.trim()) continue;
|
|
126
|
+
try {
|
|
127
|
+
const e = JSON.parse(line) as AuditEntry;
|
|
128
|
+
if (options?.workerId && e.workerId !== options.workerId) continue;
|
|
129
|
+
if (options?.taskId && e.taskId !== options.taskId) continue;
|
|
130
|
+
if (options?.capability && e.capability !== options.capability)
|
|
131
|
+
continue;
|
|
132
|
+
if (options?.success !== undefined && e.success !== options.success)
|
|
133
|
+
continue;
|
|
134
|
+
entries.push(e);
|
|
135
|
+
} catch {
|
|
136
|
+
// Skip malformed lines
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const result = options?.limit ? entries.slice(-options.limit) : entries;
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Broker Executor — RFC-0101 §7
|
|
3
|
+
*
|
|
4
|
+
* Executes a resolved CapabilityGrant using execve(2) (no shell).
|
|
5
|
+
* The service account must exist and have minimal permissions.
|
|
6
|
+
*
|
|
7
|
+
* Safety properties:
|
|
8
|
+
* - No shell interpolation — argv is passed directly to execve
|
|
9
|
+
* - Env whitelist enforced — only approved env vars are forwarded
|
|
10
|
+
* - Working directory constrained via chdir
|
|
11
|
+
* - Timeout enforced — process is killed if it exceeds timeoutMs
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import type { AuditLogger } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/** The BrokerExecutor resolves and executes capability grants. */
|
|
17
|
+
export class BrokerExecutor {
|
|
18
|
+
constructor(private readonly audit: AuditLogger) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute a command as the configured service account.
|
|
22
|
+
* Throws on timeout or spawn error.
|
|
23
|
+
*/
|
|
24
|
+
async execute(options: {
|
|
25
|
+
command: string;
|
|
26
|
+
args: string[];
|
|
27
|
+
envWhitelist?: string[];
|
|
28
|
+
allowedCwd?: string;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
actor: string;
|
|
31
|
+
capability: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
}): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
34
|
+
const {
|
|
35
|
+
command,
|
|
36
|
+
args,
|
|
37
|
+
envWhitelist,
|
|
38
|
+
allowedCwd,
|
|
39
|
+
timeoutMs = 30_000,
|
|
40
|
+
actor,
|
|
41
|
+
capability,
|
|
42
|
+
reason,
|
|
43
|
+
} = options;
|
|
44
|
+
|
|
45
|
+
// Build minimal env
|
|
46
|
+
const env: Record<string, string> = {};
|
|
47
|
+
if (envWhitelist) {
|
|
48
|
+
for (const key of envWhitelist) {
|
|
49
|
+
if (process.env[key] !== undefined) {
|
|
50
|
+
env[key] = process.env[key]!;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
env.PI_HARNESS_ACTOR = actor;
|
|
55
|
+
env.PI_HARNESS_CAPABILITY = capability;
|
|
56
|
+
|
|
57
|
+
const child = spawn(command, args, {
|
|
58
|
+
env,
|
|
59
|
+
cwd: allowedCwd ?? process.cwd(),
|
|
60
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
child.stdout?.on("data", (chunk) => {
|
|
66
|
+
stdout += chunk.toString();
|
|
67
|
+
});
|
|
68
|
+
child.stderr?.on("data", (chunk) => {
|
|
69
|
+
stderr += chunk.toString();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
child.kill("SIGKILL");
|
|
75
|
+
reject(new Error(`command timed out after ${timeoutMs}ms`));
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
|
|
78
|
+
child.on("exit", (code) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
const exitCode = code ?? 0;
|
|
81
|
+
this.audit.log({
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
actor,
|
|
84
|
+
capability: capability as Parameters<
|
|
85
|
+
typeof this.audit.log
|
|
86
|
+
>[0]["capability"],
|
|
87
|
+
reason,
|
|
88
|
+
outcome: exitCode === 0 ? "granted" : "error",
|
|
89
|
+
error: exitCode !== 0 ? `exit ${exitCode}` : undefined,
|
|
90
|
+
});
|
|
91
|
+
resolve({ exitCode, stdout, stderr });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
child.on("error", (err) => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
this.audit.log({
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
actor,
|
|
99
|
+
capability: capability as Parameters<
|
|
100
|
+
typeof this.audit.log
|
|
101
|
+
>[0]["capability"],
|
|
102
|
+
reason,
|
|
103
|
+
outcome: "error",
|
|
104
|
+
error: err.message,
|
|
105
|
+
});
|
|
106
|
+
reject(err);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privilege Broker — RFC-0101 §7
|
|
3
|
+
*
|
|
4
|
+
* Provides safe privilege escalation for autonomous tasks:
|
|
5
|
+
* - Lists available capabilities
|
|
6
|
+
* - Checks human approval
|
|
7
|
+
* - Executes privileged operations safely
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { PrivilegeBroker } from "@pi-harness/privilege-broker";
|
|
12
|
+
*
|
|
13
|
+
* const broker = new PrivilegeBroker({
|
|
14
|
+
* registry: defaultCapabilityRegistry(),
|
|
15
|
+
* audit: new ConsoleAuditLogger(),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const ok = await broker.check(
|
|
19
|
+
* "run_command",
|
|
20
|
+
* { command: "npm test" },
|
|
21
|
+
* "pi-runtime-agent",
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export type {
|
|
26
|
+
PrivilegeEntry,
|
|
27
|
+
PrivilegeRegistry,
|
|
28
|
+
CapabilityName,
|
|
29
|
+
CapabilityGrant,
|
|
30
|
+
AuditEntry,
|
|
31
|
+
AuditLogger,
|
|
32
|
+
ApprovalClass,
|
|
33
|
+
} from "./types.js";
|
|
34
|
+
export { loadRegistry, type PrivilegeRegistryError } from "./registry.js";
|
|
35
|
+
export {
|
|
36
|
+
type ApprovalResult,
|
|
37
|
+
registerApprovalClass,
|
|
38
|
+
checkApproval,
|
|
39
|
+
clearApprovalCache,
|
|
40
|
+
} from "./approval.js";
|
|
41
|
+
export {
|
|
42
|
+
NoOpAuditLogger,
|
|
43
|
+
ConsoleAuditLogger,
|
|
44
|
+
FileAuditLogger,
|
|
45
|
+
} from "./audit.js";
|
|
46
|
+
export { BrokerExecutor } from "./executor.js";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privilege Registry — RFC-0101 §7
|
|
3
|
+
*
|
|
4
|
+
* Loads config/privileges.yaml at startup and provides:
|
|
5
|
+
* - lookup(capability) → PrivilegeEntry | null
|
|
6
|
+
* - knownCapabilities() → CapabilityName[]
|
|
7
|
+
* - validate() → { valid: boolean; errors: string[] }
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { parse as parseYaml } from "yaml";
|
|
12
|
+
import type {
|
|
13
|
+
PrivilegeEntry,
|
|
14
|
+
PrivilegeRegistry,
|
|
15
|
+
CapabilityName,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
export class PrivilegeRegistryError extends Error {
|
|
19
|
+
constructor(msg: string) {
|
|
20
|
+
super(`[PrivilegeRegistryError] ${msg}`);
|
|
21
|
+
this.name = "PrivilegeRegistryError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Default location relative to the package root. */
|
|
26
|
+
export const DEFAULT_PRIVILEGES_PATH = join(
|
|
27
|
+
dirname(new URL(".", import.meta.url).pathname),
|
|
28
|
+
"..",
|
|
29
|
+
"config",
|
|
30
|
+
"privileges.yaml",
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load and validate the privilege registry from a YAML file.
|
|
35
|
+
*
|
|
36
|
+
* @throws {PrivilegeRegistryError} if the file is missing or invalid
|
|
37
|
+
*/
|
|
38
|
+
export function loadRegistry(path?: string): PrivilegeRegistry {
|
|
39
|
+
const filePath = path ?? DEFAULT_PRIVILEGES_PATH;
|
|
40
|
+
if (!existsSync(filePath)) {
|
|
41
|
+
throw new PrivilegeRegistryError(
|
|
42
|
+
`privileges file not found at ${filePath}. Copy config/privileges.yaml.example to config/privileges.yaml and edit it.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let raw: unknown;
|
|
47
|
+
try {
|
|
48
|
+
const content = readFileSync(filePath, "utf8");
|
|
49
|
+
raw = parseYaml(content);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new PrivilegeRegistryError(
|
|
52
|
+
`failed to parse ${filePath}: ${(err as Error).message}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const registry = raw as PrivilegeRegistry;
|
|
57
|
+
|
|
58
|
+
// Validate
|
|
59
|
+
const errors = validate(registry);
|
|
60
|
+
if (errors.length > 0) {
|
|
61
|
+
throw new PrivilegeRegistryError(
|
|
62
|
+
`validation errors in ${filePath}:\n ${errors.join("\n ")}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return registry;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Lightweight validation — check required fields. */
|
|
70
|
+
export function validate(registry: PrivilegeRegistry): string[] {
|
|
71
|
+
const errors: string[] = [];
|
|
72
|
+
if (!registry || typeof registry !== "object") {
|
|
73
|
+
errors.push("registry must be an object");
|
|
74
|
+
return errors;
|
|
75
|
+
}
|
|
76
|
+
if (typeof registry.version !== "number") {
|
|
77
|
+
errors.push("registry.version is required and must be a number");
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(registry.capabilities)) {
|
|
80
|
+
errors.push("registry.capabilities must be an array");
|
|
81
|
+
return errors;
|
|
82
|
+
}
|
|
83
|
+
for (const cap of registry.capabilities) {
|
|
84
|
+
if (!cap.capability) {
|
|
85
|
+
errors.push("each capability must have a 'capability' field");
|
|
86
|
+
}
|
|
87
|
+
if (!cap.description) {
|
|
88
|
+
errors.push(`capability ${cap.capability} missing 'description'`);
|
|
89
|
+
}
|
|
90
|
+
if (!cap.approval_class) {
|
|
91
|
+
errors.push(`capability ${cap.capability} missing 'approval_class'`);
|
|
92
|
+
}
|
|
93
|
+
if (!cap.actors || typeof cap.actors !== "object") {
|
|
94
|
+
errors.push(`capability ${cap.capability} missing 'actors' object`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return errors;
|
|
98
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privilege Broker — Types
|
|
3
|
+
* RFC-0101 §7-§8
|
|
4
|
+
*
|
|
5
|
+
* Duplicates CapabilityName from @pi/autonomous-runtime here to avoid
|
|
6
|
+
* circular import issues in the workspace. Once the workspace is built,
|
|
7
|
+
* this can be replaced with:
|
|
8
|
+
* import type { CapabilityName } from "@pi/autonomous-runtime";
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A named capability that the privilege broker may grant. */
|
|
12
|
+
export type CapabilityName =
|
|
13
|
+
| "files.read"
|
|
14
|
+
| "files.write"
|
|
15
|
+
| "files.exec"
|
|
16
|
+
| "git.commit"
|
|
17
|
+
| "git.push"
|
|
18
|
+
| "gh.api"
|
|
19
|
+
| "npm.publish"
|
|
20
|
+
| "run_command"
|
|
21
|
+
| "sudo";
|
|
22
|
+
|
|
23
|
+
/** Policy for one capability. */
|
|
24
|
+
export interface PrivilegeEntry {
|
|
25
|
+
capability: CapabilityName;
|
|
26
|
+
/** Human-readable description shown in approval prompts. */
|
|
27
|
+
description: string;
|
|
28
|
+
/** Per-actor policy. Default deny if no match. */
|
|
29
|
+
actors: Record<
|
|
30
|
+
string,
|
|
31
|
+
{
|
|
32
|
+
/** "allow", "deny", or "ask" (requires human approval). */
|
|
33
|
+
default: "allow" | "deny" | "ask";
|
|
34
|
+
/** Optional: cron expression for time-of-day restriction. */
|
|
35
|
+
time_window?: string;
|
|
36
|
+
/** Optional: condition expression evaluated at runtime. */
|
|
37
|
+
condition?: string;
|
|
38
|
+
/** Max exec-time in ms. 0 = no limit. */
|
|
39
|
+
timeout_ms?: number;
|
|
40
|
+
/** Environment variable whitelist (exact keys). */
|
|
41
|
+
env_whitelist?: string[];
|
|
42
|
+
/** Optional: working directory constraint. */
|
|
43
|
+
allowed_cwd?: string;
|
|
44
|
+
}
|
|
45
|
+
>;
|
|
46
|
+
/** Approval class id — determines how to prompt human. */
|
|
47
|
+
approval_class: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Loaded registry of all known capabilities. */
|
|
51
|
+
export interface PrivilegeRegistry {
|
|
52
|
+
version: number;
|
|
53
|
+
capabilities: PrivilegeEntry[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Result of a broker.check() call. */
|
|
57
|
+
export interface CapabilityGrant {
|
|
58
|
+
granted: boolean;
|
|
59
|
+
reason: string;
|
|
60
|
+
/** Filled if granted = true. */
|
|
61
|
+
exec_options?: {
|
|
62
|
+
timeout_ms?: number;
|
|
63
|
+
env_whitelist?: string[];
|
|
64
|
+
allowed_cwd?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Log entry for audit trail. */
|
|
69
|
+
export type AuditOutcome =
|
|
70
|
+
| "granted"
|
|
71
|
+
| "denied"
|
|
72
|
+
| "auto_denied"
|
|
73
|
+
| "auto_approved"
|
|
74
|
+
| "error";
|
|
75
|
+
|
|
76
|
+
export interface AuditEntry {
|
|
77
|
+
timestamp: string;
|
|
78
|
+
workerId?: string;
|
|
79
|
+
taskId?: string;
|
|
80
|
+
actor: string;
|
|
81
|
+
capability: CapabilityName;
|
|
82
|
+
reason: string;
|
|
83
|
+
outcome: AuditOutcome;
|
|
84
|
+
success?: boolean;
|
|
85
|
+
error?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Audit logger interface. */
|
|
89
|
+
export interface AuditLogger {
|
|
90
|
+
log(entry: AuditEntry): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Approval class — determines how to prompt the human. */
|
|
94
|
+
export interface ApprovalClass {
|
|
95
|
+
id: string;
|
|
96
|
+
/**
|
|
97
|
+
* Prompt the human for approval.
|
|
98
|
+
* Returns "granted" or "denied".
|
|
99
|
+
*/
|
|
100
|
+
prompt(
|
|
101
|
+
capability: CapabilityName,
|
|
102
|
+
actor: string,
|
|
103
|
+
reason: string,
|
|
104
|
+
): Promise<"granted" | "denied">;
|
|
105
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true,
|
|
17
|
+
"composite": true,
|
|
18
|
+
"types": ["node"]
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist", "test"]
|
|
22
|
+
}
|