@agentconnect.md/setup 1.44.0-rc.61 → 1.44.0-rc.62
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/dist/index.js +118 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56542,6 +56542,30 @@ const SECRET_ENVELOPE_PREFIX = "acv1:";
|
|
|
56542
56542
|
function effectiveOrgKeyPrefix(transitKey, configured) {
|
|
56543
56543
|
return configured ?? `${transitKey}-org-`;
|
|
56544
56544
|
}
|
|
56545
|
+
//#endregion
|
|
56546
|
+
//#region ../control-plane/dist/gitlab/config.js
|
|
56547
|
+
/** The default value of the host axis: an unset base URL means GitLab.com (§24.1). */
|
|
56548
|
+
const GITLAB_DEFAULT_BASE_URL = "https://gitlab.com";
|
|
56549
|
+
/** The public callback path, in its gateway form (deploy-public-url-prefix decision). */
|
|
56550
|
+
const GITLAB_OAUTH_CALLBACK_PATH = "/v1/gitlab/oauth/callback";
|
|
56551
|
+
/** The one normalization of the host axis (§24.1); downstream sees only its
|
|
56552
|
+
* result. HTTPS, no userinfo/query/fragment, lower-cased host, explicit
|
|
56553
|
+
* non-default port kept, no trailing slash, and a path prefix preserved —
|
|
56554
|
+
* a relative URL root is a first-class install shape. */
|
|
56555
|
+
function normalizeGitlabBaseUrl(raw) {
|
|
56556
|
+
const trimmed = raw.trim();
|
|
56557
|
+
let url;
|
|
56558
|
+
try {
|
|
56559
|
+
url = new URL(trimmed);
|
|
56560
|
+
} catch {
|
|
56561
|
+
throw new Error("gitlab base url must be an absolute URL");
|
|
56562
|
+
}
|
|
56563
|
+
if (url.protocol !== "https:") throw new Error("gitlab base url must use https");
|
|
56564
|
+
if (url.username !== "" || url.password !== "") throw new Error("gitlab base url must not carry userinfo");
|
|
56565
|
+
if (url.search !== "") throw new Error("gitlab base url must not carry a query");
|
|
56566
|
+
if (url.hash !== "") throw new Error("gitlab base url must not carry a fragment");
|
|
56567
|
+
return `https://${url.host}${url.pathname.replace(/\/+$/, "")}`;
|
|
56568
|
+
}
|
|
56545
56569
|
const HttpUrlSchema = string().url().superRefine((value, ctx) => {
|
|
56546
56570
|
const url = new URL(value);
|
|
56547
56571
|
if (url.protocol !== "http:" && url.protocol !== "https:") ctx.addIssue({
|
|
@@ -56605,7 +56629,20 @@ const GithubAppSchema = preprocess(withoutProviderUrlSnapshot, strictObject({
|
|
|
56605
56629
|
/** Whether Relay should accept GitHub webhook delivery for this App. Omitted means enabled. */
|
|
56606
56630
|
webhookEnabled: boolean().optional()
|
|
56607
56631
|
}));
|
|
56608
|
-
const
|
|
56632
|
+
const GitlabBaseUrlSchema = string().trim().min(1).superRefine((value, ctx) => {
|
|
56633
|
+
try {
|
|
56634
|
+
normalizeGitlabBaseUrl(value);
|
|
56635
|
+
} catch (error) {
|
|
56636
|
+
ctx.addIssue({
|
|
56637
|
+
code: "custom",
|
|
56638
|
+
message: error.message
|
|
56639
|
+
});
|
|
56640
|
+
}
|
|
56641
|
+
});
|
|
56642
|
+
const GitlabAppSchema = preprocess(withoutProviderUrlSnapshot, strictObject({
|
|
56643
|
+
clientId: string().trim().min(1),
|
|
56644
|
+
baseUrl: GitlabBaseUrlSchema.nullable().optional()
|
|
56645
|
+
}));
|
|
56609
56646
|
const SlackAppSchema = preprocess(withoutProviderUrlSnapshot, strictObject({
|
|
56610
56647
|
appId: string().trim().min(1),
|
|
56611
56648
|
clientId: string().trim().min(1)
|
|
@@ -56713,6 +56750,27 @@ var DeploymentConfigSecretRefreshRequiredError = class extends Error {
|
|
|
56713
56750
|
this.name = "DeploymentConfigSecretRefreshRequiredError";
|
|
56714
56751
|
}
|
|
56715
56752
|
};
|
|
56753
|
+
/** The named reason a GitLab base-URL change is refused (gitlab-com-integration.md §24.1). */
|
|
56754
|
+
const GITLAB_BASE_URL_LOCKED_REASON = "gitlab_base_url_locked";
|
|
56755
|
+
/** Retargeting would send the old host's credentials and host-relative numeric
|
|
56756
|
+
* ids to a new one, because no GitLab row carries instance provenance (§24.1):
|
|
56757
|
+
* the axis is immutable while any GitLab state exists. */
|
|
56758
|
+
var DeploymentConfigGitlabBaseUrlLockedError = class extends Error {
|
|
56759
|
+
currentBaseUrl;
|
|
56760
|
+
requestedBaseUrl;
|
|
56761
|
+
code = GITLAB_BASE_URL_LOCKED_REASON;
|
|
56762
|
+
constructor(currentBaseUrl, requestedBaseUrl) {
|
|
56763
|
+
super(`the GitLab instance base URL is locked while GitLab state exists (${currentBaseUrl} → ${requestedBaseUrl}); disconnect every GitLab project first`);
|
|
56764
|
+
this.currentBaseUrl = currentBaseUrl;
|
|
56765
|
+
this.requestedBaseUrl = requestedBaseUrl;
|
|
56766
|
+
this.name = "DeploymentConfigGitlabBaseUrlLockedError";
|
|
56767
|
+
}
|
|
56768
|
+
};
|
|
56769
|
+
/** The instance a document's GitLab entry selects; absent means GitLab.com (§24.1). */
|
|
56770
|
+
function effectiveGitlabBaseUrl(values) {
|
|
56771
|
+
const configured = values.gitlab?.baseUrl;
|
|
56772
|
+
return configured ? normalizeGitlabBaseUrl(configured) : GITLAB_DEFAULT_BASE_URL;
|
|
56773
|
+
}
|
|
56716
56774
|
function parseDeploymentConfigValues(schemaVersion, values) {
|
|
56717
56775
|
if (schemaVersion !== 1) throw new Error(`unsupported deployment configuration schema version: ${schemaVersion}`);
|
|
56718
56776
|
return DeploymentConfigValuesV1Schema.parse(values);
|
|
@@ -56721,7 +56779,7 @@ function deploymentSecretsRequiringRefresh(previous, next) {
|
|
|
56721
56779
|
const githubAppChanged = next.github && previous?.github?.appId !== next.github.appId;
|
|
56722
56780
|
const githubClientChanged = next.github !== null && next.github.clientId !== null && previous?.github?.clientId !== next.github.clientId;
|
|
56723
56781
|
const githubWebhookEnabled = next.github !== null && next.github.webhookEnabled !== false;
|
|
56724
|
-
const gitlabClientChanged = next.gitlab != null && previous?.gitlab?.clientId !== next.gitlab.clientId;
|
|
56782
|
+
const gitlabClientChanged = next.gitlab != null && (previous?.gitlab?.clientId !== next.gitlab.clientId || previous != null && effectiveGitlabBaseUrl(previous) !== effectiveGitlabBaseUrl(next));
|
|
56725
56783
|
const slackIdentityChanged = next.slack && (previous?.slack?.appId !== next.slack.appId || previous?.slack?.clientId !== next.slack.clientId);
|
|
56726
56784
|
const feishuIdentityChanged = next.feishu && previous?.feishu?.loginAppId !== next.feishu.loginAppId;
|
|
56727
56785
|
const larkIdentityChanged = next.lark && previous?.lark?.loginAppId !== next.lark.loginAppId;
|
|
@@ -56845,17 +56903,26 @@ var DeploymentConfigService = class {
|
|
|
56845
56903
|
}
|
|
56846
56904
|
};
|
|
56847
56905
|
//#endregion
|
|
56848
|
-
//#region ../control-plane/dist/persistence/repositories/
|
|
56906
|
+
//#region ../control-plane/dist/persistence/repositories/gitlab-axis.js
|
|
56849
56907
|
/**
|
|
56850
|
-
*
|
|
56908
|
+
* The deployment-wide GitLab host-axis fence (gitlab-com-integration.md §24.1).
|
|
56851
56909
|
*
|
|
56852
|
-
*
|
|
56853
|
-
*
|
|
56854
|
-
*
|
|
56855
|
-
*
|
|
56910
|
+
* No GitLab row carries instance provenance, so the axis may only change while
|
|
56911
|
+
* no GitLab state exists. That is a two-sided invariant, and one advisory key
|
|
56912
|
+
* carries both sides: the deployment-config writer takes it EXCLUSIVELY around
|
|
56913
|
+
* its state count, and every transaction that creates first-of-its-kind GitLab
|
|
56914
|
+
* state takes it SHARED and then proves the persisted axis still matches the
|
|
56915
|
+
* base its in-flight operation was composed against. Without the shared side a
|
|
56916
|
+
* count of zero and a concurrent connect could both commit, leaving one host's
|
|
56917
|
+
* credentials to be presented to another after the next restart.
|
|
56856
56918
|
*/
|
|
56857
|
-
const DEPLOYMENT_CONFIG_ID = 1;
|
|
56858
56919
|
const DEPLOYMENT_CONFIG_LOCK_KEY = "agentconnect:deployment-config";
|
|
56920
|
+
/** The config writer's side: nothing else may commit GitLab state alongside it. */
|
|
56921
|
+
async function lockAxisExclusive(tx) {
|
|
56922
|
+
await tx.$queryRaw(sql`SELECT pg_advisory_xact_lock(hashtextextended(${DEPLOYMENT_CONFIG_LOCK_KEY}, 0)) IS NULL AS "locked"`);
|
|
56923
|
+
}
|
|
56924
|
+
//#endregion
|
|
56925
|
+
//#region ../control-plane/dist/persistence/repositories/deployment-config.repo.js
|
|
56859
56926
|
const adminSelect = {
|
|
56860
56927
|
schemaVersion: true,
|
|
56861
56928
|
revision: true,
|
|
@@ -56908,30 +56975,50 @@ function toRuntime(row) {
|
|
|
56908
56975
|
updatedAt: row.updatedAt
|
|
56909
56976
|
};
|
|
56910
56977
|
}
|
|
56978
|
+
/** Does any GitLab state still bind this deployment to its instance (§24.1)? A
|
|
56979
|
+
* `disconnected` connection is credential-free history and does not; a binding
|
|
56980
|
+
* (`cleanup_pending` included), an account, a hook, or a claim carrying a
|
|
56981
|
+
* tombstone or an unfinished cleanup obligation does. */
|
|
56982
|
+
async function gitlabStateExists(tx) {
|
|
56983
|
+
const [connections, bindings, accounts, hooks, claims] = await Promise.all([
|
|
56984
|
+
tx.gitlabConnection.count({ where: { NOT: { state: "disconnected" } } }),
|
|
56985
|
+
tx.gitlabProjectBinding.count(),
|
|
56986
|
+
tx.gitlabAgentAccount.count(),
|
|
56987
|
+
tx.hookDef.count({ where: { kind: "gitlab" } }),
|
|
56988
|
+
tx.codeHostRepositoryClaim.count({ where: { provider: "gitlab" } })
|
|
56989
|
+
]);
|
|
56990
|
+
return connections + bindings + accounts + hooks + claims > 0;
|
|
56991
|
+
}
|
|
56911
56992
|
var PgDeploymentConfigRepository = class {
|
|
56912
56993
|
prisma;
|
|
56913
|
-
|
|
56994
|
+
/** The axis the process-level fallback selects, for the FIRST persisted
|
|
56995
|
+
* document: with no row, `GITLAB_BASE_URL` is what the running deployment
|
|
56996
|
+
* already serves, so it — not GitLab.com — is what a first write must match. */
|
|
56997
|
+
envGitlabBaseUrl;
|
|
56998
|
+
constructor(prisma, envGitlabBaseUrl) {
|
|
56914
56999
|
this.prisma = prisma;
|
|
57000
|
+
const raw = envGitlabBaseUrl?.trim();
|
|
57001
|
+
this.envGitlabBaseUrl = raw ? normalizeGitlabBaseUrl(raw) : GITLAB_DEFAULT_BASE_URL;
|
|
56915
57002
|
}
|
|
56916
57003
|
async readAdmin() {
|
|
56917
57004
|
const row = await this.prisma.deploymentConfig.findUnique({
|
|
56918
|
-
where: { id:
|
|
57005
|
+
where: { id: 1 },
|
|
56919
57006
|
select: adminSelect
|
|
56920
57007
|
});
|
|
56921
57008
|
return row ? toAdmin(row) : null;
|
|
56922
57009
|
}
|
|
56923
57010
|
async readRuntime() {
|
|
56924
57011
|
const row = await this.prisma.deploymentConfig.findUnique({
|
|
56925
|
-
where: { id:
|
|
57012
|
+
where: { id: 1 },
|
|
56926
57013
|
select: runtimeSelect
|
|
56927
57014
|
});
|
|
56928
57015
|
return row ? toRuntime(row) : null;
|
|
56929
57016
|
}
|
|
56930
57017
|
async replace(input) {
|
|
56931
57018
|
return withTx(this.prisma, async (tx) => {
|
|
56932
|
-
await tx
|
|
57019
|
+
await lockAxisExclusive(tx);
|
|
56933
57020
|
const current = await tx.deploymentConfig.findUnique({
|
|
56934
|
-
where: { id:
|
|
57021
|
+
where: { id: 1 },
|
|
56935
57022
|
select: {
|
|
56936
57023
|
revision: true,
|
|
56937
57024
|
schemaVersion: true,
|
|
@@ -56941,10 +57028,13 @@ var PgDeploymentConfigRepository = class {
|
|
|
56941
57028
|
const actualRevision = current?.revision ?? 0;
|
|
56942
57029
|
if (actualRevision !== input.expectedRevision) throw new DeploymentConfigConflictError(input.expectedRevision, actualRevision);
|
|
56943
57030
|
const previousValues = current ? parseDeploymentConfigValues(current.schemaVersion, current.values) : null;
|
|
57031
|
+
const previousBaseUrl = previousValues ? effectiveGitlabBaseUrl(previousValues) : this.envGitlabBaseUrl;
|
|
57032
|
+
const nextBaseUrl = effectiveGitlabBaseUrl(input.values);
|
|
57033
|
+
if (previousBaseUrl !== nextBaseUrl && await gitlabStateExists(tx)) throw new DeploymentConfigGitlabBaseUrlLockedError(previousBaseUrl, nextBaseUrl);
|
|
56944
57034
|
const missingRefresh = (previousValues ? deploymentSecretsRequiringRefresh(previousValues, input.values) : []).filter((key) => !input.secrets[key]);
|
|
56945
57035
|
if (missingRefresh.length > 0) throw new DeploymentConfigSecretRefreshRequiredError(missingRefresh);
|
|
56946
57036
|
const currentSecrets = await tx.deploymentSecret.findMany({
|
|
56947
|
-
where: { deploymentConfigId:
|
|
57037
|
+
where: { deploymentConfigId: 1 },
|
|
56948
57038
|
select: { key: true }
|
|
56949
57039
|
});
|
|
56950
57040
|
const effective = new Set(currentSecrets.map(({ key }) => key));
|
|
@@ -56953,9 +57043,9 @@ var PgDeploymentConfigRepository = class {
|
|
|
56953
57043
|
const missing = input.requiredSecretKeys.filter((key) => !effective.has(key));
|
|
56954
57044
|
if (missing.length > 0) throw new DeploymentConfigMissingSecretsError(missing);
|
|
56955
57045
|
await tx.deploymentConfig.upsert({
|
|
56956
|
-
where: { id:
|
|
57046
|
+
where: { id: 1 },
|
|
56957
57047
|
create: {
|
|
56958
|
-
id:
|
|
57048
|
+
id: 1,
|
|
56959
57049
|
schemaVersion: input.schemaVersion,
|
|
56960
57050
|
values: input.values,
|
|
56961
57051
|
revision: 1
|
|
@@ -56970,18 +57060,18 @@ var PgDeploymentConfigRepository = class {
|
|
|
56970
57060
|
const key = rawKey;
|
|
56971
57061
|
if (prepared === null) {
|
|
56972
57062
|
await tx.deploymentSecret.deleteMany({ where: {
|
|
56973
|
-
deploymentConfigId:
|
|
57063
|
+
deploymentConfigId: 1,
|
|
56974
57064
|
key
|
|
56975
57065
|
} });
|
|
56976
57066
|
continue;
|
|
56977
57067
|
}
|
|
56978
57068
|
await tx.deploymentSecret.upsert({
|
|
56979
57069
|
where: { deploymentConfigId_key: {
|
|
56980
|
-
deploymentConfigId:
|
|
57070
|
+
deploymentConfigId: 1,
|
|
56981
57071
|
key
|
|
56982
57072
|
} },
|
|
56983
57073
|
create: {
|
|
56984
|
-
deploymentConfigId:
|
|
57074
|
+
deploymentConfigId: 1,
|
|
56985
57075
|
key,
|
|
56986
57076
|
value: prepared.sealedValue,
|
|
56987
57077
|
fingerprint: prepared.fingerprint
|
|
@@ -56993,7 +57083,7 @@ var PgDeploymentConfigRepository = class {
|
|
|
56993
57083
|
});
|
|
56994
57084
|
}
|
|
56995
57085
|
return toAdmin(await tx.deploymentConfig.findUniqueOrThrow({
|
|
56996
|
-
where: { id:
|
|
57086
|
+
where: { id: 1 },
|
|
56997
57087
|
select: adminSelect
|
|
56998
57088
|
}));
|
|
56999
57089
|
});
|
|
@@ -57001,21 +57091,21 @@ var PgDeploymentConfigRepository = class {
|
|
|
57001
57091
|
async markAdminClaimed(expectedRevision, claimedFor) {
|
|
57002
57092
|
if ((await this.prisma.deploymentConfig.updateMany({
|
|
57003
57093
|
where: {
|
|
57004
|
-
id:
|
|
57094
|
+
id: 1,
|
|
57005
57095
|
revision: expectedRevision
|
|
57006
57096
|
},
|
|
57007
57097
|
data: { adminClaimedFor: claimedFor }
|
|
57008
57098
|
})).count === 1) return;
|
|
57009
57099
|
throw new DeploymentConfigConflictError(expectedRevision, (await this.prisma.deploymentConfig.findUnique({
|
|
57010
|
-
where: { id:
|
|
57100
|
+
where: { id: 1 },
|
|
57011
57101
|
select: { revision: true }
|
|
57012
57102
|
}))?.revision ?? 0);
|
|
57013
57103
|
}
|
|
57014
57104
|
};
|
|
57015
57105
|
/** Composition convenience used by the CP container and tests. */
|
|
57016
57106
|
var PgDeploymentConfigStore = class extends DeploymentConfigService {
|
|
57017
|
-
constructor(prisma, cipher) {
|
|
57018
|
-
super(new PgDeploymentConfigRepository(prisma), cipher);
|
|
57107
|
+
constructor(prisma, cipher, envGitlabBaseUrl) {
|
|
57108
|
+
super(new PgDeploymentConfigRepository(prisma, envGitlabBaseUrl), cipher);
|
|
57019
57109
|
}
|
|
57020
57110
|
};
|
|
57021
57111
|
//#endregion
|
|
@@ -57294,7 +57384,7 @@ function makeSecretCipher(config) {
|
|
|
57294
57384
|
/** Open exactly the DB + SecretCipher slice shared by CP and setup tooling. */
|
|
57295
57385
|
function openDeploymentConfigStore(options) {
|
|
57296
57386
|
return {
|
|
57297
|
-
store: new PgDeploymentConfigStore(createPrisma(options.databaseUrl), makeSecretCipher(options)),
|
|
57387
|
+
store: new PgDeploymentConfigStore(createPrisma(options.databaseUrl), makeSecretCipher(options), options.gitlabBaseUrl),
|
|
57298
57388
|
close: disconnectPrisma
|
|
57299
57389
|
};
|
|
57300
57390
|
}
|
|
@@ -59750,10 +59840,6 @@ async function convertGithubManifest(code, options = {}) {
|
|
|
59750
59840
|
};
|
|
59751
59841
|
}
|
|
59752
59842
|
//#endregion
|
|
59753
|
-
//#region ../control-plane/dist/gitlab/config.js
|
|
59754
|
-
/** The public callback path, in its gateway form (deploy-public-url-prefix decision). */
|
|
59755
|
-
const GITLAB_OAUTH_CALLBACK_PATH = "/v1/gitlab/oauth/callback";
|
|
59756
|
-
//#endregion
|
|
59757
59843
|
//#region src/gitlab-app.ts
|
|
59758
59844
|
/** Exactly the scope set the Control Plane asks GitLab for (gitlab-com-integration.md §9.1). */
|
|
59759
59845
|
const GITLAB_OAUTH_SCOPES = ["api"];
|
|
@@ -62183,6 +62269,7 @@ function buildSetupServer(deps, options = {}) {
|
|
|
62183
62269
|
if (error instanceof DeploymentConfigMissingSecretsError) return problem(reply, 400, error.message, error.code);
|
|
62184
62270
|
if (error instanceof DeploymentConfigSecretRefreshRequiredError) return problem(reply, 400, error.message, error.code);
|
|
62185
62271
|
if (error instanceof DeploymentConfigConflictError) return problem(reply, 409, error.message, error.code);
|
|
62272
|
+
if (error instanceof DeploymentConfigGitlabBaseUrlLockedError) return problem(reply, 409, error.message, error.code);
|
|
62186
62273
|
if (error instanceof LogtoManagementError) return problem(reply, error.code === "LOGTO_UNAVAILABLE" ? 502 : error.code === "SOCIAL_CONNECTOR_UNSUPPORTED" ? 400 : 409, error.message, error.code);
|
|
62187
62274
|
request.log.error({ err: error }, "setup-server request failed");
|
|
62188
62275
|
return problem(reply, 500, "internal server error");
|
|
@@ -63056,6 +63143,7 @@ async function serveSetupServer(env = process.env) {
|
|
|
63056
63143
|
if (!isLoopbackHostname(host) && !config.SETUP_SERVER_ALLOW_CONTAINER_PROXY) throw new Error("Setup Server may bind outside loopback only behind an isolated local port forward");
|
|
63057
63144
|
const handle = openDeploymentConfigStore({
|
|
63058
63145
|
databaseUrl: config.DATABASE_URL,
|
|
63146
|
+
...env.GITLAB_BASE_URL ? { gitlabBaseUrl: env.GITLAB_BASE_URL } : {},
|
|
63059
63147
|
SECRET_CIPHER: config.SECRET_CIPHER,
|
|
63060
63148
|
VAULT_TRANSIT_KEY: config.VAULT_TRANSIT_KEY,
|
|
63061
63149
|
VAULT_TRANSIT_MOUNT: config.VAULT_TRANSIT_MOUNT,
|