@code-partner/codepipe-hub 0.14.1-dev.420.g57ff103d → 0.14.1-dev.423.ga49d1f61
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/dashboard/dist/assets/{ModelCanvas-WXJ1hqXD.js → ModelCanvas-CKATjkFe.js} +1 -1
- package/dashboard/dist/assets/{index-K3ia3VFf.js → index-D5-hoCRk.js} +72 -72
- package/dashboard/dist/index.html +1 -1
- package/hub/dist/embedded.js +720 -220
- package/hub/dist/index.js +1136 -919
- package/package.json +1 -1
package/hub/dist/embedded.js
CHANGED
|
@@ -16,6 +16,15 @@ function humanSubjectOf(actor) {
|
|
|
16
16
|
return actor.principal.delegatedBy ?? null;
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
+
function accountOf(actor) {
|
|
20
|
+
const subjectId = humanSubjectOf(actor);
|
|
21
|
+
if (subjectId === null)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
subjectId,
|
|
25
|
+
email: actor.principal.kind === "human" && actor.displayEmail !== void 0 ? actor.displayEmail : null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
19
28
|
function serviceOf(actor) {
|
|
20
29
|
return actor.principal.kind === "service" ? actor.principal.service : null;
|
|
21
30
|
}
|
|
@@ -448,7 +457,7 @@ var PLATFORM_VERSION;
|
|
|
448
457
|
var init_version_generated = __esm({
|
|
449
458
|
"../packages/shared/dist/version.generated.js"() {
|
|
450
459
|
"use strict";
|
|
451
|
-
PLATFORM_VERSION = "0.14.1-dev.
|
|
460
|
+
PLATFORM_VERSION = "0.14.1-dev.423.ga49d1f61";
|
|
452
461
|
}
|
|
453
462
|
});
|
|
454
463
|
|
|
@@ -458,7 +467,7 @@ var init_version = __esm({
|
|
|
458
467
|
"../packages/shared/dist/version.js"() {
|
|
459
468
|
"use strict";
|
|
460
469
|
init_version_generated();
|
|
461
|
-
PROTOCOL_VERSION =
|
|
470
|
+
PROTOCOL_VERSION = 5;
|
|
462
471
|
}
|
|
463
472
|
});
|
|
464
473
|
|
|
@@ -1227,7 +1236,7 @@ function parseRepoMirror(raw) {
|
|
|
1227
1236
|
function isCredKind(value) {
|
|
1228
1237
|
return typeof value === "string" && CRED_KINDS.includes(value);
|
|
1229
1238
|
}
|
|
1230
|
-
var DEFAULT_BRANCH_PATTERN, BRANCH_SLUG_MAX_LENGTH, CYRILLIC_TRANSLIT, CRED_KINDS;
|
|
1239
|
+
var DEFAULT_BRANCH_PATTERN, BRANCH_SLUG_MAX_LENGTH, CYRILLIC_TRANSLIT, CRED_KINDS, PROJECT_ID_RE;
|
|
1231
1240
|
var init_project = __esm({
|
|
1232
1241
|
"../packages/shared/dist/project.js"() {
|
|
1233
1242
|
"use strict";
|
|
@@ -1269,6 +1278,7 @@ var init_project = __esm({
|
|
|
1269
1278
|
\u044F: "ya"
|
|
1270
1279
|
};
|
|
1271
1280
|
CRED_KINDS = ["github", "gitlab", "bitbucket", "youtrack", "contextWrite"];
|
|
1281
|
+
PROJECT_ID_RE = /^[a-z][a-z0-9-]{1,30}$/;
|
|
1272
1282
|
}
|
|
1273
1283
|
});
|
|
1274
1284
|
|
|
@@ -4231,16 +4241,40 @@ function createReadOperations(port, authorizer) {
|
|
|
4231
4241
|
return { projectId, items: rows, revision: valueRevision(rows) };
|
|
4232
4242
|
}
|
|
4233
4243
|
function projectVisibilityOf(actor) {
|
|
4234
|
-
const
|
|
4235
|
-
if (
|
|
4244
|
+
const account = accountOf(actor);
|
|
4245
|
+
if (account === null)
|
|
4236
4246
|
return null;
|
|
4237
4247
|
return {
|
|
4238
|
-
subjectId,
|
|
4239
|
-
...
|
|
4248
|
+
subjectId: account.subjectId,
|
|
4249
|
+
...account.email === null ? {} : { email: account.email },
|
|
4240
4250
|
...actor.projectScope === void 0 ? {} : { projectScope: actor.projectScope },
|
|
4241
4251
|
includeUnowned: actor.authMethod !== "cli_token"
|
|
4242
4252
|
};
|
|
4243
4253
|
}
|
|
4254
|
+
function farmsView(rows) {
|
|
4255
|
+
return {
|
|
4256
|
+
items: rows.map((r) => ({
|
|
4257
|
+
id: r.farmId,
|
|
4258
|
+
name: r.name,
|
|
4259
|
+
publicKey: r.publicKey,
|
|
4260
|
+
keyFingerprint: r.keyFingerprint,
|
|
4261
|
+
online: r.online,
|
|
4262
|
+
lastSeenAt: toOptionalTimestamp(r.lastSeenAt)
|
|
4263
|
+
}))
|
|
4264
|
+
};
|
|
4265
|
+
}
|
|
4266
|
+
function projectDeliverablesView(projectId, rows) {
|
|
4267
|
+
return {
|
|
4268
|
+
projectId,
|
|
4269
|
+
items: rows.map((r) => ({
|
|
4270
|
+
taskKey: r.taskKey,
|
|
4271
|
+
title: r.title,
|
|
4272
|
+
phase: r.phase,
|
|
4273
|
+
updatedAt: toTimestamp(r.updatedAt),
|
|
4274
|
+
repos: r.repos.map((x) => ({ repoName: x.repoName, branchName: x.branchName, baseRef: x.baseRef }))
|
|
4275
|
+
}))
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4244
4278
|
async function visibleProjects(actor) {
|
|
4245
4279
|
const visibility = projectVisibilityOf(actor);
|
|
4246
4280
|
return visibility === null ? [] : await port.visibleProjectIds(visibility);
|
|
@@ -4709,6 +4743,15 @@ function createReadOperations(port, authorizer) {
|
|
|
4709
4743
|
const rows = await port.listRepos(query.projectId);
|
|
4710
4744
|
return reposView(query.projectId, rows);
|
|
4711
4745
|
},
|
|
4746
|
+
async listProjectDeliverables(actor, query) {
|
|
4747
|
+
await require2(actor, "delivery.read", { type: "deliverable", projectId: query.projectId });
|
|
4748
|
+
return projectDeliverablesView(query.projectId, await port.listProjectDeliverables(query.projectId));
|
|
4749
|
+
},
|
|
4750
|
+
async listFarms(actor) {
|
|
4751
|
+
await require2(actor, "account.read", { type: "account" });
|
|
4752
|
+
const account = accountOf(actor);
|
|
4753
|
+
return farmsView(account === null ? [] : await port.listFarms(account));
|
|
4754
|
+
},
|
|
4712
4755
|
async listConfigProposals(actor, query) {
|
|
4713
4756
|
await require2(actor, "project.read", { type: "project", id: query.projectId, projectId: query.projectId });
|
|
4714
4757
|
const limit = normalizeLimit(query.limit);
|
|
@@ -4938,8 +4981,18 @@ function createCommandOperations(deps) {
|
|
|
4938
4981
|
});
|
|
4939
4982
|
case "unprocessable":
|
|
4940
4983
|
throw errors.unprocessable({ details: { reason: outcome.reason } });
|
|
4984
|
+
case "not_found":
|
|
4985
|
+
throw errors.notFound();
|
|
4986
|
+
case "rate_limited":
|
|
4987
|
+
throw errors.rateLimited();
|
|
4941
4988
|
}
|
|
4942
4989
|
}
|
|
4990
|
+
function accountActorOf(actor) {
|
|
4991
|
+
const account = accountOf(actor);
|
|
4992
|
+
if (account === null)
|
|
4993
|
+
throw errors.forbidden({ details: { reason: "human_only" } });
|
|
4994
|
+
return account;
|
|
4995
|
+
}
|
|
4943
4996
|
async function audited(actor, entry, run) {
|
|
4944
4997
|
const policy = { decision: "not_evaluated" };
|
|
4945
4998
|
const common = {
|
|
@@ -5428,6 +5481,115 @@ function createCommandOperations(deps) {
|
|
|
5428
5481
|
return { value: outcome.result, replayed: outcome.replayed };
|
|
5429
5482
|
});
|
|
5430
5483
|
},
|
|
5484
|
+
async createProject(actor, command) {
|
|
5485
|
+
const { project } = command;
|
|
5486
|
+
const projectId = project.projectId;
|
|
5487
|
+
if (!PROJECT_ID_RE.test(projectId)) {
|
|
5488
|
+
throw errors.invalidInput({
|
|
5489
|
+
message: "A project id is lowercase letters, digits and dashes, starts with a letter, 2\u201331 characters.",
|
|
5490
|
+
details: { field: "projectId" }
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
for (const repo of project.repos) {
|
|
5494
|
+
if (repo.defaultBranch !== void 0 && !isValidBranchName(repo.defaultBranch)) {
|
|
5495
|
+
throw errors.invalidInput({ message: "That is not a valid git branch name.", details: { field: "repos.defaultBranch", repo: repo.name } });
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
const key = asIdempotencyKey(command.idempotencyKey);
|
|
5499
|
+
const scope = { subjectId: actor.principal.subjectId, operationId: "project.create", projectId, key };
|
|
5500
|
+
return await audited(actor, { operationId: "project.create", aggregateType: "project", aggregateId: projectId, projectId, data: { name: project.name, tracker: project.trackerProvider } }, async (policy) => {
|
|
5501
|
+
await require2(actor, "account.manage", { type: "account" }, policy);
|
|
5502
|
+
const run = await runIdempotent(idempotency, scope, { project }, async () => unwrap(await write2.createProject({ project, owner: accountActorOf(actor) })), now());
|
|
5503
|
+
return { value: run.result, replayed: run.replayed };
|
|
5504
|
+
});
|
|
5505
|
+
},
|
|
5506
|
+
async provisionOnFarm(actor, command) {
|
|
5507
|
+
const { farmId, projectId } = command;
|
|
5508
|
+
const key = asIdempotencyKey(command.idempotencyKey);
|
|
5509
|
+
const scope = { subjectId: actor.principal.subjectId, operationId: "farm.provision", projectId, key };
|
|
5510
|
+
return await audited(actor, { operationId: "farm.provision", aggregateType: "project", aggregateId: projectId, projectId, data: { farmId } }, async (policy) => {
|
|
5511
|
+
await require2(actor, "project.manage", { type: "project", id: projectId, projectId }, policy);
|
|
5512
|
+
const run = await runIdempotent(idempotency, scope, { farmId, projectId }, async () => {
|
|
5513
|
+
const row = unwrap(await write2.provisionOnFarm({ farmId, projectId, sealedBundle: command.sealedBundle, actor: accountActorOf(actor) }));
|
|
5514
|
+
return { projectId, farmId, ok: row.ok, daemonStatus: row.daemonStatus, error: row.error };
|
|
5515
|
+
}, now());
|
|
5516
|
+
return { value: run.result, replayed: run.replayed };
|
|
5517
|
+
});
|
|
5518
|
+
},
|
|
5519
|
+
async provisionGhRunners(actor, command) {
|
|
5520
|
+
const { runnerId, projectId } = command;
|
|
5521
|
+
const key = asIdempotencyKey(command.idempotencyKey);
|
|
5522
|
+
const scope = { subjectId: actor.principal.subjectId, operationId: "runner.provisionGhRunners", projectId, key };
|
|
5523
|
+
return await audited(actor, { operationId: "runner.provisionGhRunners", aggregateType: "project", aggregateId: projectId, projectId, data: { runnerId } }, async (policy) => {
|
|
5524
|
+
await require2(actor, "project.manage", { type: "project", id: projectId, projectId }, policy);
|
|
5525
|
+
const run = await runIdempotent(idempotency, scope, { runnerId, projectId }, async () => {
|
|
5526
|
+
const row = unwrap(await write2.provisionGhRunners({ runnerId, projectId, sealedBundle: command.sealedBundle, actor: accountActorOf(actor) }));
|
|
5527
|
+
return {
|
|
5528
|
+
projectId,
|
|
5529
|
+
runnerId,
|
|
5530
|
+
ok: row.ok,
|
|
5531
|
+
runners: row.runners.map((r) => ({ owner: r.owner, repo: r.repo, status: r.status, error: r.error, labels: r.labels })),
|
|
5532
|
+
error: row.error
|
|
5533
|
+
};
|
|
5534
|
+
}, now());
|
|
5535
|
+
return { value: run.result, replayed: run.replayed };
|
|
5536
|
+
});
|
|
5537
|
+
},
|
|
5538
|
+
async teardownGhRunners(actor, command) {
|
|
5539
|
+
const { runnerId, projectId } = command;
|
|
5540
|
+
const key = asIdempotencyKey(command.idempotencyKey);
|
|
5541
|
+
const scope = { subjectId: actor.principal.subjectId, operationId: "runner.teardownGhRunners", projectId, key };
|
|
5542
|
+
const repo = command.repo ?? null;
|
|
5543
|
+
return await audited(actor, {
|
|
5544
|
+
operationId: "runner.teardownGhRunners",
|
|
5545
|
+
aggregateType: "project",
|
|
5546
|
+
aggregateId: projectId,
|
|
5547
|
+
projectId,
|
|
5548
|
+
data: { runnerId, ...repo === null ? {} : { repo: `${repo.owner}/${repo.repo}` } }
|
|
5549
|
+
}, async (policy) => {
|
|
5550
|
+
await require2(actor, "project.manage", { type: "project", id: projectId, projectId }, policy);
|
|
5551
|
+
const run = await runIdempotent(idempotency, scope, { runnerId, projectId, repo }, async () => {
|
|
5552
|
+
const row = unwrap(await write2.teardownGhRunners({
|
|
5553
|
+
runnerId,
|
|
5554
|
+
projectId,
|
|
5555
|
+
sealedBundle: command.sealedBundle ?? null,
|
|
5556
|
+
repo,
|
|
5557
|
+
actor: accountActorOf(actor)
|
|
5558
|
+
}));
|
|
5559
|
+
return { projectId, runnerId, ok: row.ok, error: row.error };
|
|
5560
|
+
}, now());
|
|
5561
|
+
return { value: run.result, replayed: run.replayed };
|
|
5562
|
+
});
|
|
5563
|
+
},
|
|
5564
|
+
async requestProjectIndex(actor, command) {
|
|
5565
|
+
const { projectId, kind } = command;
|
|
5566
|
+
const key = asIdempotencyKey(command.idempotencyKey);
|
|
5567
|
+
const scope = { subjectId: actor.principal.subjectId, operationId: "project.index", projectId, key };
|
|
5568
|
+
return await audited(actor, { operationId: "project.index", aggregateType: "project", aggregateId: projectId, projectId, data: { kind } }, async (policy) => {
|
|
5569
|
+
await require2(actor, "project.manage", { type: "project", id: projectId, projectId }, policy);
|
|
5570
|
+
const run = await runIdempotent(idempotency, scope, { kind }, async () => {
|
|
5571
|
+
const { jobId } = unwrap(await write2.enqueueProjectIndex({ projectId, kind }));
|
|
5572
|
+
return { jobId, kind };
|
|
5573
|
+
}, now());
|
|
5574
|
+
return { value: run.result, replayed: run.replayed };
|
|
5575
|
+
});
|
|
5576
|
+
},
|
|
5577
|
+
async issueWorkerRegistrationToken(actor) {
|
|
5578
|
+
const subjectId = actor.principal.subjectId;
|
|
5579
|
+
return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
|
|
5580
|
+
await require2(actor, "account.manage", { type: "account" }, policy);
|
|
5581
|
+
const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
|
|
5582
|
+
return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
|
|
5583
|
+
});
|
|
5584
|
+
},
|
|
5585
|
+
async reportInitStatus(actor, command) {
|
|
5586
|
+
const { projectId, phase } = command;
|
|
5587
|
+
return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
|
|
5588
|
+
await require2(actor, "project.manage", { type: "project", id: projectId, projectId }, policy);
|
|
5589
|
+
const { recorded } = unwrap(await write2.reportInitStatus({ projectId, phase, error: command.error ?? null }));
|
|
5590
|
+
return { value: { recorded }, replayed: false };
|
|
5591
|
+
});
|
|
5592
|
+
},
|
|
5431
5593
|
async setRepoDefaultBranch(actor, command) {
|
|
5432
5594
|
const { projectId, name } = command;
|
|
5433
5595
|
const revision = asRevision(command.revision);
|
|
@@ -8623,14 +8785,14 @@ function buildQueueDag(tasks, isSatisfiedOutside = () => false) {
|
|
|
8623
8785
|
function stableTopoOrder(currentOrder, dag) {
|
|
8624
8786
|
const sortable = currentOrder.filter((k) => dag.before.has(k) && !dag.cycleKeys.has(k));
|
|
8625
8787
|
const index = new Map(sortable.map((k, i) => [k, i]));
|
|
8626
|
-
const
|
|
8788
|
+
const pending5 = /* @__PURE__ */ new Map();
|
|
8627
8789
|
const dependents = /* @__PURE__ */ new Map();
|
|
8628
8790
|
for (const key of sortable) {
|
|
8629
8791
|
const preds = [...dag.before.get(key) ?? []].filter(
|
|
8630
8792
|
(p) => index.has(p)
|
|
8631
8793
|
// cycle keys / out-of-order keys don't gate the sort
|
|
8632
8794
|
);
|
|
8633
|
-
|
|
8795
|
+
pending5.set(key, preds.length);
|
|
8634
8796
|
for (const p of preds) {
|
|
8635
8797
|
const d = dependents.get(p) ?? [];
|
|
8636
8798
|
d.push(key);
|
|
@@ -8638,7 +8800,7 @@ function stableTopoOrder(currentOrder, dag) {
|
|
|
8638
8800
|
}
|
|
8639
8801
|
}
|
|
8640
8802
|
const ready = /* @__PURE__ */ new Set();
|
|
8641
|
-
for (const [key, deg] of
|
|
8803
|
+
for (const [key, deg] of pending5) if (deg === 0) ready.add(key);
|
|
8642
8804
|
const out = [];
|
|
8643
8805
|
while (ready.size > 0) {
|
|
8644
8806
|
let best = null;
|
|
@@ -8648,8 +8810,8 @@ function stableTopoOrder(currentOrder, dag) {
|
|
|
8648
8810
|
ready.delete(best);
|
|
8649
8811
|
out.push(best);
|
|
8650
8812
|
for (const dep of dependents.get(best) ?? []) {
|
|
8651
|
-
const deg =
|
|
8652
|
-
|
|
8813
|
+
const deg = pending5.get(dep) - 1;
|
|
8814
|
+
pending5.set(dep, deg);
|
|
8653
8815
|
if (deg === 0) ready.add(dep);
|
|
8654
8816
|
}
|
|
8655
8817
|
}
|
|
@@ -10331,8 +10493,8 @@ async function saveDeliveries(db, taskId2, sourceJobId, deliveries) {
|
|
|
10331
10493
|
if (deliveries.length === 0) return;
|
|
10332
10494
|
const now = Date.now();
|
|
10333
10495
|
for (const d of deliveries) {
|
|
10334
|
-
const
|
|
10335
|
-
const unpublished =
|
|
10496
|
+
const pending5 = pendingRelayRevision(d);
|
|
10497
|
+
const unpublished = pending5 !== null;
|
|
10336
10498
|
await db.run(
|
|
10337
10499
|
`INSERT INTO task_deliveries
|
|
10338
10500
|
(task_id, repo_name, delivery_ref, delivery_sha, base_sha, changed_files_json, source_job_id, created_at,
|
|
@@ -10355,7 +10517,7 @@ async function saveDeliveries(db, taskId2, sourceJobId, deliveries) {
|
|
|
10355
10517
|
JSON.stringify(d.changedFiles ?? []),
|
|
10356
10518
|
sourceJobId,
|
|
10357
10519
|
now,
|
|
10358
|
-
|
|
10520
|
+
pending5 && pending5 !== "unusable" ? JSON.stringify(pending5) : null,
|
|
10359
10521
|
unpublished ? null : now
|
|
10360
10522
|
);
|
|
10361
10523
|
}
|
|
@@ -10820,7 +10982,20 @@ var init_auth = __esm({
|
|
|
10820
10982
|
});
|
|
10821
10983
|
|
|
10822
10984
|
// src/sandbox-reg-tokens.ts
|
|
10985
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10823
10986
|
import { nanoid as nanoid7 } from "nanoid";
|
|
10987
|
+
function hashSandboxRegToken(secret) {
|
|
10988
|
+
return createHash2("sha256").update(secret, "utf8").digest("hex");
|
|
10989
|
+
}
|
|
10990
|
+
async function issueEphemeralSandboxRegToken(db, email, ttlMs = EPHEMERAL_REG_TOKEN_TTL_MS) {
|
|
10991
|
+
const normalized = email.trim().toLowerCase();
|
|
10992
|
+
const secret = `sbxreg_${nanoid7(48)}`;
|
|
10993
|
+
const now = Date.now();
|
|
10994
|
+
const expiresAt = now + ttlMs;
|
|
10995
|
+
await db.run(`INSERT INTO sandbox_reg_tokens (token_hash, email, user_id, created_at, expires_at)
|
|
10996
|
+
VALUES (?, ?, (SELECT id FROM users WHERE email = ?), ?, ?)`, hashSandboxRegToken(secret), normalized, normalized, now, expiresAt);
|
|
10997
|
+
return { secret, expiresAt };
|
|
10998
|
+
}
|
|
10824
10999
|
var EPHEMERAL_REG_TOKEN_TTL_MS;
|
|
10825
11000
|
var init_sandbox_reg_tokens = __esm({
|
|
10826
11001
|
"src/sandbox-reg-tokens.ts"() {
|
|
@@ -10886,6 +11061,9 @@ async function getInitStatus(db, projectId) {
|
|
|
10886
11061
|
async function write(db, projectId, status) {
|
|
10887
11062
|
await db.run(`UPDATE projects SET init_status_json = ? WHERE id = ?`, JSON.stringify(status), projectId);
|
|
10888
11063
|
}
|
|
11064
|
+
async function beginInitTracking(db, projectId, phase) {
|
|
11065
|
+
await write(db, projectId, { phase, updatedAt: Date.now() });
|
|
11066
|
+
}
|
|
10889
11067
|
async function advanceInitPhase(db, projectId, phase) {
|
|
10890
11068
|
const cur = await getInitStatus(db, projectId);
|
|
10891
11069
|
if (!cur) return false;
|
|
@@ -11113,6 +11291,25 @@ var init_state_machine2 = __esm({
|
|
|
11113
11291
|
});
|
|
11114
11292
|
|
|
11115
11293
|
// src/abuse-throttle.ts
|
|
11294
|
+
function createAbuseThrottle(opts) {
|
|
11295
|
+
const hits = /* @__PURE__ */ new Map();
|
|
11296
|
+
return {
|
|
11297
|
+
take(key) {
|
|
11298
|
+
const now = Date.now();
|
|
11299
|
+
const entry = hits.get(key);
|
|
11300
|
+
if (!entry || entry.resetAt <= now) {
|
|
11301
|
+
hits.set(key, { count: 1, resetAt: now + opts.windowMs });
|
|
11302
|
+
return true;
|
|
11303
|
+
}
|
|
11304
|
+
if (entry.count >= opts.max) return false;
|
|
11305
|
+
entry.count += 1;
|
|
11306
|
+
return true;
|
|
11307
|
+
},
|
|
11308
|
+
reset() {
|
|
11309
|
+
hits.clear();
|
|
11310
|
+
}
|
|
11311
|
+
};
|
|
11312
|
+
}
|
|
11116
11313
|
var init_abuse_throttle = __esm({
|
|
11117
11314
|
"src/abuse-throttle.ts"() {
|
|
11118
11315
|
"use strict";
|
|
@@ -11143,16 +11340,104 @@ var init_sandbox2 = __esm({
|
|
|
11143
11340
|
init_sealedbox();
|
|
11144
11341
|
init_abuse_throttle();
|
|
11145
11342
|
init_auth();
|
|
11146
|
-
init_cli_auth();
|
|
11147
11343
|
init_sandbox_reg_tokens();
|
|
11148
11344
|
init_users();
|
|
11149
11345
|
init_agent_auth_probe();
|
|
11150
11346
|
init_ws();
|
|
11347
|
+
init_route_registry();
|
|
11151
11348
|
}
|
|
11152
11349
|
});
|
|
11153
11350
|
|
|
11154
|
-
// src/farm
|
|
11351
|
+
// src/routes/farm.ts
|
|
11155
11352
|
import { nanoid as nanoid11 } from "nanoid";
|
|
11353
|
+
function viewerInAssignments(viewer, assignments) {
|
|
11354
|
+
return assignments.some(
|
|
11355
|
+
(a) => viewer.userId !== null && a.userId === viewer.userId || a.email !== null && a.email === viewer.email
|
|
11356
|
+
);
|
|
11357
|
+
}
|
|
11358
|
+
async function viewerOwnsFarm(db, viewer, farmId) {
|
|
11359
|
+
const row = await db.get(
|
|
11360
|
+
`SELECT 1 FROM farm_assignments
|
|
11361
|
+
WHERE farm_id = ? AND (user_id = ? OR email = ?) LIMIT 1`,
|
|
11362
|
+
farmId,
|
|
11363
|
+
viewer.userId,
|
|
11364
|
+
viewer.email
|
|
11365
|
+
);
|
|
11366
|
+
return !!row;
|
|
11367
|
+
}
|
|
11368
|
+
async function listFarms(db, viewer) {
|
|
11369
|
+
const online = new Set(listConnectedFarmIds());
|
|
11370
|
+
const rows = await db.all(`SELECT f.id, f.name, f.public_key as publicKey,
|
|
11371
|
+
f.key_fingerprint as keyFingerprint, f.last_seen_at as lastSeenAt,
|
|
11372
|
+
a.user_id as aUserId, a.email as aEmail
|
|
11373
|
+
FROM farms f LEFT JOIN farm_assignments a ON a.farm_id = f.id
|
|
11374
|
+
ORDER BY f.registered_at ASC`);
|
|
11375
|
+
const byId = /* @__PURE__ */ new Map();
|
|
11376
|
+
for (const r of rows) {
|
|
11377
|
+
let farm = byId.get(r.id);
|
|
11378
|
+
if (!farm) {
|
|
11379
|
+
farm = {
|
|
11380
|
+
farmId: r.id,
|
|
11381
|
+
name: r.name,
|
|
11382
|
+
publicKey: r.publicKey,
|
|
11383
|
+
keyFingerprint: r.keyFingerprint,
|
|
11384
|
+
online: online.has(r.id),
|
|
11385
|
+
lastSeenAt: r.lastSeenAt,
|
|
11386
|
+
assignments: []
|
|
11387
|
+
};
|
|
11388
|
+
byId.set(r.id, farm);
|
|
11389
|
+
}
|
|
11390
|
+
if (r.aUserId !== null || r.aEmail !== null) {
|
|
11391
|
+
farm.assignments.push({ userId: r.aUserId, email: r.aEmail });
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
return [...byId.values()].filter((f) => !viewer || viewer.isSuperAdmin || viewerInAssignments(viewer, f.assignments)).map(({ assignments: _assignments, ...f }) => f);
|
|
11395
|
+
}
|
|
11396
|
+
var init_farm2 = __esm({
|
|
11397
|
+
"src/routes/farm.ts"() {
|
|
11398
|
+
"use strict";
|
|
11399
|
+
init_sealedbox();
|
|
11400
|
+
init_abuse_throttle();
|
|
11401
|
+
init_ws();
|
|
11402
|
+
init_route_registry();
|
|
11403
|
+
}
|
|
11404
|
+
});
|
|
11405
|
+
|
|
11406
|
+
// src/runner-reg-tokens.ts
|
|
11407
|
+
import { nanoid as nanoid12 } from "nanoid";
|
|
11408
|
+
var init_runner_reg_tokens = __esm({
|
|
11409
|
+
"src/runner-reg-tokens.ts"() {
|
|
11410
|
+
"use strict";
|
|
11411
|
+
}
|
|
11412
|
+
});
|
|
11413
|
+
|
|
11414
|
+
// src/routes/runner.ts
|
|
11415
|
+
import { nanoid as nanoid13 } from "nanoid";
|
|
11416
|
+
async function viewerOwnsRunner(db, viewer, runnerId) {
|
|
11417
|
+
const row = await db.get(
|
|
11418
|
+
`SELECT 1 FROM runners WHERE id = ? AND (owner_user_id = ? OR owner_email = ?) LIMIT 1`,
|
|
11419
|
+
runnerId,
|
|
11420
|
+
viewer.userId,
|
|
11421
|
+
viewer.email
|
|
11422
|
+
);
|
|
11423
|
+
return !!row;
|
|
11424
|
+
}
|
|
11425
|
+
var init_runner2 = __esm({
|
|
11426
|
+
"src/routes/runner.ts"() {
|
|
11427
|
+
"use strict";
|
|
11428
|
+
init_sealedbox();
|
|
11429
|
+
init_abuse_throttle();
|
|
11430
|
+
init_cli_auth();
|
|
11431
|
+
init_auth();
|
|
11432
|
+
init_users();
|
|
11433
|
+
init_runner_reg_tokens();
|
|
11434
|
+
init_ws();
|
|
11435
|
+
init_route_registry();
|
|
11436
|
+
}
|
|
11437
|
+
});
|
|
11438
|
+
|
|
11439
|
+
// src/farm/pool-spec.ts
|
|
11440
|
+
import { nanoid as nanoid14 } from "nanoid";
|
|
11156
11441
|
async function computeFarmPoolSpec(db, farmId) {
|
|
11157
11442
|
const rows = await db.all(
|
|
11158
11443
|
`SELECT fa.user_id as userId, fa.max_sandboxes as quota,
|
|
@@ -11184,7 +11469,7 @@ async function computeFarmPoolSpec(db, farmId) {
|
|
|
11184
11469
|
return spec;
|
|
11185
11470
|
}
|
|
11186
11471
|
function buildSyncWorkerPoolMessage(accounts) {
|
|
11187
|
-
return { type: "sync_worker_pool", requestId:
|
|
11472
|
+
return { type: "sync_worker_pool", requestId: nanoid14(16), accounts };
|
|
11188
11473
|
}
|
|
11189
11474
|
var init_pool_spec = __esm({
|
|
11190
11475
|
"src/farm/pool-spec.ts"() {
|
|
@@ -11192,127 +11477,6 @@ var init_pool_spec = __esm({
|
|
|
11192
11477
|
}
|
|
11193
11478
|
});
|
|
11194
11479
|
|
|
11195
|
-
// src/farm/provision.ts
|
|
11196
|
-
import { nanoid as nanoid12 } from "nanoid";
|
|
11197
|
-
function awaitResult(requestId2, send, timeoutMs) {
|
|
11198
|
-
if (!send()) return Promise.reject(new Error("farm_offline"));
|
|
11199
|
-
return new Promise((resolve2, reject) => {
|
|
11200
|
-
const timer = setTimeout(() => {
|
|
11201
|
-
pending.delete(requestId2);
|
|
11202
|
-
reject(new Error("farm_timeout"));
|
|
11203
|
-
}, timeoutMs);
|
|
11204
|
-
pending.set(requestId2, { resolve: resolve2, reject, timer });
|
|
11205
|
-
});
|
|
11206
|
-
}
|
|
11207
|
-
function requestDelete(farmId, projectId, timeoutMs) {
|
|
11208
|
-
const requestId2 = nanoid12(16);
|
|
11209
|
-
return awaitResult(
|
|
11210
|
-
requestId2,
|
|
11211
|
-
() => sendToFarm(farmId, { type: "delete_project", requestId: requestId2, projectId }),
|
|
11212
|
-
timeoutMs
|
|
11213
|
-
);
|
|
11214
|
-
}
|
|
11215
|
-
async function requestFarmPoolSync(db, farmId) {
|
|
11216
|
-
const accounts = await computeFarmPoolSpec(db, farmId);
|
|
11217
|
-
sendToFarm(farmId, buildSyncWorkerPoolMessage(accounts));
|
|
11218
|
-
}
|
|
11219
|
-
var pending;
|
|
11220
|
-
var init_provision = __esm({
|
|
11221
|
-
"src/farm/provision.ts"() {
|
|
11222
|
-
"use strict";
|
|
11223
|
-
init_ws();
|
|
11224
|
-
init_pool_spec();
|
|
11225
|
-
pending = /* @__PURE__ */ new Map();
|
|
11226
|
-
}
|
|
11227
|
-
});
|
|
11228
|
-
|
|
11229
|
-
// src/routes/farm.ts
|
|
11230
|
-
import { nanoid as nanoid13 } from "nanoid";
|
|
11231
|
-
var init_farm2 = __esm({
|
|
11232
|
-
"src/routes/farm.ts"() {
|
|
11233
|
-
"use strict";
|
|
11234
|
-
init_sealedbox();
|
|
11235
|
-
init_abuse_throttle();
|
|
11236
|
-
init_cli_auth();
|
|
11237
|
-
init_auth();
|
|
11238
|
-
init_users();
|
|
11239
|
-
init_init_status();
|
|
11240
|
-
init_ws();
|
|
11241
|
-
init_provision();
|
|
11242
|
-
}
|
|
11243
|
-
});
|
|
11244
|
-
|
|
11245
|
-
// src/runner-reg-tokens.ts
|
|
11246
|
-
import { nanoid as nanoid14 } from "nanoid";
|
|
11247
|
-
var init_runner_reg_tokens = __esm({
|
|
11248
|
-
"src/runner-reg-tokens.ts"() {
|
|
11249
|
-
"use strict";
|
|
11250
|
-
}
|
|
11251
|
-
});
|
|
11252
|
-
|
|
11253
|
-
// src/runner/provision.ts
|
|
11254
|
-
import { nanoid as nanoid15 } from "nanoid";
|
|
11255
|
-
var init_provision2 = __esm({
|
|
11256
|
-
"src/runner/provision.ts"() {
|
|
11257
|
-
"use strict";
|
|
11258
|
-
init_ws();
|
|
11259
|
-
}
|
|
11260
|
-
});
|
|
11261
|
-
|
|
11262
|
-
// src/runner/bindings.ts
|
|
11263
|
-
function parseGhRunnerBindings(json) {
|
|
11264
|
-
if (!json) return [];
|
|
11265
|
-
try {
|
|
11266
|
-
const parsed = JSON.parse(json);
|
|
11267
|
-
return Array.isArray(parsed.bindings) ? parsed.bindings : [];
|
|
11268
|
-
} catch {
|
|
11269
|
-
return [];
|
|
11270
|
-
}
|
|
11271
|
-
}
|
|
11272
|
-
function serialize(bindings) {
|
|
11273
|
-
return JSON.stringify({ bindings });
|
|
11274
|
-
}
|
|
11275
|
-
async function removeGhRunnerBinding(db, projectId, owner, repo) {
|
|
11276
|
-
return db.tx(async (tx) => {
|
|
11277
|
-
const row = await tx.get(
|
|
11278
|
-
`SELECT gh_runner_bindings_json as bindingsJson FROM projects WHERE id = ?`,
|
|
11279
|
-
projectId
|
|
11280
|
-
);
|
|
11281
|
-
if (!row) return false;
|
|
11282
|
-
const bindings = parseGhRunnerBindings(row.bindingsJson);
|
|
11283
|
-
const kept = bindings.filter((b) => !(b.owner === owner && b.repo === repo));
|
|
11284
|
-
if (kept.length === bindings.length) return false;
|
|
11285
|
-
await tx.run(
|
|
11286
|
-
`UPDATE projects SET gh_runner_bindings_json = ? WHERE id = ?`,
|
|
11287
|
-
serialize(kept),
|
|
11288
|
-
projectId
|
|
11289
|
-
);
|
|
11290
|
-
return true;
|
|
11291
|
-
});
|
|
11292
|
-
}
|
|
11293
|
-
var init_bindings = __esm({
|
|
11294
|
-
"src/runner/bindings.ts"() {
|
|
11295
|
-
"use strict";
|
|
11296
|
-
}
|
|
11297
|
-
});
|
|
11298
|
-
|
|
11299
|
-
// src/routes/runner.ts
|
|
11300
|
-
import { nanoid as nanoid16 } from "nanoid";
|
|
11301
|
-
var init_runner2 = __esm({
|
|
11302
|
-
"src/routes/runner.ts"() {
|
|
11303
|
-
"use strict";
|
|
11304
|
-
init_sealedbox();
|
|
11305
|
-
init_abuse_throttle();
|
|
11306
|
-
init_cli_auth();
|
|
11307
|
-
init_auth();
|
|
11308
|
-
init_users();
|
|
11309
|
-
init_runner_reg_tokens();
|
|
11310
|
-
init_ws();
|
|
11311
|
-
init_provision2();
|
|
11312
|
-
init_bindings();
|
|
11313
|
-
}
|
|
11314
|
-
});
|
|
11315
|
-
|
|
11316
11480
|
// src/routes/ws.ts
|
|
11317
11481
|
function sendToDeliveryFollowers(projectId, message) {
|
|
11318
11482
|
const payload = JSON.stringify(message);
|
|
@@ -11356,6 +11520,13 @@ function sendToFarm(farmId, message) {
|
|
|
11356
11520
|
function listConnectedFarmIds() {
|
|
11357
11521
|
return [...farmConnections.keys()];
|
|
11358
11522
|
}
|
|
11523
|
+
function sendToRunner(runnerId, message) {
|
|
11524
|
+
const conn = runnerConnections.get(runnerId);
|
|
11525
|
+
if (!conn) return false;
|
|
11526
|
+
if (conn.socket.readyState !== conn.socket.OPEN) return false;
|
|
11527
|
+
conn.socket.send(JSON.stringify(message));
|
|
11528
|
+
return true;
|
|
11529
|
+
}
|
|
11359
11530
|
function getProjectCliOrigin(projectId) {
|
|
11360
11531
|
return projectCliConnections.get(projectId)?.origin ?? null;
|
|
11361
11532
|
}
|
|
@@ -11598,7 +11769,7 @@ async function getPresence(db, viewer) {
|
|
|
11598
11769
|
})()
|
|
11599
11770
|
};
|
|
11600
11771
|
}
|
|
11601
|
-
var agentLoginProviders, deliveryFollowers, projectCliConnections, projectLastSeenAt, projectLastDuplicateRegisterAt, sandboxConnections, farmConnections, cliMessageHandlers, cliRegisterHandlers;
|
|
11772
|
+
var agentLoginProviders, deliveryFollowers, projectCliConnections, projectLastSeenAt, projectLastDuplicateRegisterAt, sandboxConnections, farmConnections, runnerConnections, cliMessageHandlers, cliRegisterHandlers;
|
|
11602
11773
|
var init_ws = __esm({
|
|
11603
11774
|
"src/routes/ws.ts"() {
|
|
11604
11775
|
"use strict";
|
|
@@ -11624,6 +11795,7 @@ var init_ws = __esm({
|
|
|
11624
11795
|
projectLastDuplicateRegisterAt = /* @__PURE__ */ new Map();
|
|
11625
11796
|
sandboxConnections = /* @__PURE__ */ new Map();
|
|
11626
11797
|
farmConnections = /* @__PURE__ */ new Map();
|
|
11798
|
+
runnerConnections = /* @__PURE__ */ new Map();
|
|
11627
11799
|
cliMessageHandlers = /* @__PURE__ */ new Set();
|
|
11628
11800
|
cliRegisterHandlers = /* @__PURE__ */ new Set();
|
|
11629
11801
|
}
|
|
@@ -11751,7 +11923,7 @@ var init_policy = __esm({
|
|
|
11751
11923
|
});
|
|
11752
11924
|
|
|
11753
11925
|
// src/apply/merge-branch.ts
|
|
11754
|
-
import { nanoid as
|
|
11926
|
+
import { nanoid as nanoid15 } from "nanoid";
|
|
11755
11927
|
async function enqueueMergeBranch(db, taskId2, log) {
|
|
11756
11928
|
if (await hasInflightMergeBranch(db, taskId2)) {
|
|
11757
11929
|
log.info({ taskId: taskId2 }, "merge_branch ignored \u2014 chain already in flight");
|
|
@@ -11768,7 +11940,7 @@ async function enqueueMergeBranch(db, taskId2, log) {
|
|
|
11768
11940
|
const now = Date.now();
|
|
11769
11941
|
const ids = [];
|
|
11770
11942
|
for (const t of targets) {
|
|
11771
|
-
const id = `wa_${
|
|
11943
|
+
const id = `wa_${nanoid15(12)}`;
|
|
11772
11944
|
const action = {
|
|
11773
11945
|
type: "merge_branch",
|
|
11774
11946
|
taskId: taskId2,
|
|
@@ -11849,7 +12021,7 @@ var init_merge_branch = __esm({
|
|
|
11849
12021
|
});
|
|
11850
12022
|
|
|
11851
12023
|
// src/apply/publish-delivery.ts
|
|
11852
|
-
import { nanoid as
|
|
12024
|
+
import { nanoid as nanoid16 } from "nanoid";
|
|
11853
12025
|
async function enqueuePublishDeliveries(db, taskId2, log) {
|
|
11854
12026
|
const unpublished = await unpublishedDeliveries(db, taskId2);
|
|
11855
12027
|
const unusable = unpublished.filter((p) => p.revision === null);
|
|
@@ -11860,8 +12032,8 @@ async function enqueuePublishDeliveries(db, taskId2, log) {
|
|
|
11860
12032
|
};
|
|
11861
12033
|
}
|
|
11862
12034
|
const inFlight2 = await inFlightPublications(db, taskId2);
|
|
11863
|
-
const
|
|
11864
|
-
if (
|
|
12035
|
+
const pending5 = unpublished.filter((p) => inFlight2.get(p.repoName) !== p.revision?.sha);
|
|
12036
|
+
if (pending5.length === 0) return { queued: 0 };
|
|
11865
12037
|
const projectId = taskId2.split(":", 2)[0] ?? "";
|
|
11866
12038
|
const contextRepoCloneUrl = await contextRepoCloneUrlOf(db, projectId);
|
|
11867
12039
|
if (!contextRepoCloneUrl) {
|
|
@@ -11870,8 +12042,8 @@ async function enqueuePublishDeliveries(db, taskId2, log) {
|
|
|
11870
12042
|
}
|
|
11871
12043
|
const now = Date.now();
|
|
11872
12044
|
const ids = [];
|
|
11873
|
-
for (const p of
|
|
11874
|
-
const id = `wa_${
|
|
12045
|
+
for (const p of pending5) {
|
|
12046
|
+
const id = `wa_${nanoid16(12)}`;
|
|
11875
12047
|
const action = {
|
|
11876
12048
|
type: "publish_delivery",
|
|
11877
12049
|
taskId: taskId2,
|
|
@@ -12239,10 +12411,10 @@ __export(regimen_config_exports, {
|
|
|
12239
12411
|
loadWorkflowSettings: () => loadWorkflowSettings,
|
|
12240
12412
|
loadYouTrackWrites: () => loadYouTrackWrites
|
|
12241
12413
|
});
|
|
12242
|
-
import { createHash as
|
|
12414
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
12243
12415
|
import { parse as parseYaml2 } from "yaml";
|
|
12244
12416
|
function contentSha2(content) {
|
|
12245
|
-
return
|
|
12417
|
+
return createHash3("sha1").update(content, "utf-8").digest("hex");
|
|
12246
12418
|
}
|
|
12247
12419
|
async function applyRegimenContent(db, projectId, content, log, preParsed) {
|
|
12248
12420
|
const previous = cache3.get(projectId) ?? null;
|
|
@@ -13132,7 +13304,7 @@ var init_settings = __esm({
|
|
|
13132
13304
|
});
|
|
13133
13305
|
|
|
13134
13306
|
// src/jobs/queue.ts
|
|
13135
|
-
import { nanoid as
|
|
13307
|
+
import { nanoid as nanoid17 } from "nanoid";
|
|
13136
13308
|
async function heldByTrancheDeferral(db) {
|
|
13137
13309
|
const rows = await db.all(
|
|
13138
13310
|
`SELECT t.id AS id FROM tasks t
|
|
@@ -13204,7 +13376,7 @@ async function restampQueuedJobs(db, projectId) {
|
|
|
13204
13376
|
return restamped;
|
|
13205
13377
|
}
|
|
13206
13378
|
async function createJob(db, input) {
|
|
13207
|
-
const id = `job_${
|
|
13379
|
+
const id = `job_${nanoid17(16)}`;
|
|
13208
13380
|
const now = Date.now();
|
|
13209
13381
|
const projectRepos = await loadProjectRepoRefs(db, input.projectId);
|
|
13210
13382
|
const metadata = {
|
|
@@ -13405,7 +13577,7 @@ async function claimNextJob(db, sandboxId, capabilities, lane) {
|
|
|
13405
13577
|
data: e.data
|
|
13406
13578
|
});
|
|
13407
13579
|
},
|
|
13408
|
-
newExecutionId: () => `exe_${
|
|
13580
|
+
newExecutionId: () => `exe_${nanoid17(16)}`,
|
|
13409
13581
|
// DEV-49/DEV-221: stack the worktree on the predecessor's branch so the
|
|
13410
13582
|
// agent (and the resulting meta/bases.json baseSha) start from the chain
|
|
13411
13583
|
// tip, not the repo default. Applies to analyze/estimate/implement — a
|
|
@@ -13671,7 +13843,7 @@ var init_legacy_cred_bundles = __esm({
|
|
|
13671
13843
|
});
|
|
13672
13844
|
|
|
13673
13845
|
// src/db/schema.ts
|
|
13674
|
-
import { nanoid as
|
|
13846
|
+
import { nanoid as nanoid18 } from "nanoid";
|
|
13675
13847
|
async function ensureColumn(db, table, column, ddl) {
|
|
13676
13848
|
const row = await db.get(
|
|
13677
13849
|
`SELECT 1 FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,
|
|
@@ -13980,7 +14152,7 @@ async function initSchema(db) {
|
|
|
13980
14152
|
await ensureColumn(db, "projects", "init_status_json", "init_status_json TEXT");
|
|
13981
14153
|
await ensureColumn(db, "users", "id", "id TEXT");
|
|
13982
14154
|
for (const row of await db.all(`SELECT email FROM users WHERE id IS NULL`)) {
|
|
13983
|
-
await db.run(`UPDATE users SET id = ? WHERE email = ?`, `usr_${
|
|
14155
|
+
await db.run(`UPDATE users SET id = ? WHERE email = ?`, `usr_${nanoid18(12)}`, row.email);
|
|
13984
14156
|
}
|
|
13985
14157
|
await db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_id ON users(id)`);
|
|
13986
14158
|
await ensureColumn(db, "projects", "owner_user_id", "owner_user_id TEXT");
|
|
@@ -14064,7 +14236,7 @@ async function initSchema(db) {
|
|
|
14064
14236
|
await db.run(
|
|
14065
14237
|
`INSERT INTO context_outbox (id, project_id, ordinal, files_json, message, state, created_at)
|
|
14066
14238
|
VALUES (?, ?, ?, ?, 'migration: legacy display diffs', 'pending', ?)`,
|
|
14067
|
-
`ctx_${
|
|
14239
|
+
`ctx_${nanoid18(16)}`,
|
|
14068
14240
|
projectId,
|
|
14069
14241
|
next.next,
|
|
14070
14242
|
JSON.stringify(files),
|
|
@@ -15898,27 +16070,27 @@ var init_delivery_decision = __esm({
|
|
|
15898
16070
|
});
|
|
15899
16071
|
|
|
15900
16072
|
// src/projects/context-read.ts
|
|
15901
|
-
import { nanoid as
|
|
16073
|
+
import { nanoid as nanoid19 } from "nanoid";
|
|
15902
16074
|
function requestContextFile(projectId, path, timeoutMs = DEFAULT_TIMEOUT_MS, send = sendToProjectCli) {
|
|
15903
16075
|
const action = { type: "read_context_file", projectId, path };
|
|
15904
|
-
const actionId2 = `rdctx_${
|
|
16076
|
+
const actionId2 = `rdctx_${nanoid19(16)}`;
|
|
15905
16077
|
if (!send(projectId, { type: "dispatch_action", actionId: actionId2, action })) {
|
|
15906
16078
|
return Promise.reject(new Error("cli_offline"));
|
|
15907
16079
|
}
|
|
15908
16080
|
return new Promise((resolve2, reject) => {
|
|
15909
16081
|
const timer = setTimeout(() => {
|
|
15910
|
-
|
|
16082
|
+
pending.delete(actionId2);
|
|
15911
16083
|
reject(new Error("cli_timeout"));
|
|
15912
16084
|
}, timeoutMs);
|
|
15913
|
-
|
|
16085
|
+
pending.set(actionId2, { resolve: resolve2, reject, timer });
|
|
15914
16086
|
});
|
|
15915
16087
|
}
|
|
15916
|
-
var
|
|
16088
|
+
var pending, DEFAULT_TIMEOUT_MS;
|
|
15917
16089
|
var init_context_read = __esm({
|
|
15918
16090
|
"src/projects/context-read.ts"() {
|
|
15919
16091
|
"use strict";
|
|
15920
16092
|
init_ws();
|
|
15921
|
-
|
|
16093
|
+
pending = /* @__PURE__ */ new Map();
|
|
15922
16094
|
DEFAULT_TIMEOUT_MS = 2e4;
|
|
15923
16095
|
}
|
|
15924
16096
|
});
|
|
@@ -15963,14 +16135,14 @@ async function recordVerdicts(db, taskId2, verdicts) {
|
|
|
15963
16135
|
return current;
|
|
15964
16136
|
}
|
|
15965
16137
|
function decidePlan(plan, verdicts) {
|
|
15966
|
-
const
|
|
16138
|
+
const pending5 = plan.items.filter((i) => !(i.id in verdicts)).map((i) => i.id);
|
|
15967
16139
|
const list = plan.items.map((i) => verdicts[i.id]).filter((v) => !!v);
|
|
15968
|
-
const approved =
|
|
16140
|
+
const approved = pending5.length === 0 && list.every((v) => v.approved);
|
|
15969
16141
|
return {
|
|
15970
|
-
complete:
|
|
15971
|
-
pending:
|
|
16142
|
+
complete: pending5.length === 0,
|
|
16143
|
+
pending: pending5,
|
|
15972
16144
|
approved,
|
|
15973
|
-
rework:
|
|
16145
|
+
rework: pending5.length === 0 ? compilePlanRework(plan, list) : ""
|
|
15974
16146
|
};
|
|
15975
16147
|
}
|
|
15976
16148
|
function describeDecision(d) {
|
|
@@ -16108,7 +16280,7 @@ var init_project_access = __esm({
|
|
|
16108
16280
|
});
|
|
16109
16281
|
|
|
16110
16282
|
// src/projects/regimen-write.ts
|
|
16111
|
-
import { nanoid as
|
|
16283
|
+
import { nanoid as nanoid20 } from "nanoid";
|
|
16112
16284
|
function dispatchSetYouTrackConfig(db, projectId, payload, log) {
|
|
16113
16285
|
const action = {
|
|
16114
16286
|
type: "set_youtrack_config",
|
|
@@ -16116,7 +16288,7 @@ function dispatchSetYouTrackConfig(db, projectId, payload, log) {
|
|
|
16116
16288
|
...payload,
|
|
16117
16289
|
commitMessage: `chore(youtrack): update YT config (dashboard)`
|
|
16118
16290
|
};
|
|
16119
|
-
const actionId2 = `ytcfg_${
|
|
16291
|
+
const actionId2 = `ytcfg_${nanoid20(16)}`;
|
|
16120
16292
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
16121
16293
|
if (!sent) {
|
|
16122
16294
|
log.warn({ projectId, actionId: actionId2 }, "dispatchSetYouTrackConfig: CLI not connected");
|
|
@@ -16132,7 +16304,7 @@ function dispatchSetRepoDefaultBranch(db, projectId, repoName, defaultBranch, lo
|
|
|
16132
16304
|
repoName,
|
|
16133
16305
|
defaultBranch
|
|
16134
16306
|
};
|
|
16135
|
-
const actionId2 = `repocfg_${
|
|
16307
|
+
const actionId2 = `repocfg_${nanoid20(16)}`;
|
|
16136
16308
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
16137
16309
|
if (!sent) {
|
|
16138
16310
|
log.warn({ projectId, actionId: actionId2 }, "dispatchSetRepoDefaultBranch: CLI not connected");
|
|
@@ -16152,7 +16324,7 @@ function dispatchSetProjectSetting(db, projectId, edits, log, opts) {
|
|
|
16152
16324
|
edits,
|
|
16153
16325
|
commitMessage: opts?.commitMessage ?? "chore(settings): update project settings (dashboard)"
|
|
16154
16326
|
};
|
|
16155
|
-
const actionId2 = `${prefix}${
|
|
16327
|
+
const actionId2 = `${prefix}${nanoid20(16)}`;
|
|
16156
16328
|
if (prefix === "setg_") rememberSettingsDispatch(actionId2, edits);
|
|
16157
16329
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
16158
16330
|
if (!sent) {
|
|
@@ -16164,7 +16336,7 @@ function dispatchSetProjectSetting(db, projectId, edits, log, opts) {
|
|
|
16164
16336
|
}
|
|
16165
16337
|
function dispatchSetBudgetSource(db, projectId, source, log) {
|
|
16166
16338
|
const action = { type: "set_budget_source", projectId, source };
|
|
16167
|
-
const actionId2 = `bsrc_${
|
|
16339
|
+
const actionId2 = `bsrc_${nanoid20(16)}`;
|
|
16168
16340
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
16169
16341
|
if (!sent) log.warn({ projectId, actionId: actionId2 }, "dispatchSetBudgetSource: CLI not connected");
|
|
16170
16342
|
else log.info({ projectId, actionId: actionId2, kind: source.kind }, "dispatchSetBudgetSource: sent to CLI");
|
|
@@ -16186,7 +16358,7 @@ function dispatchEditTrackerIssue(db, projectId, fields, log) {
|
|
|
16186
16358
|
...fields.title !== void 0 ? { title: fields.title } : {},
|
|
16187
16359
|
...fields.body !== void 0 ? { body: fields.body } : {}
|
|
16188
16360
|
};
|
|
16189
|
-
const actionId2 = `tedit_${
|
|
16361
|
+
const actionId2 = `tedit_${nanoid20(16)}`;
|
|
16190
16362
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
16191
16363
|
if (!sent) {
|
|
16192
16364
|
log.warn({ projectId, actionId: actionId2, issueKey: fields.issueKey }, "dispatchEditTrackerIssue: CLI not connected");
|
|
@@ -16206,21 +16378,21 @@ var init_regimen_write = __esm({
|
|
|
16206
16378
|
});
|
|
16207
16379
|
|
|
16208
16380
|
// src/tasks/context-outbox.ts
|
|
16209
|
-
import { nanoid as
|
|
16381
|
+
import { nanoid as nanoid21 } from "nanoid";
|
|
16210
16382
|
async function nextOrdinal(db, projectId) {
|
|
16211
16383
|
const row = await db.get(`SELECT COALESCE(MAX(ordinal), 0) AS max FROM context_outbox WHERE project_id = ?`, projectId);
|
|
16212
16384
|
return row.max + 1;
|
|
16213
16385
|
}
|
|
16214
16386
|
async function enqueueContextWrite(db, projectId, files, message, log) {
|
|
16215
16387
|
if (files.length === 0) return;
|
|
16216
|
-
const id = `ctx_${
|
|
16388
|
+
const id = `ctx_${nanoid21(16)}`;
|
|
16217
16389
|
await db.run(`INSERT INTO context_outbox (id, project_id, ordinal, files_json, message, state, created_at)
|
|
16218
16390
|
VALUES (?, ?, ?, ?, ?, 'pending', ?)`, id, projectId, await nextOrdinal(db, projectId), JSON.stringify(files), message, Date.now());
|
|
16219
16391
|
await dispatchNextContextWrite(db, projectId, log);
|
|
16220
16392
|
}
|
|
16221
16393
|
async function enqueueAdrPromote(db, projectId, taskKey, slugs, log, source = "task") {
|
|
16222
16394
|
if (slugs.length === 0) return;
|
|
16223
|
-
const id = `ctx_${
|
|
16395
|
+
const id = `ctx_${nanoid21(16)}`;
|
|
16224
16396
|
await db.run(`INSERT INTO context_outbox (id, project_id, ordinal, files_json, message, state, kind, meta_json, created_at)
|
|
16225
16397
|
VALUES (?, ?, ?, ?, ?, 'pending', 'adr_promote', ?, ?)`, id, projectId, await nextOrdinal(db, projectId), JSON.stringify(slugs), `adr(${taskKey}): promote ${slugs.join(", ")}`, JSON.stringify({ taskKey, source }), Date.now());
|
|
16226
16398
|
await dispatchNextContextWrite(db, projectId, log);
|
|
@@ -16625,10 +16797,10 @@ __export(cluster_journal_exports, {
|
|
|
16625
16797
|
clusterJournal: () => clusterJournal,
|
|
16626
16798
|
recordClusterEvent: () => recordClusterEvent
|
|
16627
16799
|
});
|
|
16628
|
-
import { nanoid as
|
|
16800
|
+
import { nanoid as nanoid22 } from "nanoid";
|
|
16629
16801
|
async function recordClusterEvent(db, projectId, anchorKey, kind, detail) {
|
|
16630
16802
|
await engineStore(db).clusters.appendEvent(
|
|
16631
|
-
`ce_${
|
|
16803
|
+
`ce_${nanoid22(12)}`,
|
|
16632
16804
|
projectId,
|
|
16633
16805
|
anchorKey,
|
|
16634
16806
|
kind,
|
|
@@ -16696,7 +16868,7 @@ __export(cluster_recovery_exports, {
|
|
|
16696
16868
|
reconcileClusterRecoveries: () => reconcileClusterRecoveries,
|
|
16697
16869
|
startClusterRecovery: () => startClusterRecovery
|
|
16698
16870
|
});
|
|
16699
|
-
import { nanoid as
|
|
16871
|
+
import { nanoid as nanoid23 } from "nanoid";
|
|
16700
16872
|
async function startClusterRecovery(db, task, instruction, log, opts = {}) {
|
|
16701
16873
|
const anchorKey = await clusterAnchorFor(db, task);
|
|
16702
16874
|
const cluster = await getCluster(db, task.projectId, anchorKey);
|
|
@@ -17054,7 +17226,7 @@ async function dropCluster(db, task, cluster, reason, log, opts = {}) {
|
|
|
17054
17226
|
if (carrier) {
|
|
17055
17227
|
const now = Date.now();
|
|
17056
17228
|
for (const t of await loadCreatePRTargets(db, carrier)) {
|
|
17057
|
-
const id = `wa_${
|
|
17229
|
+
const id = `wa_${nanoid23(12)}`;
|
|
17058
17230
|
const action = {
|
|
17059
17231
|
type: "close_pr",
|
|
17060
17232
|
taskId: carrier,
|
|
@@ -17173,7 +17345,7 @@ __export(tasks_exports, {
|
|
|
17173
17345
|
queueTrackerStatusMove: () => queueTrackerStatusMove,
|
|
17174
17346
|
tasksRoutes: () => tasksRoutes
|
|
17175
17347
|
});
|
|
17176
|
-
import { nanoid as
|
|
17348
|
+
import { nanoid as nanoid24 } from "nanoid";
|
|
17177
17349
|
async function queueTrackerStatusMove(db, log, taskId2, projectId, newStatus, manual = false) {
|
|
17178
17350
|
const projectRow = await db.get(`SELECT automation_level as automationLevel FROM projects WHERE id = ?`, projectId);
|
|
17179
17351
|
const writes = await loadYouTrackWrites(
|
|
@@ -17188,7 +17360,7 @@ async function queueTrackerStatusMove(db, log, taskId2, projectId, newStatus, ma
|
|
|
17188
17360
|
newStatus,
|
|
17189
17361
|
...manual ? { manual: true } : {}
|
|
17190
17362
|
};
|
|
17191
|
-
const statusActionId = `wa_${
|
|
17363
|
+
const statusActionId = `wa_${nanoid24(12)}`;
|
|
17192
17364
|
await engineStore(db).actions.appendPlan(
|
|
17193
17365
|
taskId2,
|
|
17194
17366
|
[{ id: statusActionId, kind: "tracker_status", payloadJson: JSON.stringify(action) }],
|
|
@@ -18037,7 +18209,7 @@ async function tasksRoutes(app, deps) {
|
|
|
18037
18209
|
}
|
|
18038
18210
|
await ingestComment(db, {
|
|
18039
18211
|
taskId: taskId2,
|
|
18040
|
-
trackerCommentId: `${DASHBOARD_PLACEHOLDER_PREFIX}${
|
|
18212
|
+
trackerCommentId: `${DASHBOARD_PLACEHOLDER_PREFIX}${nanoid24(8)}`,
|
|
18041
18213
|
author: session.email,
|
|
18042
18214
|
body: req.body.comment,
|
|
18043
18215
|
origin: "dashboard",
|
|
@@ -18195,7 +18367,7 @@ async function tasksRoutes(app, deps) {
|
|
|
18195
18367
|
if (!task) return reply.status(404).send({ error: "task_not_found" });
|
|
18196
18368
|
const ingest = await ingestComment(db, {
|
|
18197
18369
|
taskId: taskId2,
|
|
18198
|
-
trackerCommentId: `${DASHBOARD_PLACEHOLDER_PREFIX}${
|
|
18370
|
+
trackerCommentId: `${DASHBOARD_PLACEHOLDER_PREFIX}${nanoid24(8)}`,
|
|
18199
18371
|
author: session.email,
|
|
18200
18372
|
body: req.body.body,
|
|
18201
18373
|
origin: "dashboard",
|
|
@@ -18264,7 +18436,7 @@ ${req.body.body}
|
|
|
18264
18436
|
// comments.jsonl; YouTrack ignores it (the token owner authors).
|
|
18265
18437
|
author: session.email
|
|
18266
18438
|
};
|
|
18267
|
-
mirrorActionId = `wa_${
|
|
18439
|
+
mirrorActionId = `wa_${nanoid24(12)}`;
|
|
18268
18440
|
await engineStore(db).actions.appendPlan(
|
|
18269
18441
|
taskId2,
|
|
18270
18442
|
[{ id: mirrorActionId, kind: "tracker_comment", payloadJson: JSON.stringify(action) }],
|
|
@@ -18455,7 +18627,7 @@ __export(observer_exports, {
|
|
|
18455
18627
|
stampTranche: () => stampTranche,
|
|
18456
18628
|
startStagedBatch: () => startStagedBatch
|
|
18457
18629
|
});
|
|
18458
|
-
import { nanoid as
|
|
18630
|
+
import { nanoid as nanoid25 } from "nanoid";
|
|
18459
18631
|
async function loadContextRepo2(db, projectId) {
|
|
18460
18632
|
const row = await db.get(
|
|
18461
18633
|
`SELECT context_repo_json AS json FROM projects WHERE id = ?`,
|
|
@@ -18500,7 +18672,7 @@ async function enqueueObserverJob(db, batch, log, opts = {}) {
|
|
|
18500
18672
|
return spec.jobId;
|
|
18501
18673
|
}
|
|
18502
18674
|
async function stampTranche(db, taskIds) {
|
|
18503
|
-
const trancheId = `tr_${
|
|
18675
|
+
const trancheId = `tr_${nanoid25(12)}`;
|
|
18504
18676
|
for (const id of taskIds) {
|
|
18505
18677
|
await engineStore(db).tasks.stampTrancheMembership(id, trancheId);
|
|
18506
18678
|
}
|
|
@@ -18926,7 +19098,7 @@ var init_queue_view = __esm({
|
|
|
18926
19098
|
});
|
|
18927
19099
|
|
|
18928
19100
|
// src/tasks/cluster-order.ts
|
|
18929
|
-
import { nanoid as
|
|
19101
|
+
import { nanoid as nanoid26 } from "nanoid";
|
|
18930
19102
|
function chainLess2(a, b) {
|
|
18931
19103
|
const aq = a.queuePosition ?? Number.MAX_SAFE_INTEGER;
|
|
18932
19104
|
const bq = b.queuePosition ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -19032,7 +19204,7 @@ function dispatchProposedLinks(db, log, projectId, anchorKey, addLinks, memberKe
|
|
|
19032
19204
|
links.push({ sourceIssueKey: source, rel: "depends_on", targetIssueKey: target });
|
|
19033
19205
|
}
|
|
19034
19206
|
if (links.length === 0) return;
|
|
19035
|
-
const actionId2 = `clnk_${
|
|
19207
|
+
const actionId2 = `clnk_${nanoid26(16)}`;
|
|
19036
19208
|
const action = { type: "apply_tracker_links", draftId: actionId2, projectId, links };
|
|
19037
19209
|
const sent = sendToProjectCli(projectId, { type: "dispatch_action", actionId: actionId2, action });
|
|
19038
19210
|
if (sent) {
|
|
@@ -19058,9 +19230,9 @@ var init_cluster_order = __esm({
|
|
|
19058
19230
|
});
|
|
19059
19231
|
|
|
19060
19232
|
// src/prompts/prompt-cache.ts
|
|
19061
|
-
import { createHash as
|
|
19233
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
19062
19234
|
function contentSha3(content) {
|
|
19063
|
-
return
|
|
19235
|
+
return createHash4("sha1").update(content, "utf-8").digest("hex");
|
|
19064
19236
|
}
|
|
19065
19237
|
function getProjectPromptTemplate(projectId, role) {
|
|
19066
19238
|
return cache4.get(projectId)?.get(role)?.content ?? null;
|
|
@@ -21020,7 +21192,7 @@ __export(cluster_finalize_exports, {
|
|
|
21020
21192
|
maybeFinalizeCluster: () => maybeFinalizeCluster,
|
|
21021
21193
|
recordClusterPrCreated: () => recordClusterPrCreated
|
|
21022
21194
|
});
|
|
21023
|
-
import { nanoid as
|
|
21195
|
+
import { nanoid as nanoid27 } from "nanoid";
|
|
21024
21196
|
async function isFullAutomation2(db, projectId) {
|
|
21025
21197
|
const row = await db.get(`SELECT automation_level as automationLevel FROM projects WHERE id = ?`, projectId);
|
|
21026
21198
|
return row?.automationLevel === "full";
|
|
@@ -21041,7 +21213,7 @@ async function loadMergePolicy2(db, projectId) {
|
|
|
21041
21213
|
};
|
|
21042
21214
|
}
|
|
21043
21215
|
async function enqueueAndDispatch(db, taskId2, action, log) {
|
|
21044
|
-
const id = `wa_${
|
|
21216
|
+
const id = `wa_${nanoid27(12)}`;
|
|
21045
21217
|
await engineStore(db).actions.appendPlan(
|
|
21046
21218
|
taskId2,
|
|
21047
21219
|
[{ id, kind: action.type, payloadJson: JSON.stringify(action) }],
|
|
@@ -21771,7 +21943,7 @@ __export(tracking_commands_exports, {
|
|
|
21771
21943
|
handleUpdatePr: () => handleUpdatePr,
|
|
21772
21944
|
loadCreatePRTargets: () => loadCreatePRTargets
|
|
21773
21945
|
});
|
|
21774
|
-
import { nanoid as
|
|
21946
|
+
import { nanoid as nanoid28 } from "nanoid";
|
|
21775
21947
|
async function loadCreatePRTargets(db, taskId2) {
|
|
21776
21948
|
const rows = await db.all(`SELECT payload_json AS payloadJson, result_json AS resultJson
|
|
21777
21949
|
FROM write_actions
|
|
@@ -21871,7 +22043,7 @@ async function handleApprove(db, task, log) {
|
|
|
21871
22043
|
const now = Date.now();
|
|
21872
22044
|
const ids = [];
|
|
21873
22045
|
for (const t of targets) {
|
|
21874
|
-
const id = `wa_${
|
|
22046
|
+
const id = `wa_${nanoid28(12)}`;
|
|
21875
22047
|
const action = {
|
|
21876
22048
|
type: "merge_pr",
|
|
21877
22049
|
taskId: chainTaskId,
|
|
@@ -22059,7 +22231,7 @@ async function handleClusterReject(db, task, comment, log) {
|
|
|
22059
22231
|
if (carrier) {
|
|
22060
22232
|
const now = Date.now();
|
|
22061
22233
|
for (const t of await loadCreatePRTargets(db, carrier)) {
|
|
22062
|
-
const id = `wa_${
|
|
22234
|
+
const id = `wa_${nanoid28(12)}`;
|
|
22063
22235
|
const action = {
|
|
22064
22236
|
type: "close_pr",
|
|
22065
22237
|
taskId: carrier,
|
|
@@ -22084,7 +22256,7 @@ async function handleClusterReject(db, task, comment, log) {
|
|
|
22084
22256
|
if (deliveredRepos.size > 0) {
|
|
22085
22257
|
const now = Date.now();
|
|
22086
22258
|
for (const repoName of deliveredRepos) {
|
|
22087
|
-
const id = `wa_${
|
|
22259
|
+
const id = `wa_${nanoid28(12)}`;
|
|
22088
22260
|
const action = {
|
|
22089
22261
|
type: "rewrite_cluster_branch",
|
|
22090
22262
|
taskId: owner.id,
|
|
@@ -22658,7 +22830,7 @@ __export(lifecycle_exports, {
|
|
|
22658
22830
|
taskRunsDirectImplement: () => taskRunsDirectImplement,
|
|
22659
22831
|
triggerRework: () => triggerRework
|
|
22660
22832
|
});
|
|
22661
|
-
import { createHash as
|
|
22833
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
22662
22834
|
async function reconcileStuckTasks(db, log) {
|
|
22663
22835
|
let recovered = 0;
|
|
22664
22836
|
for (const task of await listTasks(db)) {
|
|
@@ -23253,7 +23425,7 @@ async function maybeAwaitingAnswer(db, task, log, blockingQuestions = []) {
|
|
|
23253
23425
|
if (blocking.length === 0) return false;
|
|
23254
23426
|
const now = Date.now();
|
|
23255
23427
|
for (let i = 0; i < blocking.length; i++) {
|
|
23256
|
-
const digest2 =
|
|
23428
|
+
const digest2 = createHash5("sha256").update(blocking[i]).digest("hex").slice(0, 12);
|
|
23257
23429
|
await ingestComment(db, {
|
|
23258
23430
|
taskId: task.id,
|
|
23259
23431
|
trackerCommentId: `agent:${task.phase.toLowerCase()}:${i}:${digest2}`,
|
|
@@ -24645,25 +24817,25 @@ async function requestGrantsFrom(spec, recipient, bindingVersion, log, timeoutMs
|
|
|
24645
24817
|
const requestId2 = msg.requestId;
|
|
24646
24818
|
return await new Promise((resolve2) => {
|
|
24647
24819
|
const timer = setTimeout(() => {
|
|
24648
|
-
|
|
24820
|
+
pending2.delete(requestId2);
|
|
24649
24821
|
log.warn({ projectId: spec.projectId, jobId: spec.jobId, sandboxId: recipient.sandboxId }, "credential grants: CLI did not answer in time");
|
|
24650
24822
|
resolve2(null);
|
|
24651
24823
|
}, timeoutMs);
|
|
24652
|
-
|
|
24824
|
+
pending2.set(requestId2, {
|
|
24653
24825
|
projectId: spec.projectId,
|
|
24654
24826
|
jobId: spec.jobId,
|
|
24655
24827
|
sandboxId: recipient.sandboxId,
|
|
24656
24828
|
holder,
|
|
24657
24829
|
resolve: (answer) => {
|
|
24658
24830
|
clearTimeout(timer);
|
|
24659
|
-
|
|
24831
|
+
pending2.delete(requestId2);
|
|
24660
24832
|
resolve2(answer);
|
|
24661
24833
|
}
|
|
24662
24834
|
});
|
|
24663
24835
|
const sent = holder.kind === "provider" ? sendToAgentLoginProvider(holder.ownerKey, holder.providerId, msg) : sendToProjectCli(spec.projectId, msg);
|
|
24664
24836
|
if (!sent) {
|
|
24665
24837
|
clearTimeout(timer);
|
|
24666
|
-
|
|
24838
|
+
pending2.delete(requestId2);
|
|
24667
24839
|
resolve2(null);
|
|
24668
24840
|
}
|
|
24669
24841
|
});
|
|
@@ -24775,7 +24947,7 @@ async function sweepExpiredGrants(db, now = Date.now()) {
|
|
|
24775
24947
|
const { changes } = await db.run(`DELETE FROM credential_grants WHERE not_after <= ?`, now);
|
|
24776
24948
|
return changes;
|
|
24777
24949
|
}
|
|
24778
|
-
var GRANT_REQUEST_TIMEOUT_MS,
|
|
24950
|
+
var GRANT_REQUEST_TIMEOUT_MS, pending2, LATE_ANSWER_TTL_MS, lateAnswers;
|
|
24779
24951
|
var init_credential_grants = __esm({
|
|
24780
24952
|
"src/tracker/credential-grants.ts"() {
|
|
24781
24953
|
"use strict";
|
|
@@ -24786,7 +24958,7 @@ var init_credential_grants = __esm({
|
|
|
24786
24958
|
init_ws();
|
|
24787
24959
|
init_sandbox_keys();
|
|
24788
24960
|
GRANT_REQUEST_TIMEOUT_MS = 6e3;
|
|
24789
|
-
|
|
24961
|
+
pending2 = /* @__PURE__ */ new Map();
|
|
24790
24962
|
LATE_ANSWER_TTL_MS = 6e4;
|
|
24791
24963
|
lateAnswers = /* @__PURE__ */ new Map();
|
|
24792
24964
|
}
|
|
@@ -24840,7 +25012,7 @@ init_regimen_config();
|
|
|
24840
25012
|
// src/projects/cred-updates.ts
|
|
24841
25013
|
init_dist();
|
|
24842
25014
|
init_ws();
|
|
24843
|
-
import { nanoid as
|
|
25015
|
+
import { nanoid as nanoid29 } from "nanoid";
|
|
24844
25016
|
async function latestCredUpdates(db, projectId) {
|
|
24845
25017
|
const rows = await db.all(
|
|
24846
25018
|
`SELECT id, kind, state, detail, updated_at as updatedAt
|
|
@@ -24865,6 +25037,7 @@ async function latestCredUpdates(db, projectId) {
|
|
|
24865
25037
|
|
|
24866
25038
|
// src/application/read-model.ts
|
|
24867
25039
|
init_ws();
|
|
25040
|
+
init_farm2();
|
|
24868
25041
|
init_queue_view();
|
|
24869
25042
|
init_state_machine();
|
|
24870
25043
|
init_delivery_decision();
|
|
@@ -25076,7 +25249,7 @@ function configProposalRow(stored) {
|
|
|
25076
25249
|
}
|
|
25077
25250
|
|
|
25078
25251
|
// src/config-proposals/state-machine.ts
|
|
25079
|
-
import { nanoid as
|
|
25252
|
+
import { nanoid as nanoid30 } from "nanoid";
|
|
25080
25253
|
function rowToProposal(raw) {
|
|
25081
25254
|
return {
|
|
25082
25255
|
id: raw["id"],
|
|
@@ -25101,7 +25274,7 @@ var PROPOSAL_COLUMNS = `id, project_id as projectId, user_input as userInput, st
|
|
|
25101
25274
|
created_at as createdAt, updated_at as updatedAt`;
|
|
25102
25275
|
var PROPOSAL_SELECT = `SELECT ${PROPOSAL_COLUMNS} FROM config_proposals`;
|
|
25103
25276
|
async function createProposal(db, projectId, userInput) {
|
|
25104
|
-
const id = `cfp_${
|
|
25277
|
+
const id = `cfp_${nanoid30(16)}`;
|
|
25105
25278
|
const now = Date.now();
|
|
25106
25279
|
await db.run(
|
|
25107
25280
|
`INSERT INTO config_proposals
|
|
@@ -25803,7 +25976,7 @@ async function projectDetailOf(db, projectId, raw) {
|
|
|
25803
25976
|
actionErrors: getProjectActions(projectId)?.actionErrors ?? []
|
|
25804
25977
|
};
|
|
25805
25978
|
}
|
|
25806
|
-
function createReadModel(db) {
|
|
25979
|
+
function createReadModel(db, options = {}) {
|
|
25807
25980
|
const SUBMISSION_COLUMNS = `${DRAFT_COLUMNS},
|
|
25808
25981
|
(SELECT COUNT(*) FROM draft_tasks c WHERE c.parent_draft_id = draft_tasks.id) as childCount,
|
|
25809
25982
|
EXISTS (SELECT 1 FROM tasks t
|
|
@@ -26495,6 +26668,35 @@ function createReadModel(db) {
|
|
|
26495
26668
|
const p = await getPresence(db, { email: viewer.email ?? "", userId: viewer.subjectId });
|
|
26496
26669
|
return presenceRowOf(p, new Set(viewer.projectIds));
|
|
26497
26670
|
},
|
|
26671
|
+
// --- the account's fleet (DEV-927) ---
|
|
26672
|
+
async listFarms(viewer) {
|
|
26673
|
+
const isOperator = viewer.email !== null && (options.isOperator?.(viewer.email) ?? false);
|
|
26674
|
+
return await listFarms(db, { email: viewer.email ?? "", userId: viewer.subjectId, isSuperAdmin: isOperator });
|
|
26675
|
+
},
|
|
26676
|
+
async listProjectDeliverables(projectId) {
|
|
26677
|
+
const rows = await db.all(
|
|
26678
|
+
`SELECT id, tracker_issue_key AS "taskKey", title, phase, updated_at AS "updatedAt"
|
|
26679
|
+
FROM tasks
|
|
26680
|
+
WHERE project_id = ? AND (archived IS NULL OR archived = 0)
|
|
26681
|
+
AND phase IN ('TRACKING', 'AWAITING_CLUSTER', 'DONE', 'NEEDS_MANUAL')
|
|
26682
|
+
ORDER BY CASE WHEN phase IN ('TRACKING', 'AWAITING_CLUSTER') THEN 0 ELSE 1 END,
|
|
26683
|
+
updated_at DESC
|
|
26684
|
+
LIMIT 50`,
|
|
26685
|
+
projectId
|
|
26686
|
+
);
|
|
26687
|
+
const out = [];
|
|
26688
|
+
for (const r of rows) {
|
|
26689
|
+
const byRepo = await loadTaskBranchesByRepo(db, r.id);
|
|
26690
|
+
const repos = Object.entries(byRepo).map(([repoName, b]) => ({
|
|
26691
|
+
repoName,
|
|
26692
|
+
branchName: b.branchName,
|
|
26693
|
+
baseRef: b.baseRef
|
|
26694
|
+
}));
|
|
26695
|
+
if (repos.length === 0) continue;
|
|
26696
|
+
out.push({ taskKey: r.taskKey, title: r.title, phase: r.phase, updatedAt: Number(r.updatedAt), repos });
|
|
26697
|
+
}
|
|
26698
|
+
return out;
|
|
26699
|
+
},
|
|
26498
26700
|
async readNotificationSettings(subjectId) {
|
|
26499
26701
|
const user = await db.get(`SELECT 1 as hit FROM users WHERE id = ?`, subjectId);
|
|
26500
26702
|
if (!user) return null;
|
|
@@ -26635,7 +26837,7 @@ function createAuthorizer(db, config) {
|
|
|
26635
26837
|
// src/jobs/apply-result.ts
|
|
26636
26838
|
init_apply_overrides();
|
|
26637
26839
|
init_publish_delivery();
|
|
26638
|
-
import { nanoid as
|
|
26840
|
+
import { nanoid as nanoid31 } from "nanoid";
|
|
26639
26841
|
|
|
26640
26842
|
// src/drafts/result-handler.ts
|
|
26641
26843
|
init_dist();
|
|
@@ -27251,7 +27453,7 @@ async function applyJobResult(fx, job, result) {
|
|
|
27251
27453
|
size_bytes = excluded.size_bytes,
|
|
27252
27454
|
source_job_id = excluded.source_job_id,
|
|
27253
27455
|
created_at = excluded.created_at`,
|
|
27254
|
-
`art_${
|
|
27456
|
+
`art_${nanoid31(16)}`,
|
|
27255
27457
|
job.taskId,
|
|
27256
27458
|
a.path,
|
|
27257
27459
|
encoding,
|
|
@@ -28018,7 +28220,7 @@ init_state_machine2();
|
|
|
28018
28220
|
// src/drafts/publish.ts
|
|
28019
28221
|
init_ws();
|
|
28020
28222
|
init_state_machine2();
|
|
28021
|
-
import { nanoid as
|
|
28223
|
+
import { nanoid as nanoid32 } from "nanoid";
|
|
28022
28224
|
function firstNonBlank(...vals) {
|
|
28023
28225
|
for (const v of vals) {
|
|
28024
28226
|
if (v != null && v.trim() !== "") return v;
|
|
@@ -28026,7 +28228,7 @@ function firstNonBlank(...vals) {
|
|
|
28026
28228
|
return "";
|
|
28027
28229
|
}
|
|
28028
28230
|
async function dispatchPublish(db, draft, targetStatus, log) {
|
|
28029
|
-
const publishActionId = `dpub_${
|
|
28231
|
+
const publishActionId = `dpub_${nanoid32(16)}`;
|
|
28030
28232
|
const title = firstNonBlank(draft.editedTitle, draft.generatedTitle);
|
|
28031
28233
|
const body = firstNonBlank(draft.editedBody, draft.generatedBody);
|
|
28032
28234
|
const project = await db.get(`SELECT youtrack_project_key as youtrackProjectKey FROM projects WHERE id = ?`, draft.projectId);
|
|
@@ -28172,7 +28374,7 @@ async function maybeAdvancePackage(db, parentDraftId, log) {
|
|
|
28172
28374
|
log.info({ draftId: parentDraftId }, "package published (no links) \u2192 DONE");
|
|
28173
28375
|
return;
|
|
28174
28376
|
}
|
|
28175
|
-
const linkActionId = `dpkg_${
|
|
28377
|
+
const linkActionId = `dpkg_${nanoid32(16)}`;
|
|
28176
28378
|
const action = {
|
|
28177
28379
|
type: "apply_tracker_links",
|
|
28178
28380
|
draftId: parentDraftId,
|
|
@@ -28252,7 +28454,85 @@ async function discardDraft(db, draft, log) {
|
|
|
28252
28454
|
init_dist2();
|
|
28253
28455
|
init_regimen_write();
|
|
28254
28456
|
init_ws();
|
|
28255
|
-
|
|
28457
|
+
|
|
28458
|
+
// src/farm/provision.ts
|
|
28459
|
+
init_ws();
|
|
28460
|
+
init_pool_spec();
|
|
28461
|
+
import { nanoid as nanoid33 } from "nanoid";
|
|
28462
|
+
var pending3 = /* @__PURE__ */ new Map();
|
|
28463
|
+
function awaitResult(requestId2, send, timeoutMs) {
|
|
28464
|
+
if (!send()) return Promise.reject(new Error("farm_offline"));
|
|
28465
|
+
return new Promise((resolve2, reject) => {
|
|
28466
|
+
const timer = setTimeout(() => {
|
|
28467
|
+
pending3.delete(requestId2);
|
|
28468
|
+
reject(new Error("farm_timeout"));
|
|
28469
|
+
}, timeoutMs);
|
|
28470
|
+
pending3.set(requestId2, { resolve: resolve2, reject, timer });
|
|
28471
|
+
});
|
|
28472
|
+
}
|
|
28473
|
+
function requestProvision(farmId, projectId, sealedBundle, timeoutMs) {
|
|
28474
|
+
const requestId2 = nanoid33(16);
|
|
28475
|
+
return awaitResult(
|
|
28476
|
+
requestId2,
|
|
28477
|
+
() => sendToFarm(farmId, { type: "provision_project", requestId: requestId2, projectId, sealedBundle }),
|
|
28478
|
+
timeoutMs
|
|
28479
|
+
);
|
|
28480
|
+
}
|
|
28481
|
+
function requestDelete(farmId, projectId, timeoutMs) {
|
|
28482
|
+
const requestId2 = nanoid33(16);
|
|
28483
|
+
return awaitResult(
|
|
28484
|
+
requestId2,
|
|
28485
|
+
() => sendToFarm(farmId, { type: "delete_project", requestId: requestId2, projectId }),
|
|
28486
|
+
timeoutMs
|
|
28487
|
+
);
|
|
28488
|
+
}
|
|
28489
|
+
async function requestFarmPoolSync(db, farmId) {
|
|
28490
|
+
const accounts = await computeFarmPoolSpec(db, farmId);
|
|
28491
|
+
sendToFarm(farmId, buildSyncWorkerPoolMessage(accounts));
|
|
28492
|
+
}
|
|
28493
|
+
|
|
28494
|
+
// src/runner/provision.ts
|
|
28495
|
+
init_ws();
|
|
28496
|
+
import { nanoid as nanoid34 } from "nanoid";
|
|
28497
|
+
var pending4 = /* @__PURE__ */ new Map();
|
|
28498
|
+
function awaitResult2(requestId2, send, timeoutMs) {
|
|
28499
|
+
if (!send()) return Promise.reject(new Error("runner_offline"));
|
|
28500
|
+
return new Promise((resolve2, reject) => {
|
|
28501
|
+
const timer = setTimeout(() => {
|
|
28502
|
+
pending4.delete(requestId2);
|
|
28503
|
+
reject(new Error("runner_timeout"));
|
|
28504
|
+
}, timeoutMs);
|
|
28505
|
+
pending4.set(requestId2, { resolve: resolve2, reject, timer });
|
|
28506
|
+
});
|
|
28507
|
+
}
|
|
28508
|
+
function requestGhRunnersProvision(runnerId, projectId, sealedBundle, timeoutMs) {
|
|
28509
|
+
const requestId2 = nanoid34(16);
|
|
28510
|
+
return awaitResult2(
|
|
28511
|
+
requestId2,
|
|
28512
|
+
() => sendToRunner(runnerId, { type: "provision_gh_runners", requestId: requestId2, projectId, sealedBundle }),
|
|
28513
|
+
timeoutMs
|
|
28514
|
+
);
|
|
28515
|
+
}
|
|
28516
|
+
function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
|
|
28517
|
+
const requestId2 = nanoid34(16);
|
|
28518
|
+
return awaitResult2(
|
|
28519
|
+
requestId2,
|
|
28520
|
+
() => sendToRunner(runnerId, {
|
|
28521
|
+
type: "delete_gh_runners",
|
|
28522
|
+
requestId: requestId2,
|
|
28523
|
+
projectId,
|
|
28524
|
+
...sealedBundle ? { sealedBundle } : {}
|
|
28525
|
+
}),
|
|
28526
|
+
timeoutMs
|
|
28527
|
+
);
|
|
28528
|
+
}
|
|
28529
|
+
|
|
28530
|
+
// src/application/write-model.ts
|
|
28531
|
+
init_farm2();
|
|
28532
|
+
init_runner2();
|
|
28533
|
+
init_init_status();
|
|
28534
|
+
init_sandbox_reg_tokens();
|
|
28535
|
+
init_abuse_throttle();
|
|
28256
28536
|
|
|
28257
28537
|
// src/projects/forget.ts
|
|
28258
28538
|
init_legacy_cred_bundles();
|
|
@@ -28296,8 +28576,57 @@ async function deleteProjectData(db, projectId) {
|
|
|
28296
28576
|
});
|
|
28297
28577
|
}
|
|
28298
28578
|
|
|
28579
|
+
// src/runner/bindings.ts
|
|
28580
|
+
function parseGhRunnerBindings(json) {
|
|
28581
|
+
if (!json) return [];
|
|
28582
|
+
try {
|
|
28583
|
+
const parsed = JSON.parse(json);
|
|
28584
|
+
return Array.isArray(parsed.bindings) ? parsed.bindings : [];
|
|
28585
|
+
} catch {
|
|
28586
|
+
return [];
|
|
28587
|
+
}
|
|
28588
|
+
}
|
|
28589
|
+
function serialize(bindings) {
|
|
28590
|
+
return JSON.stringify({ bindings });
|
|
28591
|
+
}
|
|
28592
|
+
async function upsertGhRunnerBinding(db, projectId, binding) {
|
|
28593
|
+
await db.tx(async (tx) => {
|
|
28594
|
+
const row = await tx.get(
|
|
28595
|
+
`SELECT gh_runner_bindings_json as bindingsJson FROM projects WHERE id = ?`,
|
|
28596
|
+
projectId
|
|
28597
|
+
);
|
|
28598
|
+
if (!row) return;
|
|
28599
|
+
const bindings = parseGhRunnerBindings(row.bindingsJson).filter(
|
|
28600
|
+
(b) => !(b.owner === binding.owner && b.repo === binding.repo)
|
|
28601
|
+
);
|
|
28602
|
+
bindings.push(binding);
|
|
28603
|
+
await tx.run(
|
|
28604
|
+
`UPDATE projects SET gh_runner_bindings_json = ? WHERE id = ?`,
|
|
28605
|
+
serialize(bindings),
|
|
28606
|
+
projectId
|
|
28607
|
+
);
|
|
28608
|
+
});
|
|
28609
|
+
}
|
|
28610
|
+
async function removeGhRunnerBinding(db, projectId, owner, repo) {
|
|
28611
|
+
return db.tx(async (tx) => {
|
|
28612
|
+
const row = await tx.get(
|
|
28613
|
+
`SELECT gh_runner_bindings_json as bindingsJson FROM projects WHERE id = ?`,
|
|
28614
|
+
projectId
|
|
28615
|
+
);
|
|
28616
|
+
if (!row) return false;
|
|
28617
|
+
const bindings = parseGhRunnerBindings(row.bindingsJson);
|
|
28618
|
+
const kept = bindings.filter((b) => !(b.owner === owner && b.repo === repo));
|
|
28619
|
+
if (kept.length === bindings.length) return false;
|
|
28620
|
+
await tx.run(
|
|
28621
|
+
`UPDATE projects SET gh_runner_bindings_json = ? WHERE id = ?`,
|
|
28622
|
+
serialize(kept),
|
|
28623
|
+
projectId
|
|
28624
|
+
);
|
|
28625
|
+
return true;
|
|
28626
|
+
});
|
|
28627
|
+
}
|
|
28628
|
+
|
|
28299
28629
|
// src/application/write-model.ts
|
|
28300
|
-
init_bindings();
|
|
28301
28630
|
init_tracking_commands();
|
|
28302
28631
|
|
|
28303
28632
|
// src/config-proposals/apply.ts
|
|
@@ -28373,10 +28702,27 @@ function conflict(reason) {
|
|
|
28373
28702
|
function stale() {
|
|
28374
28703
|
return { ok: false, refusal: "stale_revision" };
|
|
28375
28704
|
}
|
|
28705
|
+
function notFound() {
|
|
28706
|
+
return { ok: false, refusal: "not_found" };
|
|
28707
|
+
}
|
|
28708
|
+
var FARM_PROVISION_TIMEOUT_MS = 6e4;
|
|
28709
|
+
var GH_RUNNER_RELAY_TIMEOUT_MS = 12e4;
|
|
28710
|
+
function nodeUnavailable(capability, err) {
|
|
28711
|
+
const reason = err instanceof Error ? err.message : `${capability}_error`;
|
|
28712
|
+
const what = capability === "farm" ? "The farm" : "The runner host";
|
|
28713
|
+
return {
|
|
28714
|
+
ok: false,
|
|
28715
|
+
refusal: "capability_unavailable",
|
|
28716
|
+
capability,
|
|
28717
|
+
message: reason === `${capability}_timeout` ? `${what} did not answer in time. Try again once it is responsive.` : reason === `${capability}_offline` ? `${what} is offline. Start it and try again.` : `${what} could not be reached for this. Try again in a moment.`,
|
|
28718
|
+
retryable: true
|
|
28719
|
+
};
|
|
28720
|
+
}
|
|
28376
28721
|
function taskState(row) {
|
|
28377
28722
|
return { ...row, revision: String(row.updatedAt) };
|
|
28378
28723
|
}
|
|
28379
|
-
|
|
28724
|
+
var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
28725
|
+
function createWriteModel(db, log, hooks = {}) {
|
|
28380
28726
|
async function readTaskRow(projectId, key) {
|
|
28381
28727
|
const row = await db.get(
|
|
28382
28728
|
`SELECT project_id as projectId, tracker_issue_key as key, phase,
|
|
@@ -28451,6 +28797,13 @@ function createWriteModel(db, log) {
|
|
|
28451
28797
|
if (sandboxId !== null) sendToSandbox(sandboxId, { type: "pause_job", jobId: stored.sourceJobId });
|
|
28452
28798
|
await cancelActiveJobs(db, `config::${stored.id}`);
|
|
28453
28799
|
}
|
|
28800
|
+
function isOperator(actor) {
|
|
28801
|
+
return actor.email !== null && (hooks.isOperator?.(actor.email) ?? false);
|
|
28802
|
+
}
|
|
28803
|
+
async function ghRunnerHost(runnerId, actor) {
|
|
28804
|
+
const owns = isOperator(actor) ? await db.get(`SELECT id FROM runners WHERE id = ?`, runnerId) !== void 0 : await viewerOwnsRunner(db, { email: actor.email ?? "", userId: actor.subjectId }, runnerId);
|
|
28805
|
+
return owns ? ok(true) : notFound();
|
|
28806
|
+
}
|
|
28454
28807
|
return {
|
|
28455
28808
|
async readTask(projectId, taskKey) {
|
|
28456
28809
|
const row = await readTaskRow(projectId, taskKey);
|
|
@@ -28922,6 +29275,153 @@ function createWriteModel(db, log) {
|
|
|
28922
29275
|
* daemon behind it. The Context Repo remote is not touched: what the
|
|
28923
29276
|
* pipeline wrote about the work outlives the record of the project.
|
|
28924
29277
|
*/
|
|
29278
|
+
// --- registration and the account's fleet (DEV-927) ---
|
|
29279
|
+
async createProject({ project, owner }) {
|
|
29280
|
+
const trackerProvider = project.trackerProvider;
|
|
29281
|
+
const inserted = await db.tx(async (tx) => {
|
|
29282
|
+
const existing = await tx.get(`SELECT id FROM projects WHERE id = ?`, project.projectId);
|
|
29283
|
+
if (existing) return conflict("project_already_exists");
|
|
29284
|
+
const now = Date.now();
|
|
29285
|
+
await tx.run(
|
|
29286
|
+
`INSERT INTO projects (id, name, automation_level, youtrack_project_key, tracker_provider,
|
|
29287
|
+
repos_json, owner_email, owner_user_id, language_json,
|
|
29288
|
+
provisioning_status, context_repo_json, provisioned_at, provisioning_error, created_at)
|
|
29289
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'provisioned', ?, ?, NULL, ?)`,
|
|
29290
|
+
project.projectId,
|
|
29291
|
+
project.name,
|
|
29292
|
+
project.automationLevel,
|
|
29293
|
+
project.trackerProjectKey,
|
|
29294
|
+
trackerProvider,
|
|
29295
|
+
JSON.stringify(
|
|
29296
|
+
project.repos.map((r) => ({
|
|
29297
|
+
name: r.name,
|
|
29298
|
+
host: r.host,
|
|
29299
|
+
owner: r.owner,
|
|
29300
|
+
repoName: r.repoName,
|
|
29301
|
+
...r.defaultBranch ? { defaultBranch: r.defaultBranch } : {},
|
|
29302
|
+
...r.mountAs ? { mountAs: r.mountAs } : {},
|
|
29303
|
+
...r.mirror ? { mirror: { host: r.mirror.host, owner: r.mirror.owner, repoName: r.mirror.repoName } } : {}
|
|
29304
|
+
}))
|
|
29305
|
+
),
|
|
29306
|
+
owner.email,
|
|
29307
|
+
owner.subjectId,
|
|
29308
|
+
project.language === null ? null : JSON.stringify(project.language),
|
|
29309
|
+
JSON.stringify(project.contextRepo),
|
|
29310
|
+
now,
|
|
29311
|
+
now
|
|
29312
|
+
);
|
|
29313
|
+
return ok({ projectId: project.projectId });
|
|
29314
|
+
});
|
|
29315
|
+
if (!inserted.ok) return inserted;
|
|
29316
|
+
await beginInitTracking(db, project.projectId, "registered");
|
|
29317
|
+
log.info({ projectId: project.projectId, contextRepo: project.contextRepo }, "project registered, Context Repo provisioned by the client");
|
|
29318
|
+
hooks.projectRegistered?.({
|
|
29319
|
+
projectId: project.projectId,
|
|
29320
|
+
name: project.name,
|
|
29321
|
+
automationLevel: project.automationLevel,
|
|
29322
|
+
ownerEmail: owner.email,
|
|
29323
|
+
repoCount: project.repos.length
|
|
29324
|
+
});
|
|
29325
|
+
return inserted;
|
|
29326
|
+
},
|
|
29327
|
+
async provisionOnFarm({ farmId, projectId, sealedBundle, actor }) {
|
|
29328
|
+
const usable = isOperator(actor) ? await db.get(`SELECT id FROM farms WHERE id = ?`, farmId) !== void 0 : await viewerOwnsFarm(db, { email: actor.email ?? "", userId: actor.subjectId }, farmId);
|
|
29329
|
+
if (!usable) return notFound();
|
|
29330
|
+
await beginInitTracking(db, projectId, "farm_provisioning");
|
|
29331
|
+
let result;
|
|
29332
|
+
try {
|
|
29333
|
+
result = await requestProvision(farmId, projectId, sealedBundle, FARM_PROVISION_TIMEOUT_MS);
|
|
29334
|
+
} catch (err) {
|
|
29335
|
+
const reason = err instanceof Error ? err.message : "provision_failed";
|
|
29336
|
+
await recordInitError(
|
|
29337
|
+
db,
|
|
29338
|
+
projectId,
|
|
29339
|
+
reason === "farm_offline" ? "the hosting farm is offline" : reason === "farm_timeout" ? "the hosting farm did not respond in time" : `farm provisioning failed: ${reason}`
|
|
29340
|
+
);
|
|
29341
|
+
return nodeUnavailable("farm", err);
|
|
29342
|
+
}
|
|
29343
|
+
if (result.ok) {
|
|
29344
|
+
await db.run(`UPDATE projects SET farm_id = ? WHERE id = ?`, farmId, projectId);
|
|
29345
|
+
await advanceInitPhase(db, projectId, "daemon_starting");
|
|
29346
|
+
requestFarmPoolSync(db, farmId).catch(
|
|
29347
|
+
(err) => log.warn({ farmId, err }, "farm pool sync after provision failed")
|
|
29348
|
+
);
|
|
29349
|
+
} else {
|
|
29350
|
+
await recordInitError(db, projectId, `farm provisioning failed: ${result.error ?? "unknown error"}`);
|
|
29351
|
+
}
|
|
29352
|
+
return ok({ ok: result.ok, daemonStatus: result.daemonStatus ?? null, error: result.error ?? null });
|
|
29353
|
+
},
|
|
29354
|
+
async provisionGhRunners({ runnerId, projectId, sealedBundle, actor }) {
|
|
29355
|
+
const guard = await ghRunnerHost(runnerId, actor);
|
|
29356
|
+
if (!guard.ok) return guard;
|
|
29357
|
+
let result;
|
|
29358
|
+
try {
|
|
29359
|
+
result = await requestGhRunnersProvision(runnerId, projectId, sealedBundle, GH_RUNNER_RELAY_TIMEOUT_MS);
|
|
29360
|
+
} catch (err) {
|
|
29361
|
+
return nodeUnavailable("runner", err);
|
|
29362
|
+
}
|
|
29363
|
+
const runners = result.runners ?? [];
|
|
29364
|
+
if (result.ok) {
|
|
29365
|
+
const connectedAt = Date.now();
|
|
29366
|
+
for (const r of runners) {
|
|
29367
|
+
await upsertGhRunnerBinding(db, projectId, {
|
|
29368
|
+
owner: r.owner,
|
|
29369
|
+
repo: r.repo,
|
|
29370
|
+
runnerId,
|
|
29371
|
+
connectedAt,
|
|
29372
|
+
status: r.status,
|
|
29373
|
+
...r.error ? { error: r.error } : {},
|
|
29374
|
+
...r.labels ? { labels: r.labels } : {}
|
|
29375
|
+
});
|
|
29376
|
+
}
|
|
29377
|
+
}
|
|
29378
|
+
return ok({
|
|
29379
|
+
ok: result.ok,
|
|
29380
|
+
runners: runners.map((r) => ({ owner: r.owner, repo: r.repo, status: r.status, error: r.error ?? null, labels: r.labels ?? [] })),
|
|
29381
|
+
error: result.error ?? null
|
|
29382
|
+
});
|
|
29383
|
+
},
|
|
29384
|
+
async teardownGhRunners({ runnerId, projectId, sealedBundle, repo, actor }) {
|
|
29385
|
+
const guard = await ghRunnerHost(runnerId, actor);
|
|
29386
|
+
if (!guard.ok) return guard;
|
|
29387
|
+
let result;
|
|
29388
|
+
try {
|
|
29389
|
+
result = await requestGhRunnersDelete(runnerId, projectId, sealedBundle ?? void 0, GH_RUNNER_RELAY_TIMEOUT_MS);
|
|
29390
|
+
} catch (err) {
|
|
29391
|
+
return nodeUnavailable("runner", err);
|
|
29392
|
+
}
|
|
29393
|
+
if (result.ok && repo !== null) {
|
|
29394
|
+
await removeGhRunnerBinding(db, projectId, repo.owner, repo.repo);
|
|
29395
|
+
}
|
|
29396
|
+
return ok({ ok: result.ok, error: result.error ?? null });
|
|
29397
|
+
},
|
|
29398
|
+
async enqueueProjectIndex({ projectId, kind }) {
|
|
29399
|
+
const p = await db.get(`SELECT id, context_repo_json as contextRepoJson FROM projects WHERE id = ?`, projectId);
|
|
29400
|
+
if (!p) return notFound();
|
|
29401
|
+
if (!p.contextRepoJson) return conflict("context_repo_not_provisioned");
|
|
29402
|
+
const jobId = kind === "backfill" ? await enqueueBackfillJob(db, projectId) : await enqueueIndexJob(db, projectId);
|
|
29403
|
+
await advanceInitPhase(db, projectId, "indexing");
|
|
29404
|
+
log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
|
|
29405
|
+
return ok({ jobId });
|
|
29406
|
+
},
|
|
29407
|
+
async issueWorkerRegistrationToken({ owner }) {
|
|
29408
|
+
if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
|
|
29409
|
+
if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
|
|
29410
|
+
log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
|
|
29411
|
+
return { ok: false, refusal: "rate_limited" };
|
|
29412
|
+
}
|
|
29413
|
+
const issued = await issueEphemeralSandboxRegToken(db, owner.email);
|
|
29414
|
+
log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
|
|
29415
|
+
return ok(issued);
|
|
29416
|
+
},
|
|
29417
|
+
async reportInitStatus({ projectId, phase, error }) {
|
|
29418
|
+
const recorded = await advanceInitPhase(db, projectId, phase);
|
|
29419
|
+
if (error !== null) {
|
|
29420
|
+
const cur = await getInitStatus(db, projectId);
|
|
29421
|
+
if (cur && cur.phase === phase) await recordInitError(db, projectId, error);
|
|
29422
|
+
}
|
|
29423
|
+
return ok({ recorded });
|
|
29424
|
+
},
|
|
28925
29425
|
async deleteProject({ projectId }) {
|
|
28926
29426
|
const row = await db.get(`SELECT farm_id as farmId FROM projects WHERE id = ?`, projectId);
|
|
28927
29427
|
if (!row) return conflict("project_not_found");
|
|
@@ -29289,9 +29789,9 @@ function createDirectPipeline(deps) {
|
|
|
29289
29789
|
const materialization = {
|
|
29290
29790
|
async next(projectId) {
|
|
29291
29791
|
for (; ; ) {
|
|
29292
|
-
const
|
|
29293
|
-
if (!
|
|
29294
|
-
const prepared = await prepareDispatch(db,
|
|
29792
|
+
const pending5 = await nextPendingProjectAction(db, projectId);
|
|
29793
|
+
if (!pending5) return null;
|
|
29794
|
+
const prepared = await prepareDispatch(db, pending5.id, log);
|
|
29295
29795
|
if (prepared.kind === "settled") continue;
|
|
29296
29796
|
if (prepared.kind !== "ready") return null;
|
|
29297
29797
|
await markDispatched(db, prepared.actionId);
|
|
@@ -29498,7 +29998,7 @@ async function compose(db, config, log, issueGrants) {
|
|
|
29498
29998
|
const notify2 = { db, config, channels: [new EmailChannel(mailer)], log };
|
|
29499
29999
|
setNotifyDeps(notify2);
|
|
29500
30000
|
const authorizer = createAuthorizer(db, config);
|
|
29501
|
-
const reads = createReadOperations(createReadModel(db), authorizer);
|
|
30001
|
+
const reads = createReadOperations(createReadModel(db, { isOperator: (email) => isSuperUser(config, email) }), authorizer);
|
|
29502
30002
|
const pipeline = createDirectPipeline({
|
|
29503
30003
|
db,
|
|
29504
30004
|
log,
|