@taiga-ai/mcp-server 0.36.0-rc.800 → 0.36.0-rc.926
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 +886 -56
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23899,6 +23899,18 @@ var TaigaClient = class {
|
|
|
23899
23899
|
async getLicenseSummary(productId) {
|
|
23900
23900
|
return this.request("GET", `/products/${seg(productId)}/maintenance/licenses`);
|
|
23901
23901
|
}
|
|
23902
|
+
/** One licence across a product's inventory (PRO-3251): what it asks, when to allow it, what brings it in. */
|
|
23903
|
+
async getProductLicense(productId, license) {
|
|
23904
|
+
return this.request("GET", `/products/${seg(productId)}/maintenance/licenses/detail?license=${encodeURIComponent(license)}`);
|
|
23905
|
+
}
|
|
23906
|
+
/** Allow a licence in one product: every licence finding in force for it there ignored (PRO-3727). */
|
|
23907
|
+
async allowProductLicense(productId, body) {
|
|
23908
|
+
return this.request("POST", `/products/${seg(productId)}/maintenance/licenses/ignore`, body);
|
|
23909
|
+
}
|
|
23910
|
+
/** Allow a licence in one product no longer: every one of its ignored licence findings there open again. */
|
|
23911
|
+
async reopenProductLicense(productId, license) {
|
|
23912
|
+
return this.request("POST", `/products/${seg(productId)}/maintenance/licenses/reopen`, { license });
|
|
23913
|
+
}
|
|
23902
23914
|
/** The organization's approved list and every licence in use across its products (PRO-3311). */
|
|
23903
23915
|
async getOrganizationLicenses() {
|
|
23904
23916
|
return this.request("GET", "/tenants/current/licenses");
|
|
@@ -23926,6 +23938,10 @@ var TaigaClient = class {
|
|
|
23926
23938
|
async updateFinding(id, body) {
|
|
23927
23939
|
return this.request("PATCH", `/maintenance/findings/${seg(id)}`, body);
|
|
23928
23940
|
}
|
|
23941
|
+
/** Ship notices (PRO-4165): raise the initiative that builds the product's open-source notices, or get the open one. */
|
|
23942
|
+
async shipNotices(productId) {
|
|
23943
|
+
return this.request("POST", `/products/${seg(productId)}/maintenance/notices`, {});
|
|
23944
|
+
}
|
|
23929
23945
|
async remediateFinding(productId, findingId) {
|
|
23930
23946
|
return this.request("POST", `/products/${seg(productId)}/maintenance/findings/${seg(findingId)}/remediate`, {});
|
|
23931
23947
|
}
|
|
@@ -24065,7 +24081,7 @@ var TaigaClient = class {
|
|
|
24065
24081
|
async refreshRealityDocuments(productId, docTypes) {
|
|
24066
24082
|
return this.request("POST", `/products/${seg(productId)}/documents/refresh-reality`, docTypes ? { docTypes } : void 0);
|
|
24067
24083
|
}
|
|
24068
|
-
/** What still blocks finishing setup: required documents unpublished
|
|
24084
|
+
/** What still blocks finishing setup: the required documents unpublished (PRO-2800). */
|
|
24069
24085
|
async getProductSetupBlockers(productId) {
|
|
24070
24086
|
return this.request("GET", `/products/${seg(productId)}/setup`);
|
|
24071
24087
|
}
|
|
@@ -24402,18 +24418,47 @@ const EU_COUNTRIES = [
|
|
|
24402
24418
|
}
|
|
24403
24419
|
];
|
|
24404
24420
|
/**
|
|
24405
|
-
* EEA countries outside the EU
|
|
24406
|
-
*
|
|
24421
|
+
* EEA countries outside the EU, all three, so {@link COUNTRIES} covers the
|
|
24422
|
+
* whole EU and EEA (PRO-3791). Only Norway has a country layer; Iceland and
|
|
24423
|
+
* Liechtenstein get the `eea` region layer alone, and neither is offered as a
|
|
24424
|
+
* Taiga tenant country ({@link offeredCountries}).
|
|
24425
|
+
*/
|
|
24426
|
+
const EEA_COUNTRIES = [
|
|
24427
|
+
{
|
|
24428
|
+
code: "NO",
|
|
24429
|
+
name: "Norway"
|
|
24430
|
+
},
|
|
24431
|
+
{
|
|
24432
|
+
code: "IS",
|
|
24433
|
+
name: "Iceland"
|
|
24434
|
+
},
|
|
24435
|
+
{
|
|
24436
|
+
code: "LI",
|
|
24437
|
+
name: "Liechtenstein"
|
|
24438
|
+
}
|
|
24439
|
+
];
|
|
24440
|
+
/**
|
|
24441
|
+
* The United Kingdom (PRO-3420): ISO code `GB`, neither EU nor EEA, in the
|
|
24442
|
+
* `uk` region. Offered only while the `regulations` flag is on.
|
|
24407
24443
|
*/
|
|
24408
|
-
const
|
|
24409
|
-
code: "
|
|
24410
|
-
name: "
|
|
24444
|
+
const UK_COUNTRIES = [{
|
|
24445
|
+
code: "GB",
|
|
24446
|
+
name: "United Kingdom"
|
|
24411
24447
|
}];
|
|
24412
|
-
[
|
|
24448
|
+
[
|
|
24449
|
+
...EU_COUNTRIES,
|
|
24450
|
+
...EEA_COUNTRIES,
|
|
24451
|
+
...UK_COUNTRIES
|
|
24452
|
+
];
|
|
24413
24453
|
/** Countries available during closed beta. Expand as we open new markets. */
|
|
24414
24454
|
const BETA_COUNTRIES = ["FI"];
|
|
24455
|
+
[...EU_COUNTRIES.map((c) => c.code).filter((code) => !BETA_COUNTRIES.includes(code))];
|
|
24415
24456
|
EU_COUNTRIES.filter((c) => BETA_COUNTRIES.includes(c.code));
|
|
24416
|
-
Object.fromEntries([
|
|
24457
|
+
Object.fromEntries([
|
|
24458
|
+
...EU_COUNTRIES.map((c) => [c.code, "eu"]),
|
|
24459
|
+
...EEA_COUNTRIES.map((c) => [c.code, "eea"]),
|
|
24460
|
+
...UK_COUNTRIES.map((c) => [c.code, "uk"])
|
|
24461
|
+
]);
|
|
24417
24462
|
//#endregion
|
|
24418
24463
|
//#region ../../packages/types/dist/factories.js
|
|
24419
24464
|
const FACTORY_DOCUMENT_TYPES = ["factory_instructions"];
|
|
@@ -24642,6 +24687,19 @@ const PRODUCT_DOCUMENT_DISPLAY_NAMES = {
|
|
|
24642
24687
|
const PRODUCT_DOCUMENT_ORIGINS = ["plan", "reality"];
|
|
24643
24688
|
/** The forges a repository link can name: `FORGE_PROVIDERS`, under the name links have always used. */
|
|
24644
24689
|
const VCS_PROVIDERS = FORGE_PROVIDERS;
|
|
24690
|
+
Object.fromEntries(Object.entries({
|
|
24691
|
+
specification: "specification",
|
|
24692
|
+
"user-flows": "user_flow",
|
|
24693
|
+
architecture: "architecture",
|
|
24694
|
+
"tech-decisions": "technology_decisions",
|
|
24695
|
+
"data-flow": "data_flow",
|
|
24696
|
+
"threat-model": "threat_model",
|
|
24697
|
+
dpia: "privacy_assessment",
|
|
24698
|
+
"risk-register": "risk_register",
|
|
24699
|
+
"service-blueprint": "service_blueprint",
|
|
24700
|
+
"ui-direction": "ui_direction",
|
|
24701
|
+
"codebase-snapshot": "codebase_snapshot"
|
|
24702
|
+
}).map(([slug, apiType]) => [apiType, slug]));
|
|
24645
24703
|
//#endregion
|
|
24646
24704
|
//#region ../../packages/types/dist/route-key.js
|
|
24647
24705
|
/**
|
|
@@ -24884,7 +24942,7 @@ const HALT_CAUSES = {
|
|
|
24884
24942
|
* reaped on a dead heartbeat, or found at 'planning' with no live job by the
|
|
24885
24943
|
* reconciler, which fails it with that cause on the tick that notices
|
|
24886
24944
|
* (`armPlanning` writes the status and creates the job in ONE transaction,
|
|
24887
|
-
* so that shape is never a race). One of
|
|
24945
|
+
* so that shape is never a race). One of four halts that mirror
|
|
24888
24946
|
* `PLAN_FAILURE_CAUSES` one-to-one; see `PLAN_FAILURE_HALT`.
|
|
24889
24947
|
*/
|
|
24890
24948
|
planning_stalled: {
|
|
@@ -24897,6 +24955,22 @@ const HALT_CAUSES = {
|
|
|
24897
24955
|
"remove"
|
|
24898
24956
|
]
|
|
24899
24957
|
},
|
|
24958
|
+
/**
|
|
24959
|
+
* The planner concluded nothing should be built (PRO-3941): a licence's
|
|
24960
|
+
* replacement where only build tools carry the licence and no maintained
|
|
24961
|
+
* package replaces them. Not a fault, so not `planning_failed`; its reason
|
|
24962
|
+
* is the detail, read off the planning job as a failure's is.
|
|
24963
|
+
*/
|
|
24964
|
+
planning_declined: {
|
|
24965
|
+
message: "The plan recommends not building this, so the queue stopped here.",
|
|
24966
|
+
params: [],
|
|
24967
|
+
clearedWhen: "planning is retried, or a re-plan replaces it",
|
|
24968
|
+
exits: [
|
|
24969
|
+
"auto",
|
|
24970
|
+
"reorder",
|
|
24971
|
+
"remove"
|
|
24972
|
+
]
|
|
24973
|
+
},
|
|
24900
24974
|
/** The run errored out. */
|
|
24901
24975
|
run_failed: {
|
|
24902
24976
|
message: "The run failed, so the queue stopped here.",
|
|
@@ -25178,7 +25252,8 @@ const READY_BLOCKERS = [
|
|
|
25178
25252
|
"bar_lowering",
|
|
25179
25253
|
"bar_lowering_unchecked",
|
|
25180
25254
|
"criticals_unresolved",
|
|
25181
|
-
"clean_checkout_failed"
|
|
25255
|
+
"clean_checkout_failed",
|
|
25256
|
+
"license_not_approved"
|
|
25182
25257
|
];
|
|
25183
25258
|
/**
|
|
25184
25259
|
* Why the autonomous loop handed a live pull request to a person (PRO-2409).
|
|
@@ -25291,6 +25366,7 @@ const INBOX_ITEM_KINDS = [
|
|
|
25291
25366
|
"review_pr",
|
|
25292
25367
|
"review_maintenance",
|
|
25293
25368
|
"review_policy",
|
|
25369
|
+
"attest_policy",
|
|
25294
25370
|
"setup_env_var",
|
|
25295
25371
|
"setup_secret",
|
|
25296
25372
|
"setup_third_party",
|
|
@@ -25415,6 +25491,7 @@ discriminatedUnion("kind", [
|
|
|
25415
25491
|
})).max(20),
|
|
25416
25492
|
moreFailed: number$1().int().min(0).optional(),
|
|
25417
25493
|
observedAt: optionalMoment,
|
|
25494
|
+
repository: string$2().min(1).max(255).optional(),
|
|
25418
25495
|
stepId: optionalUuid
|
|
25419
25496
|
}),
|
|
25420
25497
|
object({
|
|
@@ -26007,6 +26084,92 @@ object({
|
|
|
26007
26084
|
...outcomes === void 0 ? {} : { outcomes }
|
|
26008
26085
|
}));
|
|
26009
26086
|
//#endregion
|
|
26087
|
+
//#region ../../packages/types/dist/registries.js
|
|
26088
|
+
/**
|
|
26089
|
+
* The package ecosystems a registry can serve: every ecosystem the coding
|
|
26090
|
+
* agent builds (its skills and its image), keyed by the install tooling that
|
|
26091
|
+
* reads the credential. `maven` is a Maven repository, and Maven builds get
|
|
26092
|
+
* its credential (PRO-2916); Gradle builds name theirs in their own build
|
|
26093
|
+
* scripts and get none yet (PRO-2928).
|
|
26094
|
+
*/
|
|
26095
|
+
const REGISTRY_ECOSYSTEMS = [
|
|
26096
|
+
"npm",
|
|
26097
|
+
"terraform",
|
|
26098
|
+
"pypi",
|
|
26099
|
+
"nuget",
|
|
26100
|
+
"go",
|
|
26101
|
+
"cargo",
|
|
26102
|
+
"maven",
|
|
26103
|
+
"rubygems",
|
|
26104
|
+
"composer"
|
|
26105
|
+
];
|
|
26106
|
+
const REGISTRY_VENDORS = [
|
|
26107
|
+
"npmjs",
|
|
26108
|
+
"github",
|
|
26109
|
+
"gcp_artifact",
|
|
26110
|
+
"generic",
|
|
26111
|
+
"hcp_terraform",
|
|
26112
|
+
"scalr",
|
|
26113
|
+
"azure_artifacts",
|
|
26114
|
+
"jfrog",
|
|
26115
|
+
"git_host",
|
|
26116
|
+
"aws_codeartifact"
|
|
26117
|
+
];
|
|
26118
|
+
/** An IAM role ARN, as the CodeArtifact credential takes it. */
|
|
26119
|
+
const AWS_ROLE_ARN_PATTERN = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]{1,512}$/;
|
|
26120
|
+
//#endregion
|
|
26121
|
+
//#region ../../packages/types/dist/gitlab.js
|
|
26122
|
+
/**
|
|
26123
|
+
* GitLab connections (PRO-3096, P-PRO-150): the shared request/response
|
|
26124
|
+
* contract of `/v1/gitlab/connections`. A connection is one GitLab host
|
|
26125
|
+
* (GitLab.com, a self-managed instance or GitLab Dedicated) with a bot token
|
|
26126
|
+
* the tenant pasted. The token is write-only and appears in no response.
|
|
26127
|
+
*/
|
|
26128
|
+
const GITLAB_CONNECTION_STATUSES = [
|
|
26129
|
+
"valid",
|
|
26130
|
+
"auth_failed",
|
|
26131
|
+
"unreachable"
|
|
26132
|
+
];
|
|
26133
|
+
//#endregion
|
|
26134
|
+
//#region ../../packages/types/dist/cloud-connections.js
|
|
26135
|
+
const CLOUD_CONNECTION_STATUSES = [
|
|
26136
|
+
"pending_setup",
|
|
26137
|
+
"validating",
|
|
26138
|
+
"active",
|
|
26139
|
+
"error",
|
|
26140
|
+
"suspended",
|
|
26141
|
+
"disconnected"
|
|
26142
|
+
];
|
|
26143
|
+
/**
|
|
26144
|
+
* Keys in `providerConfig` that only Taiga's provisioning path may write.
|
|
26145
|
+
*
|
|
26146
|
+
* PRO-858. `accountName` is the correlation key the AFT account-ready callback
|
|
26147
|
+
* arrives with, and the webhook resolves it under service context with no
|
|
26148
|
+
* tenant predicate -- it has to, because the callback carries no tenant. It is
|
|
26149
|
+
* also guessable: `generateAccountName` derives it from tenant, team and
|
|
26150
|
+
* project slugs. So a tenant that could write this key could plant a row
|
|
26151
|
+
* claiming another tenant's account name, wait for that callback, and have the
|
|
26152
|
+
* victim's `deployConfig` (accountId, deployRoleArn, stateBucket) delivered to
|
|
26153
|
+
* their own environment.
|
|
26154
|
+
*
|
|
26155
|
+
* `accountKey` is the same identity in unguessable form
|
|
26156
|
+
* (`sha256(tenantId:productId:envType)`), and is what the AFT module is named
|
|
26157
|
+
* for -- equally not the caller's to assert.
|
|
26158
|
+
*
|
|
26159
|
+
* Neither is tenant-facing: no provider schema models them, and
|
|
26160
|
+
* `providerConfigProblem` documents them as the shape a MANAGED row has. The
|
|
26161
|
+
* update path merges and persists the RAW config precisely so Zod does not
|
|
26162
|
+
* strip them off managed rows, which is what made a tenant-supplied value
|
|
26163
|
+
* durable. Rejecting rather than stripping, so a client sending one is told,
|
|
26164
|
+
* instead of silently having it dropped.
|
|
26165
|
+
*/
|
|
26166
|
+
const TAIGA_MANAGED_PROVIDER_CONFIG_KEYS = ["accountName", "accountKey"];
|
|
26167
|
+
/** Whether `config` leaves alone the keys only Taiga's provisioning writes. */
|
|
26168
|
+
function isTenantWritableProviderConfig(config) {
|
|
26169
|
+
return !TAIGA_MANAGED_PROVIDER_CONFIG_KEYS.some((key) => key in config);
|
|
26170
|
+
}
|
|
26171
|
+
const TAIGA_MANAGED_PROVIDER_CONFIG_MESSAGE = `providerConfig may not set ${TAIGA_MANAGED_PROVIDER_CONFIG_KEYS.join(" or ")}: Taiga's provisioning owns those keys`;
|
|
26172
|
+
//#endregion
|
|
26010
26173
|
//#region ../../packages/types/dist/environments.js
|
|
26011
26174
|
const ENVIRONMENT_TYPES = [
|
|
26012
26175
|
"development",
|
|
@@ -26020,6 +26183,31 @@ const ENVIRONMENT_TYPES = [
|
|
|
26020
26183
|
//#endregion
|
|
26021
26184
|
//#region ../../packages/types/dist/api-keys.js
|
|
26022
26185
|
/**
|
|
26186
|
+
* API key scopes control what operations a key can perform.
|
|
26187
|
+
* Scopes act as a ceiling -- the effective permissions are the intersection
|
|
26188
|
+
* of the key's scopes and the user's role-based capabilities.
|
|
26189
|
+
*
|
|
26190
|
+
* Convention: "resource:action" matching the existing Capability type.
|
|
26191
|
+
*/
|
|
26192
|
+
const API_KEY_SCOPES = [
|
|
26193
|
+
"products:read",
|
|
26194
|
+
"products:write",
|
|
26195
|
+
"backlog:read",
|
|
26196
|
+
"backlog:write",
|
|
26197
|
+
"factories:read",
|
|
26198
|
+
"factories:documents:write",
|
|
26199
|
+
"users:read",
|
|
26200
|
+
"tenant:documents:read",
|
|
26201
|
+
"tenant:documents:write",
|
|
26202
|
+
"vcs:read",
|
|
26203
|
+
"customDocuments:read",
|
|
26204
|
+
"customDocuments:write",
|
|
26205
|
+
"customDocuments:delete",
|
|
26206
|
+
"factories:write",
|
|
26207
|
+
"vcs:manage"
|
|
26208
|
+
];
|
|
26209
|
+
const API_KEY_PREFIX = "taiga_sk_live_";
|
|
26210
|
+
/**
|
|
26023
26211
|
* The groups the MCP server's tools come in (PRO-2838). A key may name the ones
|
|
26024
26212
|
* its AI tool sees; null is every one. This is focus, not security: a tool the
|
|
26025
26213
|
* key's role or scopes do not allow is never offered, whatever is named here.
|
|
@@ -26031,6 +26219,11 @@ const MCP_TOOLSETS = [
|
|
|
26031
26219
|
"observe",
|
|
26032
26220
|
"documents"
|
|
26033
26221
|
];
|
|
26222
|
+
Object.freeze({
|
|
26223
|
+
required: false,
|
|
26224
|
+
gracePeriodDays: 0,
|
|
26225
|
+
requiredSince: null
|
|
26226
|
+
});
|
|
26034
26227
|
[...TURN_EVENT_TRIGGERS];
|
|
26035
26228
|
/** Longest request for changes from Taiga (PRO-3035): about four pages of prose. */
|
|
26036
26229
|
const CHANGE_REQUEST_MAX_CHARS = 1e4;
|
|
@@ -26051,6 +26244,8 @@ const REQUEST_EXITS = [
|
|
|
26051
26244
|
"dismiss",
|
|
26052
26245
|
"reviewed"
|
|
26053
26246
|
];
|
|
26247
|
+
/** The most ids an answer's `seen` carries; a longer list is not sent, and the review is not checked. */
|
|
26248
|
+
const REVIEW_SEEN_MAX = 1e4;
|
|
26054
26249
|
//#endregion
|
|
26055
26250
|
//#region ../../packages/types/dist/maintenance.js
|
|
26056
26251
|
/**
|
|
@@ -26146,10 +26341,30 @@ function isFixableFinding(finding) {
|
|
|
26146
26341
|
if (finding.status !== "open") return false;
|
|
26147
26342
|
if (finding.location?.historyOnly) return false;
|
|
26148
26343
|
if (!isAgentRemediable(finding)) return false;
|
|
26344
|
+
if (finding.fingerprint?.startsWith("base-image:") || finding.baseImageFix) {
|
|
26345
|
+
if (finding.baseImageFix) return finding.baseImageFix.way === "move" || finding.baseImageFix.way === "upgrade";
|
|
26346
|
+
return typeof finding.baseMove === "boolean" ? finding.baseMove : baseImageMoveRemoves(finding.data);
|
|
26347
|
+
}
|
|
26149
26348
|
if (finding.kind === "vulnerable_dependency" && isMaliciousPackageFinding(finding)) return true;
|
|
26349
|
+
if (isPinBlocked(finding)) return false;
|
|
26150
26350
|
return isCodeRemediationKind(finding.kind) || !!finding.recommendedVersion;
|
|
26151
26351
|
}
|
|
26152
26352
|
/**
|
|
26353
|
+
* Whether a Fix cannot work: another package's pin caps this finding's fix, and
|
|
26354
|
+
* the last fix ended still vulnerable against it (`opentelemetry-api` under
|
|
26355
|
+
* `aws-opentelemetry-distro`'s `==1.44.0`). Another would spend a run to meet
|
|
26356
|
+
* it again. Blocked with no such fix, a person's Fix still runs (AC-17): they
|
|
26357
|
+
* may know a way round. The gate lifts the hold when the pin moves.
|
|
26358
|
+
*/
|
|
26359
|
+
function isPinBlocked(finding) {
|
|
26360
|
+
return finding.autoFixHeld?.reason === "blocked_by_dependency" && finding.autoFixHeld.fixFailed === true;
|
|
26361
|
+
}
|
|
26362
|
+
function baseImageMoveRemoves(data) {
|
|
26363
|
+
const baseImage = typeof data === "object" && data !== null ? data.baseImage : null;
|
|
26364
|
+
const bases = typeof baseImage === "object" && baseImage !== null ? baseImage.bases : null;
|
|
26365
|
+
return Array.isArray(bases) && bases.length > 0 && bases.every((b) => typeof b === "object" && b !== null && typeof b.movesTo === "string" && b.removed === true);
|
|
26366
|
+
}
|
|
26367
|
+
/**
|
|
26153
26368
|
* A malicious package (OSV `MAL-*`): its advisory id or one of its aliases
|
|
26154
26369
|
* says so, or its tier is Act now with no KEV listing, which only a malware
|
|
26155
26370
|
* advisory earns (`findingTierReason`). The id and aliases come first: a
|
|
@@ -26165,6 +26380,17 @@ function isMaliciousPackageFinding(finding) {
|
|
|
26165
26380
|
if (Array.isArray(aliases) && aliases.some(mal)) return true;
|
|
26166
26381
|
return finding.tier === "act_now" && finding.kevListedAt == null;
|
|
26167
26382
|
}
|
|
26383
|
+
/**
|
|
26384
|
+
* The scanner CATEGORIES a sweep reports, in the order the agent reports them
|
|
26385
|
+
* (PRO-1725). Outcomes are keyed on the category, not the tool, so replacing a
|
|
26386
|
+
* tool in the agents' scanner manifest changes nothing here.
|
|
26387
|
+
*/
|
|
26388
|
+
const MAINTENANCE_SCANNERS = [
|
|
26389
|
+
"deps",
|
|
26390
|
+
"secrets",
|
|
26391
|
+
"sast",
|
|
26392
|
+
"iac"
|
|
26393
|
+
];
|
|
26168
26394
|
new Map(Object.entries({
|
|
26169
26395
|
deps: [
|
|
26170
26396
|
"vulnerable_dependency",
|
|
@@ -26370,6 +26596,19 @@ object({
|
|
|
26370
26596
|
commit: d.commit
|
|
26371
26597
|
}));
|
|
26372
26598
|
/**
|
|
26599
|
+
* The sources whose findings no agent scanner's outcome decides (PRO-4070):
|
|
26600
|
+
* the API's runtime judge (PRO-3659) and base OS judge (PRO-4074), and the
|
|
26601
|
+
* base image reading (PRO-1728), whose findings the agent drafts but only a
|
|
26602
|
+
* sweep that read the images may settle.
|
|
26603
|
+
*/
|
|
26604
|
+
const MAINTENANCE_JUDGED_SOURCES = [
|
|
26605
|
+
"runtime",
|
|
26606
|
+
"base_os",
|
|
26607
|
+
"base_image"
|
|
26608
|
+
];
|
|
26609
|
+
[...MAINTENANCE_SCANNERS, ...MAINTENANCE_JUDGED_SOURCES];
|
|
26610
|
+
new Set(MAINTENANCE_SCANNERS);
|
|
26611
|
+
/**
|
|
26373
26612
|
* The longest licence string the package list filters by (PRO-2920). The
|
|
26374
26613
|
* inventory keeps every expression whole whatever its length; this bounds only
|
|
26375
26614
|
* the query string, far above any expression seen. A longer one is listed in
|
|
@@ -26377,6 +26616,200 @@ object({
|
|
|
26377
26616
|
*/
|
|
26378
26617
|
const MAINTENANCE_LICENSE_FILTER_MAX = 4096;
|
|
26379
26618
|
//#endregion
|
|
26619
|
+
//#region ../../packages/types/dist/maintenance-advisory.js
|
|
26620
|
+
/**
|
|
26621
|
+
* Pull the renderable advisory fields out of a finding's raw OSV `data` object.
|
|
26622
|
+
*
|
|
26623
|
+
* `data` is the scanner's raw OSV vulnerability payload (MaintenanceFindingData,
|
|
26624
|
+
* shape varies), so every field is read leniently: a missing or malformed key
|
|
26625
|
+
* yields an empty value rather than throwing. Shared by the web app's finding
|
|
26626
|
+
* detail and the Taiga Audit preview and report (PRO-3791), so both show the
|
|
26627
|
+
* same identifiers for the same finding.
|
|
26628
|
+
*/
|
|
26629
|
+
/** A non-empty string, or null: anything else the scanner wrote reads as absent. */
|
|
26630
|
+
const text$3 = string$2().min(1).nullable().catch(null);
|
|
26631
|
+
/** The non-empty strings of a list; a value that is not a list reads as none. */
|
|
26632
|
+
const texts = array(unknown$1()).catch([]).transform((list) => list.filter((v) => typeof v === "string" && v.length > 0));
|
|
26633
|
+
object({
|
|
26634
|
+
type: text$3,
|
|
26635
|
+
score: text$3
|
|
26636
|
+
});
|
|
26637
|
+
object({
|
|
26638
|
+
type: text$3,
|
|
26639
|
+
url: text$3
|
|
26640
|
+
});
|
|
26641
|
+
object({
|
|
26642
|
+
aliases: texts,
|
|
26643
|
+
database_specific: object({
|
|
26644
|
+
cwe_ids: texts,
|
|
26645
|
+
severity: text$3
|
|
26646
|
+
}).nullable().catch(null),
|
|
26647
|
+
severity: array(unknown$1()).catch([]),
|
|
26648
|
+
references: array(unknown$1()).catch([]),
|
|
26649
|
+
published: text$3,
|
|
26650
|
+
modified: text$3
|
|
26651
|
+
});
|
|
26652
|
+
//#endregion
|
|
26653
|
+
//#region ../../packages/types/dist/finding-controls.js
|
|
26654
|
+
/**
|
|
26655
|
+
* A committed credential.
|
|
26656
|
+
*
|
|
26657
|
+
* SEC-3.4 "Secrets must never be stored in source code or logs"
|
|
26658
|
+
* SEC-5.1 "Secrets must be stored in an approved secrets management system"
|
|
26659
|
+
* SEC-5.2 "Secrets must be rotated at least annually" -- the half Taiga cannot
|
|
26660
|
+
* do, and the reason the remediation initiative insists the PR say so.
|
|
26661
|
+
* SDLC-3.5 "Secret detection must run pre-commit and in CI pipeline"
|
|
26662
|
+
*/
|
|
26663
|
+
const SECRET_CONTROLS = [
|
|
26664
|
+
"SEC-3.4",
|
|
26665
|
+
"SEC-5.1",
|
|
26666
|
+
"SEC-5.2",
|
|
26667
|
+
"SDLC-3.5"
|
|
26668
|
+
];
|
|
26669
|
+
/**
|
|
26670
|
+
* A vulnerable dependency.
|
|
26671
|
+
*
|
|
26672
|
+
* SCS-2.6 "critical security patches within 7 days, others within 30 days"
|
|
26673
|
+
* -- breached by a vulnerability whose fix has been out past its
|
|
26674
|
+
* window, and by no other (PRO-3420). One found this morning has missed
|
|
26675
|
+
* nothing; neither has one with no fixed release to move to.
|
|
26676
|
+
* SCS-5.2 "Critical vulnerabilities must block deployment"
|
|
26677
|
+
*
|
|
26678
|
+
* Two controls this map deliberately does NOT claim:
|
|
26679
|
+
* - SCS-2.5 "Transitive dependency vulnerabilities must be monitored
|
|
26680
|
+
* continuously". A finding is that monitoring working, not evidence it is
|
|
26681
|
+
* missing; every vulnerable dependency read as a breach of it. The sweep's
|
|
26682
|
+
* own record shows it met instead (sweep-evidence.ts).
|
|
26683
|
+
* - Anything for an outdated dependency that is no vulnerability: SCS-2.6's
|
|
26684
|
+
* rationale is "timely vulnerability remediation", and an update that fixes
|
|
26685
|
+
* nothing is not a security patch.
|
|
26686
|
+
*/
|
|
26687
|
+
const DEPENDENCY_CONTROLS = ["SCS-2.6", "SCS-5.2"];
|
|
26688
|
+
/**
|
|
26689
|
+
* A package whose licence the allowlist does not satisfy (PRO-2862).
|
|
26690
|
+
*
|
|
26691
|
+
* TIC-6.2 "Open source licenses must be reviewed and approved before adoption"
|
|
26692
|
+
* -- the same policy section lists the approved licences and the ones
|
|
26693
|
+
* requiring review, which is where the default allowlist comes from.
|
|
26694
|
+
*/
|
|
26695
|
+
const LICENSE_CONTROLS = ["TIC-6.2"];
|
|
26696
|
+
/**
|
|
26697
|
+
* Injection and unsanitized input, whatever the sink.
|
|
26698
|
+
*
|
|
26699
|
+
* SDLC-5.1 "All user input must be validated and sanitized"
|
|
26700
|
+
* SEC-6.1 "All API endpoints must validate and sanitize input"
|
|
26701
|
+
*/
|
|
26702
|
+
const INJECTION_CONTROLS = ["SDLC-5.1", "SEC-6.1"];
|
|
26703
|
+
/**
|
|
26704
|
+
* Every opengrep rule Taiga ships, mapped explicitly.
|
|
26705
|
+
*
|
|
26706
|
+
* A closed set, unlike checkov's -- these rules are ours, so an exhaustive map
|
|
26707
|
+
* is possible and a keyword heuristic would be a worse answer to an easier
|
|
26708
|
+
* question. `policy-map.test.ts` reads the rule ids straight out of the YAML
|
|
26709
|
+
* so a new rule that lands unmapped fails the build instead of silently
|
|
26710
|
+
* emitting no control.
|
|
26711
|
+
*/
|
|
26712
|
+
const SAST_CONTROLS = {
|
|
26713
|
+
"taiga.csharp.sql-string-concat": INJECTION_CONTROLS,
|
|
26714
|
+
"taiga.go.sql-string-concat": INJECTION_CONTROLS,
|
|
26715
|
+
"taiga.javascript.sql-template-concat": INJECTION_CONTROLS,
|
|
26716
|
+
"taiga.python.sqli-fstring": INJECTION_CONTROLS,
|
|
26717
|
+
"taiga.rust.sql-format-concat": INJECTION_CONTROLS,
|
|
26718
|
+
"taiga.csharp.process-start-shell-dynamic": INJECTION_CONTROLS,
|
|
26719
|
+
"taiga.go.exec-command-dynamic": INJECTION_CONTROLS,
|
|
26720
|
+
"taiga.javascript.child-process-exec-dynamic": INJECTION_CONTROLS,
|
|
26721
|
+
"taiga.python.os-system-dynamic": INJECTION_CONTROLS,
|
|
26722
|
+
"taiga.python.subprocess-shell-true": INJECTION_CONTROLS,
|
|
26723
|
+
"taiga.rust.command-shell-dynamic": INJECTION_CONTROLS,
|
|
26724
|
+
"taiga.javascript.eval-dynamic": INJECTION_CONTROLS,
|
|
26725
|
+
"taiga.python.eval-exec-dynamic": INJECTION_CONTROLS,
|
|
26726
|
+
"taiga.javascript.dangerously-set-inner-html": INJECTION_CONTROLS,
|
|
26727
|
+
"taiga.javascript.innerhtml-dynamic": INJECTION_CONTROLS,
|
|
26728
|
+
"taiga.csharp.binaryformatter-deserialize": ["SDLC-5.7"],
|
|
26729
|
+
"taiga.python.pickle-load-untrusted": ["SDLC-5.7"],
|
|
26730
|
+
"taiga.python.yaml-load-unsafe": ["SDLC-5.7"],
|
|
26731
|
+
"taiga.csharp.cert-validation-disabled": ["SEC-3.2"],
|
|
26732
|
+
"taiga.go.tls-insecure-skip-verify": ["SEC-3.2"],
|
|
26733
|
+
"taiga.javascript.tls-reject-unauthorized-false": ["SEC-3.2"],
|
|
26734
|
+
"taiga.python.requests-verify-false": ["SEC-3.2"],
|
|
26735
|
+
"taiga.rust.tls-verification-disabled": ["SEC-3.2"],
|
|
26736
|
+
"taiga.javascript.jwt-verify-none": ["SDLC-5.4", "SEC-9.1"],
|
|
26737
|
+
"taiga.python.flask-debug-true": ["SDLC-5.5"],
|
|
26738
|
+
"taiga.go.pprof-import": ["SDLC-5.5"],
|
|
26739
|
+
"taiga.csharp.weak-hash": [],
|
|
26740
|
+
"taiga.go.weak-hash": [],
|
|
26741
|
+
"taiga.javascript.weak-hash": [],
|
|
26742
|
+
"taiga.python.weak-hash": [],
|
|
26743
|
+
"taiga.rust.weak-hash": [],
|
|
26744
|
+
"taiga.go.math-rand-for-secrets": [],
|
|
26745
|
+
"taiga.rust.non-crypto-rng-for-secrets": []
|
|
26746
|
+
};
|
|
26747
|
+
/**
|
|
26748
|
+
* Checkov, matched on the check's own description.
|
|
26749
|
+
*
|
|
26750
|
+
* Checkov's rule set is open and numbers in the thousands, so an exhaustive
|
|
26751
|
+
* id map is not available the way it is for our own rules. These phrases are
|
|
26752
|
+
* matched against the check title checkov itself writes ("Ensure all data
|
|
26753
|
+
* stored in the RDS is securely encrypted at rest"), and they are deliberately
|
|
26754
|
+
* narrow: a title matching nothing yields no control, which is the safe
|
|
26755
|
+
* direction to be wrong in.
|
|
26756
|
+
*
|
|
26757
|
+
* Exported so `policy-map.test.ts` validates THESE ids against the registry
|
|
26758
|
+
* too. They are the half most likely to drift, and an id-map that only
|
|
26759
|
+
* validates the closed half is a claim the tests do not support.
|
|
26760
|
+
*/
|
|
26761
|
+
const IAC_PHRASES = [
|
|
26762
|
+
{
|
|
26763
|
+
pattern: /encrypt\w*\s+at\s+rest|at\s+rest.*encrypt|securely encrypted/i,
|
|
26764
|
+
controls: ["SEC-3.1"],
|
|
26765
|
+
why: "at-rest encryption"
|
|
26766
|
+
},
|
|
26767
|
+
{
|
|
26768
|
+
pattern: /\bKMS\b|customer[- ]managed key|\bCMK\b/i,
|
|
26769
|
+
controls: ["SEC-3.3"],
|
|
26770
|
+
why: "key management"
|
|
26771
|
+
},
|
|
26772
|
+
{
|
|
26773
|
+
pattern: /in\s+transit|\bTLS\b|\bSSL\b|\bHTTPS\b/i,
|
|
26774
|
+
controls: ["SEC-3.2"],
|
|
26775
|
+
why: "transport encryption"
|
|
26776
|
+
},
|
|
26777
|
+
{
|
|
26778
|
+
pattern: /0\.0\.0\.0\/0|ingress from|security group/i,
|
|
26779
|
+
controls: ["CLD-7.2"],
|
|
26780
|
+
why: "network default-deny"
|
|
26781
|
+
},
|
|
26782
|
+
{
|
|
26783
|
+
pattern: /public(ly)?\s+(access|read|write|expos)|public access/i,
|
|
26784
|
+
controls: ["CLD-1.3"],
|
|
26785
|
+
why: "public exposure"
|
|
26786
|
+
},
|
|
26787
|
+
{
|
|
26788
|
+
pattern: /least privilege|wildcard|admin(istrator)? (policy|access)/i,
|
|
26789
|
+
controls: ["SEC-2.1"],
|
|
26790
|
+
why: "least privilege"
|
|
26791
|
+
},
|
|
26792
|
+
{
|
|
26793
|
+
pattern: /backup|point[- ]in[- ]time recovery/i,
|
|
26794
|
+
controls: ["OPS-4.1"],
|
|
26795
|
+
why: "backup"
|
|
26796
|
+
},
|
|
26797
|
+
{
|
|
26798
|
+
pattern: /audit log|cloudtrail|access logging|logging (is )?enabled/i,
|
|
26799
|
+
controls: ["SDLC-5.8"],
|
|
26800
|
+
why: "security logging"
|
|
26801
|
+
}
|
|
26802
|
+
];
|
|
26803
|
+
[.../* @__PURE__ */ new Set([
|
|
26804
|
+
...SECRET_CONTROLS,
|
|
26805
|
+
...DEPENDENCY_CONTROLS,
|
|
26806
|
+
...LICENSE_CONTROLS,
|
|
26807
|
+
...Object.values(SAST_CONTROLS).flat(),
|
|
26808
|
+
...IAC_PHRASES.flatMap((p) => p.controls)
|
|
26809
|
+
])];
|
|
26810
|
+
new RegExp(`^\\|\\s*(${CONTROL_ID_SOURCE})\\s*\\|\\s*(.*?)\\s*(?:\\||$)`);
|
|
26811
|
+
REALITY_DERIVED_DOCUMENT_TYPES.filter((t) => t !== "codebase_snapshot");
|
|
26812
|
+
//#endregion
|
|
26380
26813
|
//#region ../../packages/types/dist/custom-documents.js
|
|
26381
26814
|
/**
|
|
26382
26815
|
* Shared types for the custom-documents feature: customer-uploaded reference
|
|
@@ -26685,6 +27118,7 @@ const uploadGrant = object({
|
|
|
26685
27118
|
expiresAt: string$2()
|
|
26686
27119
|
});
|
|
26687
27120
|
object({ version: number$1().int().min(1) });
|
|
27121
|
+
object({ documentId: string$2().uuid() });
|
|
26688
27122
|
/** The uploads a product's move could not re-file, named while they fit the answer (PRO-3497). */
|
|
26689
27123
|
const failedDocuments = array(object({
|
|
26690
27124
|
id: string$2(),
|
|
@@ -26785,6 +27219,14 @@ const cloudProviderConfig = discriminatedUnion("provider", [
|
|
|
26785
27219
|
bestPractices: cloudBestPractices
|
|
26786
27220
|
})
|
|
26787
27221
|
]);
|
|
27222
|
+
const newCloudConnection = strictObject({
|
|
27223
|
+
name: string$2().min(1).max(150),
|
|
27224
|
+
provider: _enum(CLOUD_PROVIDERS),
|
|
27225
|
+
providerConfig: cloudProviderConfig
|
|
27226
|
+
}).refine((connection) => connection.provider === connection.providerConfig.provider, {
|
|
27227
|
+
message: "connection.provider must match connection.providerConfig.provider",
|
|
27228
|
+
path: ["providerConfig", "provider"]
|
|
27229
|
+
});
|
|
26788
27230
|
const columnIndex = number$1().int().min(1).max(EXCEL_MAX_COLUMN);
|
|
26789
27231
|
const sheetMap = object({
|
|
26790
27232
|
sheetName: string$2().min(1).max(120),
|
|
@@ -26811,18 +27253,30 @@ strictObject({
|
|
|
26811
27253
|
"replanning",
|
|
26812
27254
|
"after_step",
|
|
26813
27255
|
"after_round",
|
|
26814
|
-
"held"
|
|
26815
|
-
|
|
27256
|
+
"held",
|
|
27257
|
+
"sent"
|
|
27258
|
+
]).optional() }), object({ files: array(object({
|
|
27259
|
+
id: string$2(),
|
|
27260
|
+
url: string$2().url()
|
|
27261
|
+
})) });
|
|
27262
|
+
object({ images: array(object({
|
|
27263
|
+
name: string$2(),
|
|
27264
|
+
url: string$2().url().nullable()
|
|
27265
|
+
})) });
|
|
26816
27266
|
strictObject({
|
|
26817
27267
|
exit: _enum(REQUEST_EXITS),
|
|
26818
27268
|
comment: string$2().trim().min(1).max(PLAN_REJECTION_COMMENT_MAX_CHARS).optional(),
|
|
26819
|
-
force: boolean().optional()
|
|
27269
|
+
force: boolean().optional(),
|
|
27270
|
+
seen: array(string$2().uuid()).max(REVIEW_SEEN_MAX).optional()
|
|
26820
27271
|
}).refine((answer) => answer.exit === "reject" === (answer.comment !== void 0), {
|
|
26821
27272
|
message: "A rejection takes a comment for the planner, and no other answer takes one.",
|
|
26822
27273
|
path: ["comment"]
|
|
26823
27274
|
}).refine((answer) => answer.exit === "replan" || answer.force === void 0, {
|
|
26824
27275
|
message: "Only Plan again can plan anyway.",
|
|
26825
27276
|
path: ["force"]
|
|
27277
|
+
}).refine((answer) => answer.exit === "reviewed" || answer.seen === void 0, {
|
|
27278
|
+
message: "Only Mark reviewed says what was shown.",
|
|
27279
|
+
path: ["seen"]
|
|
26826
27280
|
});
|
|
26827
27281
|
strictObject({ type: productDocumentType }), object({
|
|
26828
27282
|
version: number$1().int().min(1),
|
|
@@ -26830,7 +27284,7 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26830
27284
|
shown: array(string$2()),
|
|
26831
27285
|
count: number$1().int().min(0)
|
|
26832
27286
|
})
|
|
26833
|
-
}), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }),
|
|
27287
|
+
}), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }), strictObject({
|
|
26834
27288
|
githubRepositoryId: uuid.optional(),
|
|
26835
27289
|
/** A GitLab project, by the connection whose bot reaches it and GitLab's numeric id (P-PRO-150). */
|
|
26836
27290
|
gitlabConnectionId: uuid.optional(),
|
|
@@ -26852,13 +27306,19 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26852
27306
|
enforcementLevel: string$2().nullable()
|
|
26853
27307
|
}).nullable()
|
|
26854
27308
|
}), strictObject({
|
|
26855
|
-
license: string$2().trim().min(1).max(
|
|
27309
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX),
|
|
26856
27310
|
ignoreReason: _enum(MAINTENANCE_IGNORE_REASONS),
|
|
26857
27311
|
ignoreNote,
|
|
26858
27312
|
ignoreExpiresAt
|
|
26859
27313
|
}).refine((args) => expiresInFuture(args.ignoreExpiresAt), EXPIRY_IN_FUTURE), strictObject({
|
|
26860
|
-
license: string$2().trim().min(1).max(
|
|
27314
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX),
|
|
26861
27315
|
findingIds: array(uuid).min(1).optional()
|
|
27316
|
+
}), strictObject({ license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX) }), object({
|
|
27317
|
+
backlogItemIds: array(string$2()),
|
|
27318
|
+
findingCount: number$1()
|
|
27319
|
+
}), strictObject({}), object({
|
|
27320
|
+
backlogItemId: string$2(),
|
|
27321
|
+
created: boolean()
|
|
26862
27322
|
}), strictObject({ host: string$2().trim().toLowerCase().refine(isProductNetworkHost, { message: "A host is one exact DNS name, such as api.example.com: no wildcard, scheme, port or path" }).refine((host) => !isSharedHostingDomain(host), { message: "Anyone can host content on this domain, so it cannot be listed whole; list the one host you need under it" }) }), strictObject({
|
|
26863
27323
|
type: _enum(AGENT_JOB_TYPES),
|
|
26864
27324
|
documentType: string$2().max(30).optional(),
|
|
@@ -26902,15 +27362,16 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26902
27362
|
}), strictObject({
|
|
26903
27363
|
...policyFixOptions,
|
|
26904
27364
|
controlId: string$2().min(1).max(40)
|
|
26905
|
-
}), strictObject({
|
|
26906
|
-
|
|
26907
|
-
|
|
26908
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
26911
|
-
|
|
26912
|
-
|
|
26913
|
-
|
|
27365
|
+
}), strictObject({
|
|
27366
|
+
controlId: string$2().min(1).max(40),
|
|
27367
|
+
validMonths: union([
|
|
27368
|
+
literal(3),
|
|
27369
|
+
literal(6),
|
|
27370
|
+
literal(12)
|
|
27371
|
+
]),
|
|
27372
|
+
note: string$2().trim().max(1e3).optional()
|
|
27373
|
+
}), strictObject({ environments: array(string$2()).default(["development"]) }), object({ id: string$2().uuid() }), strictObject({
|
|
27374
|
+
connection: newCloudConnection,
|
|
26914
27375
|
environment: object({
|
|
26915
27376
|
name: string$2().min(1).max(100),
|
|
26916
27377
|
slug: string$2().min(1).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Slug must be lowercase letters, digits, and dashes"),
|
|
@@ -26919,13 +27380,159 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26919
27380
|
isProtected: boolean().default(false)
|
|
26920
27381
|
}).optional()
|
|
26921
27382
|
});
|
|
27383
|
+
/**
|
|
27384
|
+
* The MCP toolsets a key's AI tool sees: at least one, each once, or null for
|
|
27385
|
+
* every one (PRO-2838). An empty list would start a server with no tools at all.
|
|
27386
|
+
*/
|
|
27387
|
+
const mcpToolsets = array(_enum(MCP_TOOLSETS)).min(1).refine((sets) => new Set(sets).size === sets.length, "Name each toolset once").nullable();
|
|
27388
|
+
strictObject({
|
|
27389
|
+
key: string$2().regex(new RegExp(`^${API_KEY_PREFIX}[0-9a-f]{64}$`)),
|
|
27390
|
+
name: string$2().min(1).max(255),
|
|
27391
|
+
scopes: array(_enum(API_KEY_SCOPES)).optional(),
|
|
27392
|
+
expiresInDays: number$1().int().min(1).max(365).optional(),
|
|
27393
|
+
mcpToolsets: mcpToolsets.optional()
|
|
27394
|
+
}), object({ keyId: uuid });
|
|
27395
|
+
strictObject({ mcpToolsets });
|
|
27396
|
+
object({
|
|
27397
|
+
url: string$2().url(),
|
|
27398
|
+
expiresAt: string$2()
|
|
27399
|
+
});
|
|
27400
|
+
object({
|
|
27401
|
+
opens: number$1().int().nonnegative(),
|
|
27402
|
+
closes: number$1().int().nonnegative(),
|
|
27403
|
+
products: array(object({
|
|
27404
|
+
productId: string$2().uuid(),
|
|
27405
|
+
opens: number$1().int().nonnegative(),
|
|
27406
|
+
closes: number$1().int().nonnegative()
|
|
27407
|
+
}))
|
|
27408
|
+
});
|
|
26922
27409
|
object({
|
|
26923
27410
|
status: customDocumentStatus.nullable(),
|
|
26924
27411
|
/** The retry itself threw; absent when it went through. */
|
|
26925
27412
|
error: string$2().optional()
|
|
26926
27413
|
});
|
|
26927
|
-
|
|
26928
|
-
|
|
27414
|
+
/** A factory document type, its legacy name read as the current one. */
|
|
27415
|
+
const factoryDocumentType = _enum([...FACTORY_DOCUMENT_TYPES, LEGACY_FACTORY_INSTRUCTIONS_TYPE]).transform((type) => type === "team_instructions" ? FACTORY_INSTRUCTIONS_TYPE : type);
|
|
27416
|
+
strictObject({ type: factoryDocumentType }), strictObject({ type: factoryDocumentType });
|
|
27417
|
+
/**
|
|
27418
|
+
* A registry scope: an npm scope ("@acme") or a Go module path prefix
|
|
27419
|
+
* ("github.com/acme"), as `POST /registries` takes one (PRO-3670). Which
|
|
27420
|
+
* ecosystem takes which is the service's rule.
|
|
27421
|
+
*/
|
|
27422
|
+
const namedSegments = (value) => value.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
|
|
27423
|
+
const registryScope = string$2().regex(/^@?[A-Za-z0-9][A-Za-z0-9._~/-]*$/, "Not a valid scope").max(214).refine(namedSegments, "Not a valid scope");
|
|
27424
|
+
/** The package a registry's check fetches, in the union of the ecosystems' grammars. */
|
|
27425
|
+
const registryTestPackage = string$2().min(1).max(214).regex(/^@?[A-Za-z0-9_][A-Za-z0-9._~/:-]*$/, "Must be a valid package name").refine(namedSegments, "Must be a valid package name");
|
|
27426
|
+
/** KMS direct-encrypt takes 4096 bytes; the raw secret stays well under. */
|
|
27427
|
+
const REGISTRY_CREDENTIAL_MAX_BYTES = 3500;
|
|
27428
|
+
/** A registry's write-only credential: exactly one of a token, a GCP key or an AWS role, as the route takes it. */
|
|
27429
|
+
const registryCredential = strictObject({
|
|
27430
|
+
token: string$2().min(1).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
|
|
27431
|
+
username: string$2().min(1).max(256).regex(/^[^:\s]+$/, "Username must not contain a colon or whitespace").optional(),
|
|
27432
|
+
credentialsJson: string$2().min(2).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
|
|
27433
|
+
awsRoleArn: string$2().max(600).regex(AWS_ROLE_ARN_PATTERN, "Not an IAM role ARN").optional()
|
|
27434
|
+
}).refine((c) => [
|
|
27435
|
+
c.token,
|
|
27436
|
+
c.credentialsJson,
|
|
27437
|
+
c.awsRoleArn
|
|
27438
|
+
].filter((v) => v != null).length === 1, { message: "Provide exactly one of token, credentialsJson or awsRoleArn" }).refine((c) => c.username == null || c.token != null, { message: "A username goes with a token" }).refine((c) => new TextEncoder().encode(JSON.stringify(c)).length <= 4e3, { message: "Credential is too large to encrypt (must be under 4 KB once JSON-encoded)" });
|
|
27439
|
+
/** An instant as an ISO string, which the service reads as a date. */
|
|
27440
|
+
const isoInstant = string$2().datetime({ offset: true });
|
|
27441
|
+
object({
|
|
27442
|
+
status: _enum([
|
|
27443
|
+
"unvalidated",
|
|
27444
|
+
"valid",
|
|
27445
|
+
"auth_failed",
|
|
27446
|
+
"unreachable"
|
|
27447
|
+
]),
|
|
27448
|
+
lastValidatedAt: string$2(),
|
|
27449
|
+
detail: string$2().nullable()
|
|
27450
|
+
});
|
|
27451
|
+
strictObject({
|
|
27452
|
+
displayName: string$2().min(1).max(255).optional(),
|
|
27453
|
+
registryUrl: string$2().url().max(512).optional(),
|
|
27454
|
+
scopes: array(registryScope).max(50).optional(),
|
|
27455
|
+
expiresAt: isoInstant.nullable().optional(),
|
|
27456
|
+
credential: registryCredential.optional(),
|
|
27457
|
+
testPackage: registryTestPackage.nullable().optional()
|
|
27458
|
+
});
|
|
27459
|
+
/** A GitLab bot token, as the route takes one. */
|
|
27460
|
+
const gitlabToken = string$2().trim().min(20, "That does not look like a GitLab access token").max(1024).regex(/^[A-Za-z0-9_.-]+$/, "That does not look like a GitLab access token");
|
|
27461
|
+
/** A private CA as PEM certificates; the service parses it. */
|
|
27462
|
+
const gitlabCaBundle = string$2().min(1).max(65536);
|
|
27463
|
+
/** What a connect or a token change learned, beyond the connection's record. */
|
|
27464
|
+
const gitlabValidationReport = object({
|
|
27465
|
+
pushableProjectCount: number$1().int().nullable(),
|
|
27466
|
+
warnings: array(string$2())
|
|
27467
|
+
});
|
|
27468
|
+
strictObject({
|
|
27469
|
+
displayName: string$2().trim().min(1).max(255).optional(),
|
|
27470
|
+
token: gitlabToken.optional(),
|
|
27471
|
+
caBundlePem: gitlabCaBundle.nullable().optional()
|
|
27472
|
+
}), gitlabValidationReport.optional(), object({
|
|
27473
|
+
status: _enum(GITLAB_CONNECTION_STATUSES),
|
|
27474
|
+
lastValidatedAt: string$2(),
|
|
27475
|
+
detail: string$2().nullable()
|
|
27476
|
+
}), object({ finished: boolean() }), strictObject({ search: string$2().trim().max(100).optional() }), object({
|
|
27477
|
+
projects: array(object({
|
|
27478
|
+
id: number$1().int(),
|
|
27479
|
+
pathWithNamespace: string$2(),
|
|
27480
|
+
nameWithNamespace: string$2(),
|
|
27481
|
+
webUrl: string$2(),
|
|
27482
|
+
defaultBranch: string$2().nullable()
|
|
27483
|
+
})).nullable(),
|
|
27484
|
+
truncated: boolean()
|
|
27485
|
+
});
|
|
27486
|
+
object({
|
|
27487
|
+
outcome: _enum([
|
|
27488
|
+
"uninstalled",
|
|
27489
|
+
"left_in_use",
|
|
27490
|
+
"uninstall_failed"
|
|
27491
|
+
]),
|
|
27492
|
+
manageUrl: string$2().nullable()
|
|
27493
|
+
});
|
|
27494
|
+
/** A value GitHub's redirect carries back: a state token, or a one-time code. */
|
|
27495
|
+
const githubCallbackSecret = string$2().trim().min(1).max(1024);
|
|
27496
|
+
strictObject({
|
|
27497
|
+
displayName: string$2().trim().min(1).max(255),
|
|
27498
|
+
baseUrl: string$2().trim().min(1).max(480),
|
|
27499
|
+
token: gitlabToken,
|
|
27500
|
+
caBundlePem: gitlabCaBundle.nullable().optional()
|
|
27501
|
+
}), object({
|
|
27502
|
+
connectionId: uuid,
|
|
27503
|
+
validation: gitlabValidationReport
|
|
27504
|
+
}), strictObject({
|
|
27505
|
+
displayName: string$2().min(1).max(255),
|
|
27506
|
+
ecosystem: _enum(REGISTRY_ECOSYSTEMS).optional(),
|
|
27507
|
+
vendor: _enum(REGISTRY_VENDORS),
|
|
27508
|
+
registryUrl: string$2().url().max(512),
|
|
27509
|
+
scopes: array(registryScope).max(50).optional(),
|
|
27510
|
+
expiresAt: isoInstant.nullable().optional(),
|
|
27511
|
+
credential: registryCredential,
|
|
27512
|
+
testPackage: registryTestPackage.optional()
|
|
27513
|
+
}), strictObject({ hostId: uuid.optional() }), object({ installUrl: string$2().url() }), strictObject({ state: githubCallbackSecret }), object({
|
|
27514
|
+
clientId: string$2(),
|
|
27515
|
+
authorizeUrl: string$2(),
|
|
27516
|
+
redirectUri: string$2().optional()
|
|
27517
|
+
}), strictObject({
|
|
27518
|
+
installationId: number$1().int().positive(),
|
|
27519
|
+
setupAction: _enum(["install", "update"]),
|
|
27520
|
+
state: githubCallbackSecret.optional(),
|
|
27521
|
+
code: githubCallbackSecret.optional()
|
|
27522
|
+
}), strictObject({
|
|
27523
|
+
subdomain: string$2().min(1).max(39).optional(),
|
|
27524
|
+
serverUrl: string$2().min(1).max(2048).optional(),
|
|
27525
|
+
caBundlePem: string$2().max(65536).optional(),
|
|
27526
|
+
orgLogin: string$2().min(1).max(39),
|
|
27527
|
+
displayName: string$2().max(255).optional()
|
|
27528
|
+
}), object({
|
|
27529
|
+
manifest: record(string$2(), unknown$1()),
|
|
27530
|
+
postUrl: string$2().url(),
|
|
27531
|
+
state: string$2()
|
|
27532
|
+
}), strictObject({
|
|
27533
|
+
state: githubCallbackSecret,
|
|
27534
|
+
code: githubCallbackSecret
|
|
27535
|
+
}), strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({ type: _enum(TENANT_DOCUMENT_TYPES).refine((type) => type !== "design_standards", { message: "The design standards draft is not made through sync" }) }), strictObject({
|
|
26929
27536
|
name: string$2().min(1).max(50),
|
|
26930
27537
|
action: _enum(["accept", "decline"])
|
|
26931
27538
|
}), object({
|
|
@@ -26964,7 +27571,26 @@ strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({
|
|
|
26964
27571
|
}), strictObject({ ids: array(uuid).min(1).max(25) }), object({ referenced: array(string$2()) }), strictObject({
|
|
26965
27572
|
approve: array(string$2().min(1).max(100)).max(200).default([]),
|
|
26966
27573
|
remove: array(string$2().min(1).max(100)).max(200).default([])
|
|
26967
|
-
}).refine((b) => b.approve.length + b.remove.length > 0, { message: "Name a license to approve or remove" }), object({ version: number$1().nullable() })
|
|
27574
|
+
}).refine((b) => b.approve.length + b.remove.length > 0, { message: "Name a license to approve or remove" }), object({ version: number$1().nullable() }), strictObject({
|
|
27575
|
+
approve: string$2().trim().min(1).max(100).optional(),
|
|
27576
|
+
remove: string$2().trim().min(1).max(100).optional()
|
|
27577
|
+
}).refine((a) => Boolean(a.approve) !== Boolean(a.remove), { message: "Name one license to approve or remove" }), object({
|
|
27578
|
+
enrolled: number$1().int().nonnegative(),
|
|
27579
|
+
total: number$1().int().nonnegative()
|
|
27580
|
+
}), object({
|
|
27581
|
+
principalArn: string$2().nullable(),
|
|
27582
|
+
externalId: string$2()
|
|
27583
|
+
});
|
|
27584
|
+
strictObject({ kind: _enum(["sbom", "notices"]) }), object({
|
|
27585
|
+
url: string$2().url().nullable(),
|
|
27586
|
+
expiresAt: string$2().nullable()
|
|
27587
|
+
}), object({
|
|
27588
|
+
branches: array(object({
|
|
27589
|
+
name: string$2(),
|
|
27590
|
+
protected: boolean()
|
|
27591
|
+
})).nullable(),
|
|
27592
|
+
truncated: boolean()
|
|
27593
|
+
});
|
|
26968
27594
|
strictObject({
|
|
26969
27595
|
ignoreReason: _enum(MAINTENANCE_IGNORE_REASONS),
|
|
26970
27596
|
ignoreNote,
|
|
@@ -26976,6 +27602,11 @@ strictObject({ tier: _enum([
|
|
|
26976
27602
|
"staging",
|
|
26977
27603
|
"production"
|
|
26978
27604
|
]) });
|
|
27605
|
+
strictObject({
|
|
27606
|
+
name: string$2().min(1).max(150).optional(),
|
|
27607
|
+
status: _enum(CLOUD_CONNECTION_STATUSES).optional(),
|
|
27608
|
+
providerConfig: record(string$2(), unknown$1()).refine(isTenantWritableProviderConfig, { message: TAIGA_MANAGED_PROVIDER_CONFIG_MESSAGE }).optional()
|
|
27609
|
+
});
|
|
26979
27610
|
object({
|
|
26980
27611
|
url: string$2().url(),
|
|
26981
27612
|
expiresAt: string$2(),
|
|
@@ -27167,6 +27798,7 @@ const TOOLSET_OF = {
|
|
|
27167
27798
|
taiga_fix_finding: "operate",
|
|
27168
27799
|
taiga_fix_policy_violation: "operate",
|
|
27169
27800
|
taiga_ignore_finding: "operate",
|
|
27801
|
+
taiga_allow_license: "operate",
|
|
27170
27802
|
taiga_resolve_action: "operate",
|
|
27171
27803
|
taiga_create_factory: "setup",
|
|
27172
27804
|
taiga_rename_factory: "setup",
|
|
@@ -27191,6 +27823,7 @@ const TOOLSET_OF = {
|
|
|
27191
27823
|
taiga_pause_line: "setup",
|
|
27192
27824
|
taiga_create_environment: "setup",
|
|
27193
27825
|
taiga_update_environment: "setup",
|
|
27826
|
+
taiga_ship_notices: "setup",
|
|
27194
27827
|
taiga_get_deployments: "observe",
|
|
27195
27828
|
taiga_get_monitoring: "observe",
|
|
27196
27829
|
taiga_get_delivery_metrics: "observe",
|
|
@@ -27242,6 +27875,7 @@ const DELIVERY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
27242
27875
|
"taiga_request_initiatives",
|
|
27243
27876
|
"taiga_fix_finding",
|
|
27244
27877
|
"taiga_fix_policy_violation",
|
|
27878
|
+
"taiga_ship_notices",
|
|
27245
27879
|
"taiga_set_auto_build",
|
|
27246
27880
|
"taiga_set_auto_merge",
|
|
27247
27881
|
"taiga_pause_line",
|
|
@@ -27491,7 +28125,6 @@ function setupNextStep(product) {
|
|
|
27491
28125
|
if (product.discoveryStep === void 0) return null;
|
|
27492
28126
|
switch (product.discoveryStep) {
|
|
27493
28127
|
case "documents": return `The specification is done; the other documents are generated from it (taiga_generate_product_documents starts the ones still missing). Once every required one is published, ${FINISH_DISCOVERY}`;
|
|
27494
|
-
case "checks": return `The documents are published; the checks are stamped in the web, then ${FINISH_DISCOVERY}`;
|
|
27495
28128
|
default: return `Discovery is a conversation held in the web: the person describes the product there and the specification comes out of it. The other documents follow, then ${FINISH_DISCOVERY}`;
|
|
27496
28129
|
}
|
|
27497
28130
|
}
|
|
@@ -27520,8 +28153,16 @@ const BY_ARGUMENT = {
|
|
|
27520
28153
|
knowledgeId: "taiga_list_knowledge lists the knowledge files.",
|
|
27521
28154
|
productId: "taiga_list_products lists the products the key can see; a name is not an ID (taiga_get_product takes a slug)."
|
|
27522
28155
|
};
|
|
28156
|
+
/**
|
|
28157
|
+
* A licence a product does not hold, as against a product the key cannot see
|
|
28158
|
+
* (PRO-3879). Any other 404 (the routes do not exist where the licenses
|
|
28159
|
+
* feature is off) blames neither.
|
|
28160
|
+
*/
|
|
28161
|
+
const licenseMiss = (hint) => (said) => /\blicen[cs]e\b/i.test(said) ? hint : /\b(product|project) not found/i.test(said) ? BY_ARGUMENT["productId"] : void 0;
|
|
27523
28162
|
/** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
|
|
27524
28163
|
const BY_TOOL = {
|
|
28164
|
+
taiga_allow_license: licenseMiss("taiga_list_packages (productId) summarizes the product's licenses as its findings name them; a license with no finding there has nothing to allow or reopen."),
|
|
28165
|
+
taiga_list_licenses: licenseMiss("taiga_list_licenses without license lists the licenses in use, and taiga_list_packages (productId) one product's, spelled as their findings spell them."),
|
|
27525
28166
|
taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
|
|
27526
28167
|
taiga_get_sbom: () => "An SBOM exists once a sweep has inventoried that repository, where the licenses feature is enabled; taiga_list_packages (linkId) says whether it has packages, and taiga_list_product_repositories lists the links.",
|
|
27527
28168
|
taiga_get_notices: () => "Notices exist once a sweep has inventoried that repository, where the licenses feature is enabled; taiga_list_product_repositories lists the links.",
|
|
@@ -27998,6 +28639,8 @@ const PACKAGE_ROW_FIELDS = [
|
|
|
27998
28639
|
"scopeReason",
|
|
27999
28640
|
"parentReason",
|
|
28000
28641
|
"sourcePath",
|
|
28642
|
+
"baseImage",
|
|
28643
|
+
"imageLayer",
|
|
28001
28644
|
"productRepositoryId",
|
|
28002
28645
|
"detectedAt"
|
|
28003
28646
|
];
|
|
@@ -28019,7 +28662,11 @@ const FINDING_ROW_FIELDS = [
|
|
|
28019
28662
|
"tier",
|
|
28020
28663
|
"kevListedAt",
|
|
28021
28664
|
"malicious",
|
|
28665
|
+
"baseMove",
|
|
28666
|
+
"baseImageMove",
|
|
28667
|
+
"baseImageFix",
|
|
28022
28668
|
"removal",
|
|
28669
|
+
"replaceable",
|
|
28023
28670
|
"status",
|
|
28024
28671
|
"title",
|
|
28025
28672
|
"ecosystem",
|
|
@@ -28145,14 +28792,22 @@ function withFixability(row) {
|
|
|
28145
28792
|
return {
|
|
28146
28793
|
...row,
|
|
28147
28794
|
fixable: false,
|
|
28148
|
-
fixNote: reason === "not_shipped" ? "No longer applies: the package is only a build for platforms the repository does not deploy to. Nothing to do." : reason === "license_approved" ? "No longer applies: its license is on the organization's approved list. Nothing to do." : reason === "license_undecided" ? "No longer applies: its license can no longer be judged against the list (some of what it can stand for is approved). Nothing to do." : reason === "superseded" ? "Replaced by the repository's finding of its license, which lists every package under it. Nothing to do here." : "Resolved: the sweep no longer reports it. Nothing to do."
|
|
28795
|
+
fixNote: reason === "not_shipped" ? "No longer applies: the package is only a build for platforms the repository does not deploy to. Nothing to do." : reason === "license_approved" ? "No longer applies: its license is on the organization's approved list. Nothing to do." : reason === "license_undecided" ? "No longer applies: its license can no longer be judged against the list (some of what it can stand for is approved). Nothing to do." : reason === "superseded" ? "Replaced by the repository's finding of its license, which lists every package under it. Nothing to do here." : reason === "license_not_judged" ? "No longer applies: an OS package's license is judged only when it restricts use. Its packages keep it. Nothing to do." : "Resolved: the sweep no longer reports it. Nothing to do."
|
|
28149
28796
|
};
|
|
28150
28797
|
}
|
|
28151
|
-
if (row["kind"] === "license_violation") return {
|
|
28798
|
+
if (row["kind"] === "license_violation" && row["replaceable"] === true) return {
|
|
28152
28799
|
...row,
|
|
28153
|
-
fixable:
|
|
28154
|
-
fixNote: "
|
|
28800
|
+
fixable: true,
|
|
28801
|
+
fixNote: "taiga_fix_finding raises an initiative that replaces the packages under this license with ones whose license, and their own dependencies' licenses, are on the approved list. It never approves the license: that is taiga_change_license, or ignoring the finding."
|
|
28155
28802
|
};
|
|
28803
|
+
if (row["kind"] === "license_violation") {
|
|
28804
|
+
const status = String(row["status"]);
|
|
28805
|
+
return {
|
|
28806
|
+
...row,
|
|
28807
|
+
fixable: false,
|
|
28808
|
+
fixNote: status === "remediating" ? "Being fixed: an initiative replaces the packages under this license, and its plan may be waiting for a person's review. taiga_list_initiatives with query \"Replace the\" finds it; follow it there." : status === "open" ? "This Taiga does not replace the packages under a license yet, so taiga_fix_finding refuses it: approve the license (taiga_change_license) or ignore the finding." : `taiga_fix_finding takes only an open license finding, and this one is ${status}.`
|
|
28809
|
+
};
|
|
28810
|
+
}
|
|
28156
28811
|
const dependency = row["kind"] === "vulnerable_dependency" || row["kind"] === "outdated_dependency";
|
|
28157
28812
|
const signals = {
|
|
28158
28813
|
malicious: typeof row["malicious"] === "boolean" ? row["malicious"] : null,
|
|
@@ -28176,6 +28831,38 @@ function withFixability(row) {
|
|
|
28176
28831
|
fixable: true,
|
|
28177
28832
|
fixNote: "A malicious package: taiga_fix_finding removes it (or replaces it), with every advisory on that version."
|
|
28178
28833
|
} : row;
|
|
28834
|
+
const held = row["autoFixHeld"];
|
|
28835
|
+
const inBase = row["baseImageFix"] != null || typeof row["baseMove"] === "boolean";
|
|
28836
|
+
if (row["status"] === "open" && !inBase && held && isPinBlocked({ autoFixHeld: held })) {
|
|
28837
|
+
const what = held.blockingPackage ?? "another dependency";
|
|
28838
|
+
return {
|
|
28839
|
+
...row,
|
|
28840
|
+
fixable: false,
|
|
28841
|
+
fixNote: `Waiting on ${what}${held.blockingConstraint ? `, which requires ${held.blockingConstraint}` : ""}. taiga_fix_finding refuses it until that moves.`
|
|
28842
|
+
};
|
|
28843
|
+
}
|
|
28844
|
+
const base = row["baseImageFix"];
|
|
28845
|
+
if (base && base.way !== "move" && isAgentRemediable({ remediableBy: remediableByOf(row) })) {
|
|
28846
|
+
if (base.way === "upgrade") return row["status"] === "open" ? {
|
|
28847
|
+
...row,
|
|
28848
|
+
fixable: true,
|
|
28849
|
+
fixNote: "taiga_fix_finding adds a temporary line to the Dockerfile that upgrades this package over its base, until the base publishes a build with the fix."
|
|
28850
|
+
} : row;
|
|
28851
|
+
return {
|
|
28852
|
+
...row,
|
|
28853
|
+
fixable: false,
|
|
28854
|
+
fixNote: baseImageWaitNote(base)
|
|
28855
|
+
};
|
|
28856
|
+
}
|
|
28857
|
+
if (typeof row["baseMove"] === "boolean" && isAgentRemediable({ remediableBy: remediableByOf(row) })) return row["baseMove"] === true && row["status"] === "open" ? {
|
|
28858
|
+
...row,
|
|
28859
|
+
fixable: true,
|
|
28860
|
+
fixNote: "taiga_fix_finding moves the base images it is in to the builds their tags name now, the FROM line alone, with every other advisory that move removes."
|
|
28861
|
+
} : {
|
|
28862
|
+
...row,
|
|
28863
|
+
fixable: false,
|
|
28864
|
+
fixNote: "Inside a base image no newer build of which removes it yet: it is fixed by a base move, not a package bump, and is held until one does."
|
|
28865
|
+
};
|
|
28179
28866
|
if (dependency && !row["recommendedVersion"] && row["remediableBy"] === "coding_agent") return {
|
|
28180
28867
|
...row,
|
|
28181
28868
|
fixable: false,
|
|
@@ -28237,6 +28924,14 @@ function withResolutionLabel(row) {
|
|
|
28237
28924
|
previouslyResolvedReason: resolvedReason
|
|
28238
28925
|
};
|
|
28239
28926
|
}
|
|
28927
|
+
/** Why a base image's finding has no Fix, in the page's words (`maintaining_base_wait_*`). */
|
|
28928
|
+
function baseImageWaitNote(fix) {
|
|
28929
|
+
if (fix.way === "wait_for_distro") return `${fix.distro} has no fix for this yet.`;
|
|
28930
|
+
if (fix.way === "end_of_life") return `${fix.distro} has reached its end of life, so upgrading this one package is no fix. It clears when the base moves to a supported release.`;
|
|
28931
|
+
const image = fix.images.join(", ") || "The base image";
|
|
28932
|
+
return `${fix.distro} has fixed this, but ${image} has not published a build with the fix yet. It clears when it does.`;
|
|
28933
|
+
}
|
|
28934
|
+
const remediableByOf = (row) => typeof row["remediableBy"] === "string" ? row["remediableBy"] : null;
|
|
28240
28935
|
/** A finding as a tool answers it: the row fields, the scope, and whether Fix would take it. */
|
|
28241
28936
|
function findingRows(findings, extra = []) {
|
|
28242
28937
|
return pickFields(findings, [
|
|
@@ -28253,7 +28948,10 @@ const ADVISORY_KEPT = [
|
|
|
28253
28948
|
"summary",
|
|
28254
28949
|
"details",
|
|
28255
28950
|
"severity",
|
|
28256
|
-
"
|
|
28951
|
+
"severitySource",
|
|
28952
|
+
"cvssScore",
|
|
28953
|
+
"published",
|
|
28954
|
+
"baseImage"
|
|
28257
28955
|
];
|
|
28258
28956
|
const ADVISORY_TEXT_LIMIT = 1500;
|
|
28259
28957
|
/** The packages of a licence finding shown without full: true (PRO-3394); the rest are counted. */
|
|
@@ -28294,6 +28992,36 @@ const SWEEP_FIELDS = [
|
|
|
28294
28992
|
"createdAt",
|
|
28295
28993
|
"completedAt"
|
|
28296
28994
|
];
|
|
28995
|
+
/**
|
|
28996
|
+
* The images a sweep's shipped images build on that it could not read, and
|
|
28997
|
+
* why (PRO-4073): a base nobody read is no clean base. Only where the API
|
|
28998
|
+
* returns the list (null with the containerImages flag off, or from a sweep
|
|
28999
|
+
* that listed none).
|
|
29000
|
+
*/
|
|
29001
|
+
const BASE_IMAGES_SHOWN = 10;
|
|
29002
|
+
function notReadBases(sweep) {
|
|
29003
|
+
const bases = sweep.baseImages ?? [];
|
|
29004
|
+
const notRead = bases.filter((b) => b.state === "not_read" || b.packages?.state === "not_read");
|
|
29005
|
+
const read = bases.flatMap((b) => b.packages?.state === "read" ? [{
|
|
29006
|
+
image: b.image,
|
|
29007
|
+
path: b.path,
|
|
29008
|
+
os: b.os ?? null,
|
|
29009
|
+
vulnerabilities: b.packages.vulnerabilities,
|
|
29010
|
+
removedByMoving: b.packages.candidate?.removes ?? 0,
|
|
29011
|
+
noDistroFix: b.packages.noDistroFix ?? null,
|
|
29012
|
+
movesTo: b.packages.candidate?.image ?? null
|
|
29013
|
+
}] : []);
|
|
29014
|
+
return {
|
|
29015
|
+
...read.length > 0 ? {
|
|
29016
|
+
baseImages: read.slice(0, BASE_IMAGES_SHOWN),
|
|
29017
|
+
...read.length > BASE_IMAGES_SHOWN ? { baseImagesCount: read.length } : {}
|
|
29018
|
+
} : {},
|
|
29019
|
+
...notRead.length > 0 ? {
|
|
29020
|
+
baseImagesNotRead: notRead.slice(0, BASE_IMAGES_SHOWN),
|
|
29021
|
+
...notRead.length > BASE_IMAGES_SHOWN ? { baseImagesNotReadCount: notRead.length } : {}
|
|
29022
|
+
} : {}
|
|
29023
|
+
};
|
|
29024
|
+
}
|
|
28297
29025
|
/** A policy violation in a list: what it is, how bad, and what is being done about it. */
|
|
28298
29026
|
const VIOLATION_FIELDS = [
|
|
28299
29027
|
"id",
|
|
@@ -28350,11 +29078,51 @@ function standingSummary(standing) {
|
|
|
28350
29078
|
resolvedViolations: resolved
|
|
28351
29079
|
};
|
|
28352
29080
|
}
|
|
29081
|
+
/**
|
|
29082
|
+
* How the backlog covers the controls that need a feature (PRO-3860), one row
|
|
29083
|
+
* each: the live initiatives that build it, or why it is deferred, and whether
|
|
29084
|
+
* nothing covers it. Empty before a backlog answered for any. It says what is
|
|
29085
|
+
* planned, never that a control is met.
|
|
29086
|
+
*/
|
|
29087
|
+
function controlCoverageList(controls) {
|
|
29088
|
+
return (Array.isArray(controls) ? controls : []).flatMap((control) => {
|
|
29089
|
+
const coverage = control["coverage"];
|
|
29090
|
+
if (!coverage || typeof coverage !== "object") return [];
|
|
29091
|
+
const initiatives = Array.isArray(coverage["initiatives"]) ? coverage["initiatives"] : [];
|
|
29092
|
+
return [{
|
|
29093
|
+
controlId: control["controlId"],
|
|
29094
|
+
state: control["state"],
|
|
29095
|
+
planned: plannedFor(coverage),
|
|
29096
|
+
uncovered: coverage["uncovered"] === true,
|
|
29097
|
+
...initiatives.length > 0 ? { initiatives: initiatives.map((item) => ({
|
|
29098
|
+
id: item["id"],
|
|
29099
|
+
title: item["title"]
|
|
29100
|
+
})) } : {},
|
|
29101
|
+
...coverage["deferredReason"] ? { deferredReason: coverage["deferredReason"] } : {}
|
|
29102
|
+
}];
|
|
29103
|
+
});
|
|
29104
|
+
}
|
|
29105
|
+
/**
|
|
29106
|
+
* Whether the backlog plans a control: yes when a live initiative builds it, later when it deferred it with a
|
|
29107
|
+
* reason, no otherwise. A deferred control reads "planned later" rather than only "not assessed" (PRO-3860).
|
|
29108
|
+
*/
|
|
29109
|
+
function plannedFor(coverage) {
|
|
29110
|
+
if (Array.isArray(coverage["initiatives"]) && coverage["initiatives"].length > 0) return "yes";
|
|
29111
|
+
return coverage["deferredReason"] ? "later" : "no";
|
|
29112
|
+
}
|
|
29113
|
+
/** The coverage, when the backlog answered for any control, for the default and full answers. */
|
|
29114
|
+
function controlCoveragePart(controls) {
|
|
29115
|
+
const coverage = controlCoverageList(controls);
|
|
29116
|
+
return coverage.length > 0 ? { controlCoverage: coverage } : {};
|
|
29117
|
+
}
|
|
28353
29118
|
const findingId = idInput("finding", "taiga_list_findings gives it");
|
|
28354
29119
|
/** Open findings by severity and kind, and the latest sweeps, as the first page of findings carries them. */
|
|
28355
29120
|
async function sweepOverview(client, productId) {
|
|
28356
29121
|
const [summary, sweeps] = await Promise.all([client.getMaintenanceSummary(productId), client.listMaintenanceSweeps(productId, { limit: 3 })]);
|
|
28357
|
-
const latest =
|
|
29122
|
+
const latest = sweeps.data.map((sweep) => ({
|
|
29123
|
+
...pickFields([sweep], SWEEP_FIELDS)[0],
|
|
29124
|
+
...notReadBases(sweep)
|
|
29125
|
+
}));
|
|
28358
29126
|
const { lastRun: _lastRun, lastRuns: _lastRuns, topFindings: _top, violatedControls: _controls, ...counts } = summary.data;
|
|
28359
29127
|
return {
|
|
28360
29128
|
...counts,
|
|
@@ -28362,6 +29130,20 @@ async function sweepOverview(client, productId) {
|
|
|
28362
29130
|
...latest.length === 0 ? { note: "This product has never been swept: no findings is not an all-clear." } : {}
|
|
28363
29131
|
};
|
|
28364
29132
|
}
|
|
29133
|
+
/** The organization's licence read takes an id this long at most, as `organizationLicenseQuery` does. */
|
|
29134
|
+
const ORGANIZATION_LICENSE_MAX = 200;
|
|
29135
|
+
/**
|
|
29136
|
+
* Why a license is allowed in a product, in a license's words, as the web's
|
|
29137
|
+
* Allow dialog offers them (PRO-3727); each is stored as the ignore reason
|
|
29138
|
+
* beside it.
|
|
29139
|
+
*/
|
|
29140
|
+
const LICENSE_ALLOW_REASON_TO_IGNORE = {
|
|
29141
|
+
accepted: "accepted_risk",
|
|
29142
|
+
not_handed_out: "not_reachable",
|
|
29143
|
+
no_alternative: "wont_fix",
|
|
29144
|
+
read_wrong: "false_positive"
|
|
29145
|
+
};
|
|
29146
|
+
const LICENSE_ALLOW_REASONS = Object.keys(LICENSE_ALLOW_REASON_TO_IGNORE);
|
|
28365
29147
|
function registerMaintenanceTools(server, client) {
|
|
28366
29148
|
if (client.hasCapability("tenant:settings")) server.registerTool("taiga_change_license", {
|
|
28367
29149
|
title: "Approve or remove a license",
|
|
@@ -28449,10 +29231,18 @@ function registerMaintenanceTools(server, client) {
|
|
|
28449
29231
|
}));
|
|
28450
29232
|
server.registerTool("taiga_list_licenses", {
|
|
28451
29233
|
title: "List the organization's licenses",
|
|
28452
|
-
description: "List the organization's approved license list and every license in use across its products, approved or not, with packages, products and repositories. With license, where that one is used: products, repositories
|
|
28453
|
-
inputSchema: object({
|
|
29234
|
+
description: "List the organization's approved license list and every license in use across its products, approved or not, with packages, products and repositories. With license, where that one is used: products, repositories, what brings its packages in, and its approved-list history. With license and productId, that license in one product: its standing against the list, what it asks, when to allow it there, how many packages ship, and what brings them in. Decide it there with taiga_allow_license. NOT_FOUND where the licenses feature is off.",
|
|
29235
|
+
inputSchema: object({
|
|
29236
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX).optional().describe("An SPDX license id or expression: where it is used, instead of the list"),
|
|
29237
|
+
productId: productId.optional().describe("With license: that license in this product only (taiga_list_products gives it)")
|
|
29238
|
+
}),
|
|
28454
29239
|
annotations: READ_ONLY
|
|
28455
|
-
}, (params) => runTool(async () =>
|
|
29240
|
+
}, (params) => runTool(async () => {
|
|
29241
|
+
if (params.productId !== void 0 && params.license === void 0) return toolError("VALIDATION_ERROR", "Name the license to read in this product (license).");
|
|
29242
|
+
if (params.license === void 0) return toolResult((await client.getOrganizationLicenses()).data);
|
|
29243
|
+
if (params.productId === void 0 && params.license.length > ORGANIZATION_LICENSE_MAX) return toolError("VALIDATION_ERROR", `A license id is at most ${ORGANIZATION_LICENSE_MAX} characters; name a product (productId) to read a longer expression.`);
|
|
29244
|
+
return toolResult(params.productId !== void 0 ? (await client.getProductLicense(params.productId, params.license)).data : (await client.getOrganizationLicense(params.license)).data);
|
|
29245
|
+
}));
|
|
28456
29246
|
server.registerTool("taiga_get_sbom", {
|
|
28457
29247
|
title: "Download a repository SBOM",
|
|
28458
29248
|
description: "Download one repository's software bill of materials (CycloneDX 1.6). Large; taiga_list_packages summarizes. NOT_FOUND where the licenses feature is off, or before a sweep has read the repository.",
|
|
@@ -28519,7 +29309,8 @@ function registerMaintenanceTools(server, client) {
|
|
|
28519
29309
|
const own = violations.filter((violation) => String(violation["controlId"]).toUpperCase() === wanted);
|
|
28520
29310
|
return toolResult({
|
|
28521
29311
|
...control,
|
|
28522
|
-
...control.state === "no_evidence" ? { meaning: "Not assessed: no finding or violation is recorded against it, and the documents were not found to show it met. That is no evidence either way, not a pass." } : {},
|
|
29312
|
+
...control.state === "no_evidence" ? { meaning: (control.coverage && plannedFor(control.coverage) === "later" ? `Planned later: the backlog deferred it (${String(control.coverage.deferredReason).replace(/\.$/, "")}). ` : "") + "Not assessed: no finding or violation is recorded against it, and the documents were not found to show it met. That is no evidence either way, not a pass." } : {},
|
|
29313
|
+
...control.coverage ? { coverageNote: "coverage is how the backlog plans for it: the live initiatives that build it, or why it is deferred. It says what is planned, not that the control is met." } : {},
|
|
28523
29314
|
...control.assessment == null && (control.state === "breached" || control.state === "diverged") ? { assessmentNote: "No met or not-applicable verdict is shown: an open finding or violation against the control outranks one." } : {},
|
|
28524
29315
|
...Number(control.findingCount ?? 0) > 0 ? { findingsNote: `${String(control.findingCount)} open scanner finding${control.findingCount === 1 ? "" : "s"} breach it; taiga_list_findings (productId) lists them with the controls each breaches. violations below are the documents' only.` } : {},
|
|
28525
29316
|
violations: pickFields(own, VIOLATION_FIELDS),
|
|
@@ -28542,6 +29333,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
28542
29333
|
...wording,
|
|
28543
29334
|
...standing,
|
|
28544
29335
|
controls: listed,
|
|
29336
|
+
...controlCoveragePart(controls),
|
|
28545
29337
|
divergences: cutLongText(violations),
|
|
28546
29338
|
resolvedDivergences: cutLongText(resolvedAll.slice(0, FULL_RESOLVED)),
|
|
28547
29339
|
...resolvedAll.length > FULL_RESOLVED ? { resolvedNote: `The first ${FULL_RESOLVED} of ${resolvedAll.length} resolved violations.` } : {}
|
|
@@ -28565,7 +29357,8 @@ function registerMaintenanceTools(server, client) {
|
|
|
28565
29357
|
"findingsByTier",
|
|
28566
29358
|
"plannedCount"
|
|
28567
29359
|
]),
|
|
28568
|
-
violations: pickFields(violations, VIOLATION_FIELDS)
|
|
29360
|
+
violations: pickFields(violations, VIOLATION_FIELDS),
|
|
29361
|
+
...controlCoveragePart(controls)
|
|
28569
29362
|
});
|
|
28570
29363
|
}));
|
|
28571
29364
|
if (!client.hasCapability("products:write")) return;
|
|
@@ -28591,9 +29384,31 @@ function registerMaintenanceTools(server, client) {
|
|
|
28591
29384
|
const finding = (await client.updateFinding(params.findingId, body)).data;
|
|
28592
29385
|
return toolResult(findingRows([finding], ["ignoreNote"])[0]);
|
|
28593
29386
|
}));
|
|
29387
|
+
server.registerTool("taiga_allow_license", {
|
|
29388
|
+
title: "Allow or reopen a license in a product",
|
|
29389
|
+
description: "Allow a license in one product without approving it for the organization: every license finding of it there that is open, or whose ignore lapsed, is ignored with one reason, as the web's Allow in this product does; one already ignored keeps its reason and expiry, so reopen first to change them. allowed: false reopens them. Another product using the license still raises its finding; to approve it everywhere, use taiga_change_license. Allowing a license is the person's decision. NOT_FOUND when the product has no license finding for it.",
|
|
29390
|
+
inputSchema: object({
|
|
29391
|
+
productId,
|
|
29392
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX).describe("The license as the product's license findings or taiga_list_packages name it, such as MPL-2.0"),
|
|
29393
|
+
allowed: boolean().describe("true allows the license in this product, false reopens its findings"),
|
|
29394
|
+
reason: _enum(LICENSE_ALLOW_REASONS).optional().describe("Why it is allowed; required when allowing. accepted: reviewed and allowed here, usually for a limited time; not_handed_out: the product runs only internally or on your own servers, its code is never given to anyone; no_alternative: no package under an approved license does the job yet; read_wrong: the packages are not actually under this license"),
|
|
29395
|
+
note: string$2().trim().max(280).optional().describe("A short justification, kept for audit"),
|
|
29396
|
+
expiresAt: string$2().datetime({ offset: true }).meta({ pattern: void 0 }).optional().describe("When the allowance lapses and the findings come back if still there (ISO 8601, in the future)")
|
|
29397
|
+
}),
|
|
29398
|
+
annotations: OPERATE_SETTING
|
|
29399
|
+
}, (params) => runTool(async () => {
|
|
29400
|
+
if (!params.allowed) return toolResult((await client.reopenProductLicense(params.productId, params.license)).data);
|
|
29401
|
+
if (params.reason === void 0) return toolError("VALIDATION_ERROR", "Say why the license is allowed (reason).");
|
|
29402
|
+
return toolResult((await client.allowProductLicense(params.productId, {
|
|
29403
|
+
license: params.license,
|
|
29404
|
+
ignoreReason: LICENSE_ALLOW_REASON_TO_IGNORE[params.reason],
|
|
29405
|
+
...params.note !== void 0 ? { ignoreNote: params.note } : {},
|
|
29406
|
+
...params.expiresAt !== void 0 ? { ignoreExpiresAt: params.expiresAt } : {}
|
|
29407
|
+
})).data);
|
|
29408
|
+
}));
|
|
28594
29409
|
server.registerTool("taiga_fix_finding", {
|
|
28595
29410
|
title: "Fix a finding",
|
|
28596
|
-
description: "Raise an initiative that fixes a finding; it ends in a pull request, which merges on its own only where the product's auto-merge allows it. wholeGroup: true fixes, in that repository, every open advisory on that package version in one bump, or every place the rule flagged. A malicious package is removed, with every advisory on that version, whatever wholeGroup says. A language runtime moves every file declaring it together, in a container image only to a tag the image has published. Refused when there is no version to move to (fixable: false; for a runtime, also while its image has not published the fix), the code is in a submodule, or a secret is only in history (rotate it outside Taiga).",
|
|
29411
|
+
description: "Raise an initiative that fixes a finding; it ends in a pull request, which merges on its own only where the product's auto-merge allows it. wholeGroup: true fixes, in that repository, every open advisory on that package version in one bump, or every place the rule flagged. A malicious package is removed, with every advisory on that version, whatever wholeGroup says. A license finding the row marks fixable has its packages replaced by ones on the approved list. A language runtime moves every file declaring it together, in a container image only to a tag the image has published. Refused when there is no version to move to (fixable: false; for a runtime, also while its image has not published the fix), the code is in a submodule, or a secret is only in history (rotate it outside Taiga).",
|
|
28597
29412
|
inputSchema: object({
|
|
28598
29413
|
productId,
|
|
28599
29414
|
findingId,
|
|
@@ -28632,6 +29447,13 @@ function registerMaintenanceTools(server, client) {
|
|
|
28632
29447
|
const body = { refuseIfFixedSinceAssessment: params.fixAgain !== true };
|
|
28633
29448
|
return toolResult((params.controlId ? await client.remediatePolicyControl(params.productId, params.controlId, body) : await client.remediatePolicyViolation(params.productId, params.violationId, body)).data);
|
|
28634
29449
|
}));
|
|
29450
|
+
if (!client.hasCapability("backlog:write")) return;
|
|
29451
|
+
server.registerTool("taiga_ship_notices", {
|
|
29452
|
+
title: "Ship open-source notices",
|
|
29453
|
+
description: "Raise the initiative that builds open-source notices into a product's Vite web app: every production build writes the license texts and NOTICE files of the packages in its browser bundle, and the app shows them at /legal/open-source. Taiga writes the brief from the inventory; the plan waits for review before it builds. One that is already open is handed back (created: false) rather than raised again. Refused where no repository of the product declares Vite.",
|
|
29454
|
+
inputSchema: object({ productId }),
|
|
29455
|
+
annotations: OPERATE
|
|
29456
|
+
}, (params) => runTool(async () => toolResult((await client.shipNotices(params.productId)).data)));
|
|
28635
29457
|
}
|
|
28636
29458
|
//#endregion
|
|
28637
29459
|
//#region src/tools/sections.ts
|
|
@@ -28957,20 +29779,17 @@ async function setupReadiness(client, product, links) {
|
|
|
28957
29779
|
const settings = product["settings"] ?? {};
|
|
28958
29780
|
const importState = imported ? IMPORT_STATE[String(settings.importStatus)] ?? "not started" : void 0;
|
|
28959
29781
|
const missing = blockers ? blockers.missingDocuments : null;
|
|
28960
|
-
const openChecks = blockers ? blockers.openChecks.map((check) => check.criterion) : null;
|
|
28961
29782
|
return {
|
|
28962
|
-
ready: blockers ? missing.length === 0
|
|
29783
|
+
ready: blockers ? missing.length === 0 : null,
|
|
28963
29784
|
repository,
|
|
28964
29785
|
...imported ? { import: typeof settings.importStatus === "string" ? settings.importStatus : "pending" } : {},
|
|
28965
29786
|
...imported ? {} : { discoveryStep: full["discoveryStep"] ?? null },
|
|
28966
29787
|
missingDocuments: missing,
|
|
28967
|
-
openChecks,
|
|
28968
29788
|
nextStep: nextStepFor({
|
|
28969
29789
|
imported,
|
|
28970
29790
|
importState,
|
|
28971
29791
|
linked: repository?.linked ?? null,
|
|
28972
29792
|
missing,
|
|
28973
|
-
openChecks,
|
|
28974
29793
|
product: full
|
|
28975
29794
|
})
|
|
28976
29795
|
};
|
|
@@ -28992,7 +29811,6 @@ function nextStepFor(state) {
|
|
|
28992
29811
|
if (!state.missing) return setupNextStep(state.product);
|
|
28993
29812
|
if (state.missing.includes("specification")) return "The specification comes from the Discovery conversation, held in the web: the person describes the product there. The other documents follow from it." + (state.linked === false ? " Linking a repository (taiga_link_repository) can wait until before the first build." : "");
|
|
28994
29813
|
if (state.missing.length > 0) return `Still to publish: ${named(state.missing)}. taiga_generate_product_documents generates the missing ones (paid agent work: ask first); a draft is published with taiga_publish_product_document.`;
|
|
28995
|
-
if (state.openChecks && state.openChecks.length > 0) return `The documents are published; the checks are stamped in the web, then ${finish}`;
|
|
28996
29814
|
return `Every required document is published: ${finish}`;
|
|
28997
29815
|
}
|
|
28998
29816
|
//#endregion
|
|
@@ -30076,7 +30894,8 @@ const READY_BLOCKER_SAID = {
|
|
|
30076
30894
|
bar_lowering: "a fix lowered a check instead of fixing the code",
|
|
30077
30895
|
bar_lowering_unchecked: "Taiga could not check whether a fix lowered a check",
|
|
30078
30896
|
criticals_unresolved: "Taiga's own review left a critical issue unresolved",
|
|
30079
|
-
clean_checkout_failed: "the branch does not install or pass its tests from a clean checkout"
|
|
30897
|
+
clean_checkout_failed: "the branch does not install or pass its tests from a clean checkout",
|
|
30898
|
+
license_not_approved: "a package it adds has a licence that is not on your approved list"
|
|
30080
30899
|
};
|
|
30081
30900
|
/** The first sentence of a run's error, short enough for a row. */
|
|
30082
30901
|
function errorSaid(error) {
|
|
@@ -30100,10 +30919,11 @@ function runOutcome(run) {
|
|
|
30100
30919
|
const why = errorSaid(run["error"]) ?? (typeof run["failedReason"] === "string" ? run["failedReason"] : null);
|
|
30101
30920
|
const handback = handbackSaid(run["reviewEscalation"]);
|
|
30102
30921
|
const draft = run["prDraft"] === true ? `, still a draft` : "";
|
|
30922
|
+
const blocked = reasonOnOneLine(run["factoryBlockedBecause"]);
|
|
30103
30923
|
return {
|
|
30104
30924
|
disposition,
|
|
30105
30925
|
outcome: {
|
|
30106
|
-
in_flight: status === "pending" ? "Queued: waiting to start." : status === "replanning" ? steerPaused(run["failedReason"], progress) : `Running: ${RUNNING[status] ?? status}${progress}.`,
|
|
30926
|
+
in_flight: blocked ? `Blocked: the factory has stopped working on this run${progress}. ${blocked}` : status === "pending" ? "Queued: waiting to start." : status === "replanning" ? steerPaused(run["failedReason"], progress) : `Running: ${RUNNING[status] ?? status}${progress}.`,
|
|
30107
30927
|
gated: handback ? `Handed back to a person: ${handback}. ${pr}${draft ? `${draft},` : ""} waits for them to ${handbackNextStep(run["reviewEscalation"], run["provider"])}.` : `Waiting for a person to review and merge ${pr}${draft}.`,
|
|
30108
30928
|
succeeded: `Merged: ${pr} is in ${typeof run["baseBranch"] === "string" ? run["baseBranch"] : "the base branch"}.`,
|
|
30109
30929
|
unmerged: typeof run["prNumber"] === "number" || run["prUrl"] ? `Settled without merging: ${pr} was left unmerged.` : "Settled without a merged pull request.",
|
|
@@ -30127,12 +30947,12 @@ function steerPaused(reason, progress) {
|
|
|
30127
30947
|
*/
|
|
30128
30948
|
function replanHandedBack(reason, error, progress) {
|
|
30129
30949
|
if (reason !== "replan_refused" && reason !== "replan_failed") return null;
|
|
30130
|
-
const why =
|
|
30950
|
+
const why = reasonOnOneLine(error);
|
|
30131
30951
|
if (reason === "replan_refused") return `Stopped${progress}, handed back: the planner would not plan the request for changes${why ? `: ${why}` : "."} Resume continues the original plan.`;
|
|
30132
30952
|
return `Stopped${progress}, handed back: ${why ?? "the plan could not be revised."} Resume continues the original plan.`;
|
|
30133
30953
|
}
|
|
30134
30954
|
/** A hand-back's reason on one line, ending as a sentence, capped for a row. */
|
|
30135
|
-
function
|
|
30955
|
+
function reasonOnOneLine(error) {
|
|
30136
30956
|
if (typeof error !== "string") return null;
|
|
30137
30957
|
const text = error.replace(/\s+/g, " ").trim();
|
|
30138
30958
|
if (!text) return null;
|
|
@@ -30218,7 +31038,7 @@ const FAILED_REASON_MEANS = {
|
|
|
30218
31038
|
* text out: its first sentence is in the outcome, and taiga_get_run has it all.
|
|
30219
31039
|
*/
|
|
30220
31040
|
function readableRun(run, opts = {}) {
|
|
30221
|
-
const { prDraft, error, reviewEscalation, runtime: _runtime, origin, ...rest } = run;
|
|
31041
|
+
const { prDraft, error, reviewEscalation, runtime: _runtime, origin, factoryBlockedBecause: _inTheOutcome, ...rest } = run;
|
|
30222
31042
|
const said = runOutcome(run);
|
|
30223
31043
|
const means = FAILED_REASON_MEANS[String(run["failedReason"])];
|
|
30224
31044
|
const hardening = run["hardeningResult"];
|
|
@@ -30583,6 +31403,7 @@ function registerBuildTools(server, client) {
|
|
|
30583
31403
|
}
|
|
30584
31404
|
/** What happened to a request that steers a run (PRO-3161), in one sentence. */
|
|
30585
31405
|
function steerSituation(steer) {
|
|
31406
|
+
if (steer === "sent") return "The run takes it as an instruction while it keeps working.";
|
|
30586
31407
|
if (steer === "replanning") return "The run has paused and its plan is being revised for the request.";
|
|
30587
31408
|
if (steer === "after_step") return "The run stops when the step it is building ends, then its plan is revised.";
|
|
30588
31409
|
if (steer === "after_round") return "The run is answering review comments or fixing CI; the request is planned when that round ends, and no further automatic round starts while it is open.";
|
|
@@ -30834,6 +31655,10 @@ function standing(item) {
|
|
|
30834
31655
|
const life = item["lifecycle"];
|
|
30835
31656
|
const run = life?.latestBuild;
|
|
30836
31657
|
const situation = situationOf(item);
|
|
31658
|
+
const open = (life?.openPullRequests ?? []).flatMap((pull) => typeof pull.repositoryFullName === "string" && typeof pull.prNumber === "number" ? [{
|
|
31659
|
+
repository: pull.repositoryFullName,
|
|
31660
|
+
prNumber: pull.prNumber
|
|
31661
|
+
}] : []);
|
|
30837
31662
|
return {
|
|
30838
31663
|
column: typeof item["state"] === "string" ? item["state"] : null,
|
|
30839
31664
|
turn: typeof life?.turn === "string" ? life.turn : null,
|
|
@@ -30841,7 +31666,8 @@ function standing(item) {
|
|
|
30841
31666
|
latestRun: run && typeof run.id === "string" ? {
|
|
30842
31667
|
id: run.id,
|
|
30843
31668
|
...typeof run.prNumber === "number" ? { prNumber: run.prNumber } : {}
|
|
30844
|
-
} : null
|
|
31669
|
+
} : null,
|
|
31670
|
+
...open.length > 1 ? { pullRequests: open } : {}
|
|
30845
31671
|
};
|
|
30846
31672
|
}
|
|
30847
31673
|
/** The move each call to action is, in the tools' words. */
|
|
@@ -31024,7 +31850,8 @@ const PLAN_SUMMARY_FIELDS = [
|
|
|
31024
31850
|
"backlogItemId",
|
|
31025
31851
|
"version",
|
|
31026
31852
|
"status",
|
|
31027
|
-
"createdAt"
|
|
31853
|
+
"createdAt",
|
|
31854
|
+
"controls"
|
|
31028
31855
|
];
|
|
31029
31856
|
const PLAN_STEP_SUMMARY_FIELDS = [
|
|
31030
31857
|
"id",
|
|
@@ -31171,10 +31998,12 @@ function registerBacklogReads(server, client) {
|
|
|
31171
31998
|
...revised
|
|
31172
31999
|
});
|
|
31173
32000
|
const steps = Array.isArray(plan.steps) ? plan.steps : [];
|
|
32001
|
+
const several = (plan.repositories?.length ?? 0) > 1;
|
|
31174
32002
|
return toolResult({
|
|
31175
32003
|
...pickFields([plan], PLAN_SUMMARY_FIELDS)[0],
|
|
32004
|
+
...several ? { repositories: plan.repositories } : {},
|
|
31176
32005
|
...revised,
|
|
31177
|
-
steps: pickFields(steps, PLAN_STEP_SUMMARY_FIELDS)
|
|
32006
|
+
steps: several ? pickFields(steps, [...PLAN_STEP_SUMMARY_FIELDS, "repositoryName"]) : pickFields(steps, PLAN_STEP_SUMMARY_FIELDS)
|
|
31178
32007
|
});
|
|
31179
32008
|
}));
|
|
31180
32009
|
}
|
|
@@ -32130,7 +32959,7 @@ async function licenseReviews(client, productId, now) {
|
|
|
32130
32959
|
packages: data["packages"],
|
|
32131
32960
|
...Number.isNaN(created) ? {} : { waitingDays: Math.floor((now - created) / DAY_MS) },
|
|
32132
32961
|
readWith: "taiga_list_licenses (license)",
|
|
32133
|
-
decideWith: "taiga_change_license (license, action: 'approve'); dismissing it is only in the web"
|
|
32962
|
+
decideWith: "taiga_change_license (license, action: 'approve') for every product" + (client.hasCapability("products:write") ? ", or taiga_allow_license (productId, license) for this one" : "") + "; dismissing it is only in the web"
|
|
32134
32963
|
};
|
|
32135
32964
|
}).filter((r) => productId === void 0 || r.productId === productId),
|
|
32136
32965
|
truncated: result.truncated === true
|
|
@@ -48897,7 +49726,7 @@ function registerSetupTools(server, client) {
|
|
|
48897
49726
|
}, (params) => runTool(async () => toolResult((await client.refreshRealityDocuments(params.productId, params.docTypes)).data)));
|
|
48898
49727
|
server.registerTool("taiga_finish_discovery", {
|
|
48899
49728
|
title: "Finish Discovery",
|
|
48900
|
-
description: "Finish a product's setup: lock its documents and start planning its first initiatives. Refused while documents are missing (the refusal lists them)
|
|
49729
|
+
description: "Finish a product's setup: lock its documents and start planning its first initiatives. Refused while documents are missing (the refusal lists them).",
|
|
48901
49730
|
inputSchema: object({ productId }),
|
|
48902
49731
|
annotations: OPERATE
|
|
48903
49732
|
}, (params) => runTool(async () => {
|
|
@@ -49162,6 +49991,7 @@ const PAID_WORK = /* @__PURE__ */ new Set([
|
|
|
49162
49991
|
"taiga_request_changes",
|
|
49163
49992
|
"taiga_request_initiatives",
|
|
49164
49993
|
"taiga_resume_initiative",
|
|
49994
|
+
"taiga_ship_notices",
|
|
49165
49995
|
"taiga_retry_knowledge",
|
|
49166
49996
|
"taiga_review_plan",
|
|
49167
49997
|
"taiga_set_auto_build",
|