@loopover/miner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOYMENT.md +156 -0
- package/Dockerfile +38 -0
- package/README.md +269 -0
- package/bin/gittensory-miner-mcp.d.ts +64 -0
- package/bin/gittensory-miner-mcp.js +279 -0
- package/bin/gittensory-miner.js +196 -0
- package/docs/coding-agent-driver.md +140 -0
- package/docs/config-precedence.md +96 -0
- package/docs/cross-repo-discovery-phase1.md +55 -0
- package/docs/discovery-index-contract.md +60 -0
- package/docs/discovery-plane-operator-guide.md +101 -0
- package/docs/env-reference.md +30 -0
- package/docs/fleet-run-manifest.md +50 -0
- package/docs/miner-goal-spec.md +76 -0
- package/docs/miner-selfimprove-calibration.md +105 -0
- package/docs/observability.md +60 -0
- package/docs/operations-runbook.md +209 -0
- package/docs/repo-agnostic-capability-audit.md +129 -0
- package/docs/unattended-scheduling.md +107 -0
- package/expected-engine.version +1 -0
- package/lib/ams-policy.d.ts +20 -0
- package/lib/ams-policy.js +68 -0
- package/lib/attempt-cli.d.ts +99 -0
- package/lib/attempt-cli.js +523 -0
- package/lib/attempt-input-builder.d.ts +24 -0
- package/lib/attempt-input-builder.js +91 -0
- package/lib/attempt-log.d.ts +37 -0
- package/lib/attempt-log.js +177 -0
- package/lib/attempt-runner.d.ts +59 -0
- package/lib/attempt-runner.js +164 -0
- package/lib/attempt-worktree.d.ts +31 -0
- package/lib/attempt-worktree.js +94 -0
- package/lib/calibration-cli.d.ts +1 -0
- package/lib/calibration-cli.js +92 -0
- package/lib/calibration-run.d.ts +161 -0
- package/lib/calibration-run.js +232 -0
- package/lib/calibration-types.d.ts +46 -0
- package/lib/calibration-types.js +74 -0
- package/lib/calibration.d.ts +24 -0
- package/lib/calibration.js +99 -0
- package/lib/ci-poller.d.ts +33 -0
- package/lib/ci-poller.js +234 -0
- package/lib/claim-adjudication.d.ts +21 -0
- package/lib/claim-adjudication.js +36 -0
- package/lib/claim-conflict-resolver.d.ts +29 -0
- package/lib/claim-conflict-resolver.js +101 -0
- package/lib/claim-ledger-cli.d.ts +59 -0
- package/lib/claim-ledger-cli.js +320 -0
- package/lib/claim-ledger-expiry.d.ts +20 -0
- package/lib/claim-ledger-expiry.js +41 -0
- package/lib/claim-ledger.d.ts +63 -0
- package/lib/claim-ledger.js +303 -0
- package/lib/cli-error.d.ts +3 -0
- package/lib/cli-error.js +27 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +72 -0
- package/lib/coding-agent-construction.d.ts +17 -0
- package/lib/coding-agent-construction.js +103 -0
- package/lib/coding-agent-house-rules.d.ts +24 -0
- package/lib/coding-agent-house-rules.js +64 -0
- package/lib/coding-task-spec.d.ts +44 -0
- package/lib/coding-task-spec.js +183 -0
- package/lib/config-precedence.d.ts +25 -0
- package/lib/config-precedence.js +38 -0
- package/lib/deny-check.d.ts +11 -0
- package/lib/deny-check.js +76 -0
- package/lib/deny-hook-synthesis.d.ts +93 -0
- package/lib/deny-hook-synthesis.js +399 -0
- package/lib/deny-hooks.d.ts +28 -0
- package/lib/deny-hooks.js +155 -0
- package/lib/deployment-docs-audit.d.ts +41 -0
- package/lib/deployment-docs-audit.js +115 -0
- package/lib/discover-cli.d.ts +94 -0
- package/lib/discover-cli.js +284 -0
- package/lib/discovery-throttle.d.ts +8 -0
- package/lib/discovery-throttle.js +33 -0
- package/lib/env-file-indirection.d.ts +4 -0
- package/lib/env-file-indirection.js +45 -0
- package/lib/event-ledger-cli.d.ts +82 -0
- package/lib/event-ledger-cli.js +266 -0
- package/lib/event-ledger.d.ts +37 -0
- package/lib/event-ledger.js +210 -0
- package/lib/execute-local-write.d.ts +14 -0
- package/lib/execute-local-write.js +50 -0
- package/lib/feasibility-cli.d.ts +25 -0
- package/lib/feasibility-cli.js +80 -0
- package/lib/forge-config.d.ts +17 -0
- package/lib/forge-config.js +37 -0
- package/lib/governor-action-mode.d.ts +26 -0
- package/lib/governor-action-mode.js +47 -0
- package/lib/governor-chokepoint-persisted.d.ts +18 -0
- package/lib/governor-chokepoint-persisted.js +46 -0
- package/lib/governor-chokepoint.d.ts +14 -0
- package/lib/governor-chokepoint.js +52 -0
- package/lib/governor-kill-switch.d.ts +26 -0
- package/lib/governor-kill-switch.js +47 -0
- package/lib/governor-ledger-cli.d.ts +32 -0
- package/lib/governor-ledger-cli.js +155 -0
- package/lib/governor-ledger.d.ts +46 -0
- package/lib/governor-ledger.js +203 -0
- package/lib/governor-open-pr.d.ts +21 -0
- package/lib/governor-open-pr.js +27 -0
- package/lib/governor-pause-cli.d.ts +23 -0
- package/lib/governor-pause-cli.js +171 -0
- package/lib/governor-run-halt.d.ts +37 -0
- package/lib/governor-run-halt.js +58 -0
- package/lib/governor-state.d.ts +63 -0
- package/lib/governor-state.js +363 -0
- package/lib/governor-write-rate-limit.d.ts +30 -0
- package/lib/governor-write-rate-limit.js +64 -0
- package/lib/harness-submission-trigger.d.ts +69 -0
- package/lib/harness-submission-trigger.js +138 -0
- package/lib/http-retry.d.ts +12 -0
- package/lib/http-retry.js +52 -0
- package/lib/laptop-init.d.ts +53 -0
- package/lib/laptop-init.js +332 -0
- package/lib/live-issue-snapshot.d.ts +16 -0
- package/lib/live-issue-snapshot.js +117 -0
- package/lib/local-store.d.ts +18 -0
- package/lib/local-store.js +62 -0
- package/lib/logger.d.ts +58 -0
- package/lib/logger.js +167 -0
- package/lib/loop-cli.d.ts +66 -0
- package/lib/loop-cli.js +540 -0
- package/lib/loop-closure.d.ts +35 -0
- package/lib/loop-closure.js +66 -0
- package/lib/loop-reentry.d.ts +51 -0
- package/lib/loop-reentry.js +124 -0
- package/lib/manage-poll.d.ts +79 -0
- package/lib/manage-poll.js +254 -0
- package/lib/manage-status.d.ts +70 -0
- package/lib/manage-status.js +241 -0
- package/lib/metrics-cli.d.ts +9 -0
- package/lib/metrics-cli.js +50 -0
- package/lib/migrate-cli.d.ts +24 -0
- package/lib/migrate-cli.js +111 -0
- package/lib/miner-goal-spec.d.ts +12 -0
- package/lib/miner-goal-spec.js +67 -0
- package/lib/opportunity-fanout.d.ts +86 -0
- package/lib/opportunity-fanout.js +597 -0
- package/lib/opportunity-ranker.d.ts +36 -0
- package/lib/opportunity-ranker.js +118 -0
- package/lib/orb-export.d.ts +54 -0
- package/lib/orb-export.js +206 -0
- package/lib/plan-store-cli.d.ts +37 -0
- package/lib/plan-store-cli.js +151 -0
- package/lib/plan-store.d.ts +51 -0
- package/lib/plan-store.js +231 -0
- package/lib/policy-doc-cache.d.ts +25 -0
- package/lib/policy-doc-cache.js +79 -0
- package/lib/policy-verdict-cache.d.ts +35 -0
- package/lib/policy-verdict-cache.js +106 -0
- package/lib/portfolio-dashboard.d.ts +30 -0
- package/lib/portfolio-dashboard.js +105 -0
- package/lib/portfolio-discovery.d.ts +29 -0
- package/lib/portfolio-discovery.js +100 -0
- package/lib/portfolio-queue-cli.d.ts +87 -0
- package/lib/portfolio-queue-cli.js +509 -0
- package/lib/portfolio-queue-expiry.d.ts +20 -0
- package/lib/portfolio-queue-expiry.js +51 -0
- package/lib/portfolio-queue-manager.d.ts +50 -0
- package/lib/portfolio-queue-manager.js +134 -0
- package/lib/portfolio-queue.d.ts +62 -0
- package/lib/portfolio-queue.js +351 -0
- package/lib/pr-disposition-poller.d.ts +26 -0
- package/lib/pr-disposition-poller.js +163 -0
- package/lib/pr-number-parse.d.ts +4 -0
- package/lib/pr-number-parse.js +23 -0
- package/lib/pr-outcome.d.ts +41 -0
- package/lib/pr-outcome.js +90 -0
- package/lib/prediction-ledger.d.ts +47 -0
- package/lib/prediction-ledger.js +222 -0
- package/lib/pretooluse-hook.d.ts +31 -0
- package/lib/pretooluse-hook.js +93 -0
- package/lib/process-lifecycle.d.ts +31 -0
- package/lib/process-lifecycle.js +106 -0
- package/lib/purge-cli.d.ts +44 -0
- package/lib/purge-cli.js +198 -0
- package/lib/rejection-signal.d.ts +6 -0
- package/lib/rejection-signal.js +101 -0
- package/lib/rejection-state-machine.d.ts +37 -0
- package/lib/rejection-state-machine.js +81 -0
- package/lib/rejection-templates.d.ts +12 -0
- package/lib/rejection-templates.js +71 -0
- package/lib/replay-objective-anchor.d.ts +79 -0
- package/lib/replay-objective-anchor.js +179 -0
- package/lib/replay-snapshot.d.ts +41 -0
- package/lib/replay-snapshot.js +288 -0
- package/lib/replay-task-generation.d.ts +126 -0
- package/lib/replay-task-generation.js +223 -0
- package/lib/repo-clone.d.ts +19 -0
- package/lib/repo-clone.js +102 -0
- package/lib/run-state-cli.d.ts +27 -0
- package/lib/run-state-cli.js +154 -0
- package/lib/run-state.d.ts +37 -0
- package/lib/run-state.js +150 -0
- package/lib/schema-version.d.ts +17 -0
- package/lib/schema-version.js +71 -0
- package/lib/self-review-context.d.ts +28 -0
- package/lib/self-review-context.js +339 -0
- package/lib/slop-assessment.d.ts +3 -0
- package/lib/slop-assessment.js +20 -0
- package/lib/stack-detection.d.ts +41 -0
- package/lib/stack-detection.js +248 -0
- package/lib/status.d.ts +64 -0
- package/lib/status.js +441 -0
- package/lib/store-maintenance.d.ts +31 -0
- package/lib/store-maintenance.js +183 -0
- package/lib/submission-freshness-check.d.ts +32 -0
- package/lib/submission-freshness-check.js +93 -0
- package/lib/update-check.d.ts +36 -0
- package/lib/update-check.js +161 -0
- package/lib/version.d.ts +3 -0
- package/lib/version.js +10 -0
- package/lib/worktree-allocator.d.ts +39 -0
- package/lib/worktree-allocator.js +262 -0
- package/package.json +50 -0
- package/schema/miner-goal-spec.schema.json +111 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import { normalizeGovernorLedgerEvent } from "@loopover/engine";
|
|
6
|
+
import { applySchemaMigrations } from "./schema-version.js";
|
|
7
|
+
import {
|
|
8
|
+
GOVERNOR_LEDGER_PURGE_SPEC,
|
|
9
|
+
GOVERNOR_LEDGER_RETENTION_SPEC,
|
|
10
|
+
purgeStoreByRepo,
|
|
11
|
+
pruneLedgerByRetention,
|
|
12
|
+
resolveLedgerRetentionPolicy,
|
|
13
|
+
} from "./store-maintenance.js";
|
|
14
|
+
|
|
15
|
+
// Append-only governor decision ledger (#2328): every allowed/denied/throttled/kill-switch outcome lands in a
|
|
16
|
+
// local SQLite table for contributor audit. IMMUTABILITY INVARIANT: `appendGovernorEvent`/`readGovernorEvents`
|
|
17
|
+
// only ever issue INSERT and SELECT — never UPDATE/DELETE. Two documented exceptions, both separate maintenance
|
|
18
|
+
// operations rather than part of normal ledger operation: opt-in retention pruning (#4834, automatic) and
|
|
19
|
+
// `purgeByRepo` (#5564, always explicit and operator-invoked, never automatic).
|
|
20
|
+
// This module does not enforce governor policy; it only persists structured events other phases will emit.
|
|
21
|
+
|
|
22
|
+
const defaultDbFileName = "governor-ledger.sqlite3";
|
|
23
|
+
let defaultGovernorLedger = null;
|
|
24
|
+
|
|
25
|
+
export function resolveGovernorLedgerDbPath(env = process.env) {
|
|
26
|
+
const explicitPath = typeof env.GITTENSORY_MINER_GOVERNOR_LEDGER_DB === "string"
|
|
27
|
+
? env.GITTENSORY_MINER_GOVERNOR_LEDGER_DB.trim()
|
|
28
|
+
: "";
|
|
29
|
+
if (explicitPath) return explicitPath;
|
|
30
|
+
|
|
31
|
+
const explicitConfigDir = typeof env.GITTENSORY_MINER_CONFIG_DIR === "string"
|
|
32
|
+
? env.GITTENSORY_MINER_CONFIG_DIR.trim()
|
|
33
|
+
: "";
|
|
34
|
+
if (explicitConfigDir) return join(explicitConfigDir, defaultDbFileName);
|
|
35
|
+
|
|
36
|
+
const configHome = typeof env.XDG_CONFIG_HOME === "string" && env.XDG_CONFIG_HOME.trim()
|
|
37
|
+
? env.XDG_CONFIG_HOME.trim()
|
|
38
|
+
: join(homedir(), ".config");
|
|
39
|
+
return join(configHome, "gittensory-miner", defaultDbFileName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeDbPath(dbPath) {
|
|
43
|
+
const path = (dbPath ?? resolveGovernorLedgerDbPath()).trim();
|
|
44
|
+
if (!path) throw new Error("invalid_governor_ledger_db_path");
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeOptionalRepoFullName(repoFullName) {
|
|
49
|
+
if (repoFullName === undefined || repoFullName === null) return undefined;
|
|
50
|
+
if (typeof repoFullName !== "string") throw new Error("invalid_repo_full_name");
|
|
51
|
+
const [owner, repo, extra] = repoFullName.trim().split("/");
|
|
52
|
+
if (!owner || !repo || extra !== undefined) throw new Error("invalid_repo_full_name");
|
|
53
|
+
return `${owner}/${repo}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function rowToEntry(row) {
|
|
57
|
+
let payload;
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(row.payload_json);
|
|
60
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
61
|
+
throw new Error("corrupted_governor_row");
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error("corrupted_governor_row");
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: row.id,
|
|
68
|
+
ts: row.ts,
|
|
69
|
+
eventType: row.event_type,
|
|
70
|
+
repoFullName: row.repo_full_name,
|
|
71
|
+
actionClass: row.action_class,
|
|
72
|
+
decision: row.decision,
|
|
73
|
+
reason: row.reason,
|
|
74
|
+
payload,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Decision-log projection (#5159): the public, MCP-exposed shape. Deliberately omits payload_json (which #5134
|
|
79
|
+
// is expanding with reputation/self-plagiarism/budget state). Kept honest by an explicit named-column SELECT
|
|
80
|
+
// below — never SELECT * — so the sensitive column cannot leak even by accident.
|
|
81
|
+
function rowToDecision(row) {
|
|
82
|
+
return {
|
|
83
|
+
id: row.id,
|
|
84
|
+
ts: row.ts,
|
|
85
|
+
eventType: row.event_type,
|
|
86
|
+
repoFullName: row.repo_full_name,
|
|
87
|
+
actionClass: row.action_class,
|
|
88
|
+
decision: row.decision,
|
|
89
|
+
reason: row.reason,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Opens the append-only governor ledger, creating the table on first use. Rows are returned in ascending `id`
|
|
95
|
+
* order (insertion order). (#2328)
|
|
96
|
+
*/
|
|
97
|
+
export function initGovernorLedger(dbPath = resolveGovernorLedgerDbPath()) {
|
|
98
|
+
const resolvedPath = normalizeDbPath(dbPath);
|
|
99
|
+
mkdirSync(dirname(resolvedPath), { recursive: true, mode: 0o700 });
|
|
100
|
+
const db = new DatabaseSync(resolvedPath);
|
|
101
|
+
chmodSync(resolvedPath, 0o600);
|
|
102
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
103
|
+
db.exec(`
|
|
104
|
+
CREATE TABLE IF NOT EXISTS governor_events (
|
|
105
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
106
|
+
ts TEXT NOT NULL,
|
|
107
|
+
event_type TEXT NOT NULL,
|
|
108
|
+
repo_full_name TEXT,
|
|
109
|
+
action_class TEXT NOT NULL,
|
|
110
|
+
decision TEXT NOT NULL,
|
|
111
|
+
reason TEXT NOT NULL,
|
|
112
|
+
payload_json TEXT NOT NULL
|
|
113
|
+
)
|
|
114
|
+
`);
|
|
115
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_governor_events_repo ON governor_events (repo_full_name, id)");
|
|
116
|
+
// Schema-version convention (#4832): stamp the baseline and run any post-baseline migrations (none yet).
|
|
117
|
+
applySchemaMigrations(db, []);
|
|
118
|
+
// Opt-in retention (#4834): prune aged/excess rows when an operator has enabled it; a no-op by default.
|
|
119
|
+
pruneLedgerByRetention(db, GOVERNOR_LEDGER_RETENTION_SPEC, resolveLedgerRetentionPolicy(), Date.now());
|
|
120
|
+
|
|
121
|
+
const appendStatement = db.prepare(`
|
|
122
|
+
INSERT INTO governor_events (ts, event_type, repo_full_name, action_class, decision, reason, payload_json)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
124
|
+
`);
|
|
125
|
+
const getByIdStatement = db.prepare("SELECT * FROM governor_events WHERE id = ?");
|
|
126
|
+
const readAllStatement = db.prepare("SELECT * FROM governor_events ORDER BY id ASC");
|
|
127
|
+
const readByRepoStatement = db.prepare(
|
|
128
|
+
"SELECT * FROM governor_events WHERE repo_full_name = ? ORDER BY id ASC",
|
|
129
|
+
);
|
|
130
|
+
// Explicit named-column projection for the read-only decision log (#5159) — payload_json is intentionally
|
|
131
|
+
// NOT in this list, so widening it would be a deliberate edit that the redaction test guards against.
|
|
132
|
+
const decisionColumns = "id, ts, event_type, repo_full_name, action_class, decision, reason";
|
|
133
|
+
const readDecisionsAllStatement = db.prepare(
|
|
134
|
+
`SELECT ${decisionColumns} FROM governor_events ORDER BY id ASC`,
|
|
135
|
+
);
|
|
136
|
+
const readDecisionsByRepoStatement = db.prepare(
|
|
137
|
+
`SELECT ${decisionColumns} FROM governor_events WHERE repo_full_name = ? ORDER BY id ASC`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
dbPath: resolvedPath,
|
|
142
|
+
appendGovernorEvent(event) {
|
|
143
|
+
const normalized = normalizeGovernorLedgerEvent(event);
|
|
144
|
+
const ts = new Date().toISOString();
|
|
145
|
+
const result = appendStatement.run(
|
|
146
|
+
ts,
|
|
147
|
+
normalized.eventType,
|
|
148
|
+
normalized.repoFullName,
|
|
149
|
+
normalized.actionClass,
|
|
150
|
+
normalized.decision,
|
|
151
|
+
normalized.reason,
|
|
152
|
+
normalized.payloadJson,
|
|
153
|
+
);
|
|
154
|
+
return rowToEntry(getByIdStatement.get(Number(result.lastInsertRowid)));
|
|
155
|
+
},
|
|
156
|
+
readGovernorEvents(filter = {}) {
|
|
157
|
+
const repoFullName = normalizeOptionalRepoFullName(filter.repoFullName);
|
|
158
|
+
const rows =
|
|
159
|
+
repoFullName === undefined
|
|
160
|
+
? readAllStatement.all()
|
|
161
|
+
: readByRepoStatement.all(repoFullName);
|
|
162
|
+
return rows.map(rowToEntry);
|
|
163
|
+
},
|
|
164
|
+
readGovernorDecisions(filter = {}) {
|
|
165
|
+
const repoFullName = normalizeOptionalRepoFullName(filter.repoFullName);
|
|
166
|
+
const rows =
|
|
167
|
+
repoFullName === undefined
|
|
168
|
+
? readDecisionsAllStatement.all()
|
|
169
|
+
: readDecisionsByRepoStatement.all(repoFullName);
|
|
170
|
+
return rows.map(rowToDecision);
|
|
171
|
+
},
|
|
172
|
+
// Explicit, operator-invoked right-to-be-forgotten purge (#5564) — never runs automatically. See the
|
|
173
|
+
// IMMUTABILITY INVARIANT note above: this is a deliberate, separate exception, not a normal ledger write.
|
|
174
|
+
// Requires a real repoFullName (unlike the optional filters above): a purge must never silently no-op.
|
|
175
|
+
purgeByRepo(repoFullName) {
|
|
176
|
+
const normalized = normalizeOptionalRepoFullName(repoFullName);
|
|
177
|
+
if (normalized === undefined) throw new Error("invalid_repo_full_name");
|
|
178
|
+
return purgeStoreByRepo(db, GOVERNOR_LEDGER_PURGE_SPEC, normalized);
|
|
179
|
+
},
|
|
180
|
+
close() {
|
|
181
|
+
db.close();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getDefaultGovernorLedger() {
|
|
187
|
+
defaultGovernorLedger ??= initGovernorLedger();
|
|
188
|
+
return defaultGovernorLedger;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function appendGovernorEvent(event) {
|
|
192
|
+
return getDefaultGovernorLedger().appendGovernorEvent(event);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function readGovernorEvents(filter) {
|
|
196
|
+
return getDefaultGovernorLedger().readGovernorEvents(filter);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function closeDefaultGovernorLedger() {
|
|
200
|
+
if (!defaultGovernorLedger) return;
|
|
201
|
+
defaultGovernorLedger.close();
|
|
202
|
+
defaultGovernorLedger = null;
|
|
203
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
OwnSubmissionRecord,
|
|
3
|
+
SelfPlagiarismCandidate,
|
|
4
|
+
SelfPlagiarismVerdict,
|
|
5
|
+
} from "@loopover/engine";
|
|
6
|
+
import type { AppendGovernorEventInput, GovernorLedgerEntry } from "./governor-ledger.js";
|
|
7
|
+
|
|
8
|
+
export type EvaluateOpenPrSelfPlagiarismInput = {
|
|
9
|
+
candidate: SelfPlagiarismCandidate;
|
|
10
|
+
recentOwnSubmissions?: readonly OwnSubmissionRecord[];
|
|
11
|
+
selfPlagiarismConfig?: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type EvaluateOpenPrSelfPlagiarismOptions = {
|
|
15
|
+
append?: (event: AppendGovernorEventInput) => GovernorLedgerEntry;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function evaluateOpenPrSelfPlagiarism(
|
|
19
|
+
input: EvaluateOpenPrSelfPlagiarismInput,
|
|
20
|
+
options?: EvaluateOpenPrSelfPlagiarismOptions,
|
|
21
|
+
): { verdict: SelfPlagiarismVerdict; recorded: GovernorLedgerEntry };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Governor open_pr self-plagiarism gate (#2345). Consults the engine's pure selfPlagiarismCheck before an open_pr
|
|
2
|
+
// write is allowed and records throttled/denied outcomes to the append-only governor ledger.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildSelfPlagiarismGovernorLedgerEvent,
|
|
6
|
+
resolveSelfPlagiarismConfig,
|
|
7
|
+
selfPlagiarismCheck,
|
|
8
|
+
} from "@loopover/engine";
|
|
9
|
+
import { appendGovernorEvent } from "./governor-ledger.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run the self-plagiarism throttle for a prospective open_pr and persist the governor decision.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} input
|
|
15
|
+
* @param {import("@loopover/engine").SelfPlagiarismCandidate} input.candidate
|
|
16
|
+
* @param {readonly import("@loopover/engine").OwnSubmissionRecord[]} input.recentOwnSubmissions
|
|
17
|
+
* @param {unknown} [input.selfPlagiarismConfig] parsed `.gittensory-miner.yml` selfPlagiarism block
|
|
18
|
+
* @param {{ append?: typeof appendGovernorEvent }} [options]
|
|
19
|
+
*/
|
|
20
|
+
export function evaluateOpenPrSelfPlagiarism(input, options = {}) {
|
|
21
|
+
const append = options.append ?? appendGovernorEvent;
|
|
22
|
+
const config = resolveSelfPlagiarismConfig(input.selfPlagiarismConfig);
|
|
23
|
+
const verdict = selfPlagiarismCheck(input.candidate, input.recentOwnSubmissions ?? [], config);
|
|
24
|
+
const ledgerEvent = buildSelfPlagiarismGovernorLedgerEvent(input.candidate.repoFullName, verdict);
|
|
25
|
+
const recorded = append(ledgerEvent);
|
|
26
|
+
return { verdict, recorded };
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GovernorState } from "./governor-state.js";
|
|
2
|
+
|
|
3
|
+
export type ParsedGovernorPauseArgs =
|
|
4
|
+
| { json: boolean; dryRun: boolean; reason: string | null }
|
|
5
|
+
| { error: string };
|
|
6
|
+
|
|
7
|
+
export type ParsedGovernorResumeArgs = { json: boolean; dryRun: boolean } | { error: string };
|
|
8
|
+
|
|
9
|
+
export type ParsedGovernorNoArgsSubcommand = { json: boolean } | { error: string };
|
|
10
|
+
|
|
11
|
+
export type GovernorPauseCliOptions = {
|
|
12
|
+
openGovernorState?: () => GovernorState;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function parseGovernorPauseArgs(args: string[]): ParsedGovernorPauseArgs;
|
|
16
|
+
|
|
17
|
+
export function parseGovernorResumeArgs(args: string[]): ParsedGovernorResumeArgs;
|
|
18
|
+
|
|
19
|
+
export function runGovernorPause(args: string[], options?: GovernorPauseCliOptions): Promise<number>;
|
|
20
|
+
|
|
21
|
+
export function runGovernorResume(args: string[], options?: GovernorPauseCliOptions): Promise<number>;
|
|
22
|
+
|
|
23
|
+
export function runGovernorStatus(args: string[], options?: GovernorPauseCliOptions): Promise<number>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// The governor pause/resume control surface (#4851): a real, persisted pause flag an operator (or, in a future
|
|
2
|
+
// wave, the governor itself) can toggle via this CLI, that loop-cli.js's iteration loop actually checks before
|
|
3
|
+
// each cycle. Distinct from governor-kill-switch.js (a read-only resolver over pre-existing env/YAML inputs this
|
|
4
|
+
// package never itself writes) and governor-run-halt.js (a one-way, run-scoped terminal breaker with no resume
|
|
5
|
+
// path) -- this is the first genuinely operator/governor-writable stop/go control. Persisted on governor-state.js's
|
|
6
|
+
// existing single-row scalar-state table, not a new store: a pause flag has no relational key of its own, the
|
|
7
|
+
// same reasoning that table's other scalar fields (rate-limit buckets, cap usage) already rely on.
|
|
8
|
+
|
|
9
|
+
import { openGovernorState } from "./governor-state.js";
|
|
10
|
+
|
|
11
|
+
const GOVERNOR_PAUSE_USAGE = "Usage: gittensory-miner governor pause [--reason <text>] [--dry-run] [--json]";
|
|
12
|
+
const GOVERNOR_RESUME_USAGE = "Usage: gittensory-miner governor resume [--dry-run] [--json]";
|
|
13
|
+
const GOVERNOR_STATUS_USAGE = "Usage: gittensory-miner governor status [--json]";
|
|
14
|
+
|
|
15
|
+
export function parseGovernorPauseArgs(args) {
|
|
16
|
+
const options = { json: false, dryRun: false, reason: null };
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
const token = args[index];
|
|
20
|
+
if (token === "--json") {
|
|
21
|
+
options.json = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// #4847: reports what pausing would do and returns before writing to governor-state.
|
|
25
|
+
if (token === "--dry-run") {
|
|
26
|
+
options.dryRun = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (token === "--reason") {
|
|
30
|
+
const value = args[index + 1];
|
|
31
|
+
if (!value || value.startsWith("-")) return { error: GOVERNOR_PAUSE_USAGE };
|
|
32
|
+
options.reason = value;
|
|
33
|
+
index += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return { error: `Unknown option: ${token}` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return options;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function parseGovernorResumeArgs(args) {
|
|
43
|
+
const options = { json: false, dryRun: false };
|
|
44
|
+
|
|
45
|
+
for (const token of args) {
|
|
46
|
+
if (token === "--json") {
|
|
47
|
+
options.json = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// #4847: reports what resuming would do and returns before writing to governor-state.
|
|
51
|
+
if (token === "--dry-run") {
|
|
52
|
+
options.dryRun = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
return { error: GOVERNOR_RESUME_USAGE };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return options;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseNoArgsSubcommand(args, usage) {
|
|
62
|
+
if (args.length === 0) return { json: false };
|
|
63
|
+
if (args.length === 1 && args[0] === "--json") return { json: true };
|
|
64
|
+
return { error: usage };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function withGovernorState(options, run) {
|
|
68
|
+
const ownsGovernorState = options.openGovernorState === undefined;
|
|
69
|
+
const governorState = (options.openGovernorState ?? openGovernorState)();
|
|
70
|
+
try {
|
|
71
|
+
return run(governorState);
|
|
72
|
+
} finally {
|
|
73
|
+
if (ownsGovernorState) governorState.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderPauseState(pauseState) {
|
|
78
|
+
if (!pauseState.paused) return "governor is not paused";
|
|
79
|
+
const reason = pauseState.reason ? ` (${pauseState.reason})` : "";
|
|
80
|
+
return `governor is PAUSED since ${pauseState.pausedAt}${reason}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function runGovernorPause(args, options = {}) {
|
|
84
|
+
const parsed = parseGovernorPauseArgs(args);
|
|
85
|
+
if ("error" in parsed) {
|
|
86
|
+
console.error(parsed.error);
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (parsed.dryRun) {
|
|
91
|
+
const dryRunResult = { outcome: "dry_run", paused: true, reason: parsed.reason };
|
|
92
|
+
if (parsed.json) {
|
|
93
|
+
console.log(JSON.stringify(dryRunResult));
|
|
94
|
+
} else {
|
|
95
|
+
const reason = parsed.reason ? ` (${parsed.reason})` : "";
|
|
96
|
+
console.log(`DRY RUN: would pause the governor${reason}. No governor-state write was made.`);
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
return await withGovernorState(options, (governorState) => {
|
|
103
|
+
const pauseState = governorState.savePauseState({ paused: true, reason: parsed.reason });
|
|
104
|
+
if (parsed.json) {
|
|
105
|
+
console.log(JSON.stringify(pauseState));
|
|
106
|
+
} else {
|
|
107
|
+
console.log(renderPauseState(pauseState));
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
113
|
+
return 2;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function runGovernorResume(args, options = {}) {
|
|
118
|
+
const parsed = parseGovernorResumeArgs(args);
|
|
119
|
+
if ("error" in parsed) {
|
|
120
|
+
console.error(parsed.error);
|
|
121
|
+
return 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (parsed.dryRun) {
|
|
125
|
+
const dryRunResult = { outcome: "dry_run", paused: false };
|
|
126
|
+
if (parsed.json) {
|
|
127
|
+
console.log(JSON.stringify(dryRunResult));
|
|
128
|
+
} else {
|
|
129
|
+
console.log("DRY RUN: would resume the governor. No governor-state write was made.");
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
return await withGovernorState(options, (governorState) => {
|
|
136
|
+
const pauseState = governorState.savePauseState({ paused: false });
|
|
137
|
+
if (parsed.json) {
|
|
138
|
+
console.log(JSON.stringify(pauseState));
|
|
139
|
+
} else {
|
|
140
|
+
console.log(renderPauseState(pauseState));
|
|
141
|
+
}
|
|
142
|
+
return 0;
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function runGovernorStatus(args, options = {}) {
|
|
151
|
+
const parsed = parseNoArgsSubcommand(args, GOVERNOR_STATUS_USAGE);
|
|
152
|
+
if ("error" in parsed) {
|
|
153
|
+
console.error(parsed.error);
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
return await withGovernorState(options, (governorState) => {
|
|
159
|
+
const pauseState = governorState.loadPauseState();
|
|
160
|
+
if (parsed.json) {
|
|
161
|
+
console.log(JSON.stringify(pauseState));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(renderPauseState(pauseState));
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GovernorCapLimits,
|
|
3
|
+
GovernorCapUsage,
|
|
4
|
+
PortfolioConvergenceInput,
|
|
5
|
+
PortfolioConvergenceThresholds,
|
|
6
|
+
RunLoopHaltVerdict,
|
|
7
|
+
} from "@loopover/engine";
|
|
8
|
+
import type { AppendGovernorEventInput, GovernorLedgerEntry } from "./governor-ledger.js";
|
|
9
|
+
import type { QueueEntry } from "./portfolio-queue.js";
|
|
10
|
+
|
|
11
|
+
export type RunLoopInFlightItem = {
|
|
12
|
+
repoFullName: string;
|
|
13
|
+
identifier: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type EvaluateRunLoopBoundaryGateInput = {
|
|
17
|
+
runHalted?: boolean;
|
|
18
|
+
usage: GovernorCapUsage;
|
|
19
|
+
limits: GovernorCapLimits;
|
|
20
|
+
convergence: PortfolioConvergenceInput;
|
|
21
|
+
convergenceThresholds?: PortfolioConvergenceThresholds;
|
|
22
|
+
inFlightItem?: RunLoopInFlightItem | null;
|
|
23
|
+
markFailed?: (repoFullName: string, identifier: string) => QueueEntry | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type EvaluateRunLoopBoundaryGateResult = {
|
|
27
|
+
verdict: RunLoopHaltVerdict;
|
|
28
|
+
recorded: GovernorLedgerEntry | null;
|
|
29
|
+
runHalted: boolean;
|
|
30
|
+
canClaimNext: boolean;
|
|
31
|
+
releasedItem: QueueEntry | null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function evaluateRunLoopBoundaryGate(
|
|
35
|
+
input: EvaluateRunLoopBoundaryGateInput,
|
|
36
|
+
options?: { append?: (event: AppendGovernorEventInput) => GovernorLedgerEntry },
|
|
37
|
+
): EvaluateRunLoopBoundaryGateResult;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Governor run-loop halt gate (#2347). Consults non-convergence + budget caps at each iteration boundary,
|
|
2
|
+
// releases in-flight portfolio items on a fresh halt, and records the decision to the governor ledger.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildRunLoopHaltGovernorLedgerEvent,
|
|
6
|
+
evaluateRunLoopHalt,
|
|
7
|
+
} from "@loopover/engine";
|
|
8
|
+
import { appendGovernorEvent } from "./governor-ledger.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate run-loop halt signals before claiming the next portfolio item.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} input
|
|
14
|
+
* @param {boolean} [input.runHalted] whether the run is already halted
|
|
15
|
+
* @param {import("@loopover/engine").GovernorCapUsage} input.usage cumulative run usage
|
|
16
|
+
* @param {import("@loopover/engine").GovernorCapLimits} input.limits run ceilings
|
|
17
|
+
* @param {import("@loopover/engine").PortfolioConvergenceInput} input.convergence in-flight item history
|
|
18
|
+
* @param {import("@loopover/engine").PortfolioConvergenceThresholds} [input.convergenceThresholds]
|
|
19
|
+
* @param {{ repoFullName: string, identifier: string } | null | undefined} [input.inFlightItem]
|
|
20
|
+
* @param {(repoFullName: string, identifier: string) => import("./portfolio-queue.js").QueueEntry | null} [input.markFailed]
|
|
21
|
+
* @param {{ append?: typeof appendGovernorEvent }} [options]
|
|
22
|
+
*/
|
|
23
|
+
export function evaluateRunLoopBoundaryGate(input, options = {}) {
|
|
24
|
+
const append = options.append ?? appendGovernorEvent;
|
|
25
|
+
const wasHalted = Boolean(input.runHalted);
|
|
26
|
+
const verdict = evaluateRunLoopHalt({
|
|
27
|
+
runHalted: wasHalted,
|
|
28
|
+
usage: input.usage,
|
|
29
|
+
limits: input.limits,
|
|
30
|
+
convergence: input.convergence,
|
|
31
|
+
convergenceThresholds: input.convergenceThresholds,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const newlyHalted = !wasHalted && verdict.shouldHalt;
|
|
35
|
+
let releasedItem = null;
|
|
36
|
+
if (newlyHalted && input.inFlightItem && typeof input.markFailed === "function") {
|
|
37
|
+
releasedItem = input.markFailed(input.inFlightItem.repoFullName, input.inFlightItem.identifier);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const recorded =
|
|
41
|
+
newlyHalted || (!wasHalted && !verdict.shouldHalt)
|
|
42
|
+
? append(
|
|
43
|
+
buildRunLoopHaltGovernorLedgerEvent(
|
|
44
|
+
input.inFlightItem?.repoFullName ?? null,
|
|
45
|
+
input.inFlightItem?.identifier ?? null,
|
|
46
|
+
verdict,
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
: null;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
verdict,
|
|
53
|
+
recorded,
|
|
54
|
+
runHalted: verdict.shouldHalt,
|
|
55
|
+
canClaimNext: verdict.canClaimNext,
|
|
56
|
+
releasedItem,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { GovernorCapUsage, OwnSubmissionRecord, RepoOutcomeHistory, WriteRateLimitBackoffStore, WriteRateLimitBucketStore } from "@loopover/engine";
|
|
2
|
+
|
|
3
|
+
export type GovernorRateLimitState = {
|
|
4
|
+
buckets: WriteRateLimitBucketStore;
|
|
5
|
+
backoffAttempts: WriteRateLimitBackoffStore;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ListRecentOwnSubmissionsFilter = {
|
|
9
|
+
repoFullName?: string;
|
|
10
|
+
limit?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type GovernorPauseState = {
|
|
14
|
+
paused: boolean;
|
|
15
|
+
reason: string | null;
|
|
16
|
+
pausedAt: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type GovernorPauseInput = {
|
|
20
|
+
paused: boolean;
|
|
21
|
+
reason?: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type GovernorState = {
|
|
25
|
+
dbPath: string;
|
|
26
|
+
loadRateLimitState(): GovernorRateLimitState;
|
|
27
|
+
saveRateLimitState(rateLimitState: GovernorRateLimitState): void;
|
|
28
|
+
loadCapUsage(): GovernorCapUsage;
|
|
29
|
+
saveCapUsage(capUsage: GovernorCapUsage): void;
|
|
30
|
+
loadPauseState(): GovernorPauseState;
|
|
31
|
+
savePauseState(pauseState: GovernorPauseInput): GovernorPauseState;
|
|
32
|
+
loadReputationHistory(repoFullName: string, apiBaseUrl?: string): RepoOutcomeHistory;
|
|
33
|
+
saveReputationHistory(repoFullName: string, history: RepoOutcomeHistory, apiBaseUrl?: string): RepoOutcomeHistory;
|
|
34
|
+
recordOwnSubmission(record: OwnSubmissionRecord): OwnSubmissionRecord;
|
|
35
|
+
listRecentOwnSubmissions(filter?: ListRecentOwnSubmissionsFilter): OwnSubmissionRecord[];
|
|
36
|
+
close(): void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function resolveGovernorStateDbPath(env?: Record<string, string | undefined>): string;
|
|
40
|
+
|
|
41
|
+
export function openGovernorState(dbPath?: string): GovernorState;
|
|
42
|
+
|
|
43
|
+
export function loadRateLimitState(): GovernorRateLimitState;
|
|
44
|
+
|
|
45
|
+
export function saveRateLimitState(rateLimitState: GovernorRateLimitState): void;
|
|
46
|
+
|
|
47
|
+
export function loadCapUsage(): GovernorCapUsage;
|
|
48
|
+
|
|
49
|
+
export function saveCapUsage(capUsage: GovernorCapUsage): void;
|
|
50
|
+
|
|
51
|
+
export function loadPauseState(): GovernorPauseState;
|
|
52
|
+
|
|
53
|
+
export function savePauseState(pauseState: GovernorPauseInput): GovernorPauseState;
|
|
54
|
+
|
|
55
|
+
export function loadReputationHistory(repoFullName: string, apiBaseUrl?: string): RepoOutcomeHistory;
|
|
56
|
+
|
|
57
|
+
export function saveReputationHistory(repoFullName: string, history: RepoOutcomeHistory, apiBaseUrl?: string): RepoOutcomeHistory;
|
|
58
|
+
|
|
59
|
+
export function recordOwnSubmission(record: OwnSubmissionRecord): OwnSubmissionRecord;
|
|
60
|
+
|
|
61
|
+
export function listRecentOwnSubmissions(filter?: ListRecentOwnSubmissionsFilter): OwnSubmissionRecord[];
|
|
62
|
+
|
|
63
|
+
export function closeDefaultGovernorState(): void;
|