@taiga-ai/mcp-server 0.36.0-rc.800 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +670 -42
- 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,40 @@ 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
|
|
26010
26121
|
//#region ../../packages/types/dist/environments.js
|
|
26011
26122
|
const ENVIRONMENT_TYPES = [
|
|
26012
26123
|
"development",
|
|
@@ -26031,6 +26142,11 @@ const MCP_TOOLSETS = [
|
|
|
26031
26142
|
"observe",
|
|
26032
26143
|
"documents"
|
|
26033
26144
|
];
|
|
26145
|
+
Object.freeze({
|
|
26146
|
+
required: false,
|
|
26147
|
+
gracePeriodDays: 0,
|
|
26148
|
+
requiredSince: null
|
|
26149
|
+
});
|
|
26034
26150
|
[...TURN_EVENT_TRIGGERS];
|
|
26035
26151
|
/** Longest request for changes from Taiga (PRO-3035): about four pages of prose. */
|
|
26036
26152
|
const CHANGE_REQUEST_MAX_CHARS = 1e4;
|
|
@@ -26051,6 +26167,8 @@ const REQUEST_EXITS = [
|
|
|
26051
26167
|
"dismiss",
|
|
26052
26168
|
"reviewed"
|
|
26053
26169
|
];
|
|
26170
|
+
/** The most ids an answer's `seen` carries; a longer list is not sent, and the review is not checked. */
|
|
26171
|
+
const REVIEW_SEEN_MAX = 1e4;
|
|
26054
26172
|
//#endregion
|
|
26055
26173
|
//#region ../../packages/types/dist/maintenance.js
|
|
26056
26174
|
/**
|
|
@@ -26146,9 +26264,15 @@ function isFixableFinding(finding) {
|
|
|
26146
26264
|
if (finding.status !== "open") return false;
|
|
26147
26265
|
if (finding.location?.historyOnly) return false;
|
|
26148
26266
|
if (!isAgentRemediable(finding)) return false;
|
|
26267
|
+
if (finding.fingerprint?.startsWith("base-image:")) return typeof finding.baseMove === "boolean" ? finding.baseMove : baseImageMoveRemoves(finding.data);
|
|
26149
26268
|
if (finding.kind === "vulnerable_dependency" && isMaliciousPackageFinding(finding)) return true;
|
|
26150
26269
|
return isCodeRemediationKind(finding.kind) || !!finding.recommendedVersion;
|
|
26151
26270
|
}
|
|
26271
|
+
function baseImageMoveRemoves(data) {
|
|
26272
|
+
const baseImage = typeof data === "object" && data !== null ? data.baseImage : null;
|
|
26273
|
+
const bases = typeof baseImage === "object" && baseImage !== null ? baseImage.bases : null;
|
|
26274
|
+
return Array.isArray(bases) && bases.length > 0 && bases.every((b) => typeof b === "object" && b !== null && typeof b.movesTo === "string" && b.removed === true);
|
|
26275
|
+
}
|
|
26152
26276
|
/**
|
|
26153
26277
|
* A malicious package (OSV `MAL-*`): its advisory id or one of its aliases
|
|
26154
26278
|
* says so, or its tier is Act now with no KEV listing, which only a malware
|
|
@@ -26165,6 +26289,17 @@ function isMaliciousPackageFinding(finding) {
|
|
|
26165
26289
|
if (Array.isArray(aliases) && aliases.some(mal)) return true;
|
|
26166
26290
|
return finding.tier === "act_now" && finding.kevListedAt == null;
|
|
26167
26291
|
}
|
|
26292
|
+
/**
|
|
26293
|
+
* The scanner CATEGORIES a sweep reports, in the order the agent reports them
|
|
26294
|
+
* (PRO-1725). Outcomes are keyed on the category, not the tool, so replacing a
|
|
26295
|
+
* tool in the agents' scanner manifest changes nothing here.
|
|
26296
|
+
*/
|
|
26297
|
+
const MAINTENANCE_SCANNERS = [
|
|
26298
|
+
"deps",
|
|
26299
|
+
"secrets",
|
|
26300
|
+
"sast",
|
|
26301
|
+
"iac"
|
|
26302
|
+
];
|
|
26168
26303
|
new Map(Object.entries({
|
|
26169
26304
|
deps: [
|
|
26170
26305
|
"vulnerable_dependency",
|
|
@@ -26370,6 +26505,19 @@ object({
|
|
|
26370
26505
|
commit: d.commit
|
|
26371
26506
|
}));
|
|
26372
26507
|
/**
|
|
26508
|
+
* The sources whose findings no agent scanner's outcome decides (PRO-4070):
|
|
26509
|
+
* the API's runtime judge (PRO-3659) and base OS judge (PRO-4074), and the
|
|
26510
|
+
* base image reading (PRO-1728), whose findings the agent drafts but only a
|
|
26511
|
+
* sweep that read the images may settle.
|
|
26512
|
+
*/
|
|
26513
|
+
const MAINTENANCE_JUDGED_SOURCES = [
|
|
26514
|
+
"runtime",
|
|
26515
|
+
"base_os",
|
|
26516
|
+
"base_image"
|
|
26517
|
+
];
|
|
26518
|
+
[...MAINTENANCE_SCANNERS, ...MAINTENANCE_JUDGED_SOURCES];
|
|
26519
|
+
new Set(MAINTENANCE_SCANNERS);
|
|
26520
|
+
/**
|
|
26373
26521
|
* The longest licence string the package list filters by (PRO-2920). The
|
|
26374
26522
|
* inventory keeps every expression whole whatever its length; this bounds only
|
|
26375
26523
|
* the query string, far above any expression seen. A longer one is listed in
|
|
@@ -26377,6 +26525,200 @@ object({
|
|
|
26377
26525
|
*/
|
|
26378
26526
|
const MAINTENANCE_LICENSE_FILTER_MAX = 4096;
|
|
26379
26527
|
//#endregion
|
|
26528
|
+
//#region ../../packages/types/dist/maintenance-advisory.js
|
|
26529
|
+
/**
|
|
26530
|
+
* Pull the renderable advisory fields out of a finding's raw OSV `data` object.
|
|
26531
|
+
*
|
|
26532
|
+
* `data` is the scanner's raw OSV vulnerability payload (MaintenanceFindingData,
|
|
26533
|
+
* shape varies), so every field is read leniently: a missing or malformed key
|
|
26534
|
+
* yields an empty value rather than throwing. Shared by the web app's finding
|
|
26535
|
+
* detail and the Taiga Audit preview and report (PRO-3791), so both show the
|
|
26536
|
+
* same identifiers for the same finding.
|
|
26537
|
+
*/
|
|
26538
|
+
/** A non-empty string, or null: anything else the scanner wrote reads as absent. */
|
|
26539
|
+
const text$3 = string$2().min(1).nullable().catch(null);
|
|
26540
|
+
/** The non-empty strings of a list; a value that is not a list reads as none. */
|
|
26541
|
+
const texts = array(unknown$1()).catch([]).transform((list) => list.filter((v) => typeof v === "string" && v.length > 0));
|
|
26542
|
+
object({
|
|
26543
|
+
type: text$3,
|
|
26544
|
+
score: text$3
|
|
26545
|
+
});
|
|
26546
|
+
object({
|
|
26547
|
+
type: text$3,
|
|
26548
|
+
url: text$3
|
|
26549
|
+
});
|
|
26550
|
+
object({
|
|
26551
|
+
aliases: texts,
|
|
26552
|
+
database_specific: object({
|
|
26553
|
+
cwe_ids: texts,
|
|
26554
|
+
severity: text$3
|
|
26555
|
+
}).nullable().catch(null),
|
|
26556
|
+
severity: array(unknown$1()).catch([]),
|
|
26557
|
+
references: array(unknown$1()).catch([]),
|
|
26558
|
+
published: text$3,
|
|
26559
|
+
modified: text$3
|
|
26560
|
+
});
|
|
26561
|
+
//#endregion
|
|
26562
|
+
//#region ../../packages/types/dist/finding-controls.js
|
|
26563
|
+
/**
|
|
26564
|
+
* A committed credential.
|
|
26565
|
+
*
|
|
26566
|
+
* SEC-3.4 "Secrets must never be stored in source code or logs"
|
|
26567
|
+
* SEC-5.1 "Secrets must be stored in an approved secrets management system"
|
|
26568
|
+
* SEC-5.2 "Secrets must be rotated at least annually" -- the half Taiga cannot
|
|
26569
|
+
* do, and the reason the remediation initiative insists the PR say so.
|
|
26570
|
+
* SDLC-3.5 "Secret detection must run pre-commit and in CI pipeline"
|
|
26571
|
+
*/
|
|
26572
|
+
const SECRET_CONTROLS = [
|
|
26573
|
+
"SEC-3.4",
|
|
26574
|
+
"SEC-5.1",
|
|
26575
|
+
"SEC-5.2",
|
|
26576
|
+
"SDLC-3.5"
|
|
26577
|
+
];
|
|
26578
|
+
/**
|
|
26579
|
+
* A vulnerable dependency.
|
|
26580
|
+
*
|
|
26581
|
+
* SCS-2.6 "critical security patches within 7 days, others within 30 days"
|
|
26582
|
+
* -- breached by a vulnerability whose fix has been out past its
|
|
26583
|
+
* window, and by no other (PRO-3420). One found this morning has missed
|
|
26584
|
+
* nothing; neither has one with no fixed release to move to.
|
|
26585
|
+
* SCS-5.2 "Critical vulnerabilities must block deployment"
|
|
26586
|
+
*
|
|
26587
|
+
* Two controls this map deliberately does NOT claim:
|
|
26588
|
+
* - SCS-2.5 "Transitive dependency vulnerabilities must be monitored
|
|
26589
|
+
* continuously". A finding is that monitoring working, not evidence it is
|
|
26590
|
+
* missing; every vulnerable dependency read as a breach of it. The sweep's
|
|
26591
|
+
* own record shows it met instead (sweep-evidence.ts).
|
|
26592
|
+
* - Anything for an outdated dependency that is no vulnerability: SCS-2.6's
|
|
26593
|
+
* rationale is "timely vulnerability remediation", and an update that fixes
|
|
26594
|
+
* nothing is not a security patch.
|
|
26595
|
+
*/
|
|
26596
|
+
const DEPENDENCY_CONTROLS = ["SCS-2.6", "SCS-5.2"];
|
|
26597
|
+
/**
|
|
26598
|
+
* A package whose licence the allowlist does not satisfy (PRO-2862).
|
|
26599
|
+
*
|
|
26600
|
+
* TIC-6.2 "Open source licenses must be reviewed and approved before adoption"
|
|
26601
|
+
* -- the same policy section lists the approved licences and the ones
|
|
26602
|
+
* requiring review, which is where the default allowlist comes from.
|
|
26603
|
+
*/
|
|
26604
|
+
const LICENSE_CONTROLS = ["TIC-6.2"];
|
|
26605
|
+
/**
|
|
26606
|
+
* Injection and unsanitized input, whatever the sink.
|
|
26607
|
+
*
|
|
26608
|
+
* SDLC-5.1 "All user input must be validated and sanitized"
|
|
26609
|
+
* SEC-6.1 "All API endpoints must validate and sanitize input"
|
|
26610
|
+
*/
|
|
26611
|
+
const INJECTION_CONTROLS = ["SDLC-5.1", "SEC-6.1"];
|
|
26612
|
+
/**
|
|
26613
|
+
* Every opengrep rule Taiga ships, mapped explicitly.
|
|
26614
|
+
*
|
|
26615
|
+
* A closed set, unlike checkov's -- these rules are ours, so an exhaustive map
|
|
26616
|
+
* is possible and a keyword heuristic would be a worse answer to an easier
|
|
26617
|
+
* question. `policy-map.test.ts` reads the rule ids straight out of the YAML
|
|
26618
|
+
* so a new rule that lands unmapped fails the build instead of silently
|
|
26619
|
+
* emitting no control.
|
|
26620
|
+
*/
|
|
26621
|
+
const SAST_CONTROLS = {
|
|
26622
|
+
"taiga.csharp.sql-string-concat": INJECTION_CONTROLS,
|
|
26623
|
+
"taiga.go.sql-string-concat": INJECTION_CONTROLS,
|
|
26624
|
+
"taiga.javascript.sql-template-concat": INJECTION_CONTROLS,
|
|
26625
|
+
"taiga.python.sqli-fstring": INJECTION_CONTROLS,
|
|
26626
|
+
"taiga.rust.sql-format-concat": INJECTION_CONTROLS,
|
|
26627
|
+
"taiga.csharp.process-start-shell-dynamic": INJECTION_CONTROLS,
|
|
26628
|
+
"taiga.go.exec-command-dynamic": INJECTION_CONTROLS,
|
|
26629
|
+
"taiga.javascript.child-process-exec-dynamic": INJECTION_CONTROLS,
|
|
26630
|
+
"taiga.python.os-system-dynamic": INJECTION_CONTROLS,
|
|
26631
|
+
"taiga.python.subprocess-shell-true": INJECTION_CONTROLS,
|
|
26632
|
+
"taiga.rust.command-shell-dynamic": INJECTION_CONTROLS,
|
|
26633
|
+
"taiga.javascript.eval-dynamic": INJECTION_CONTROLS,
|
|
26634
|
+
"taiga.python.eval-exec-dynamic": INJECTION_CONTROLS,
|
|
26635
|
+
"taiga.javascript.dangerously-set-inner-html": INJECTION_CONTROLS,
|
|
26636
|
+
"taiga.javascript.innerhtml-dynamic": INJECTION_CONTROLS,
|
|
26637
|
+
"taiga.csharp.binaryformatter-deserialize": ["SDLC-5.7"],
|
|
26638
|
+
"taiga.python.pickle-load-untrusted": ["SDLC-5.7"],
|
|
26639
|
+
"taiga.python.yaml-load-unsafe": ["SDLC-5.7"],
|
|
26640
|
+
"taiga.csharp.cert-validation-disabled": ["SEC-3.2"],
|
|
26641
|
+
"taiga.go.tls-insecure-skip-verify": ["SEC-3.2"],
|
|
26642
|
+
"taiga.javascript.tls-reject-unauthorized-false": ["SEC-3.2"],
|
|
26643
|
+
"taiga.python.requests-verify-false": ["SEC-3.2"],
|
|
26644
|
+
"taiga.rust.tls-verification-disabled": ["SEC-3.2"],
|
|
26645
|
+
"taiga.javascript.jwt-verify-none": ["SDLC-5.4", "SEC-9.1"],
|
|
26646
|
+
"taiga.python.flask-debug-true": ["SDLC-5.5"],
|
|
26647
|
+
"taiga.go.pprof-import": ["SDLC-5.5"],
|
|
26648
|
+
"taiga.csharp.weak-hash": [],
|
|
26649
|
+
"taiga.go.weak-hash": [],
|
|
26650
|
+
"taiga.javascript.weak-hash": [],
|
|
26651
|
+
"taiga.python.weak-hash": [],
|
|
26652
|
+
"taiga.rust.weak-hash": [],
|
|
26653
|
+
"taiga.go.math-rand-for-secrets": [],
|
|
26654
|
+
"taiga.rust.non-crypto-rng-for-secrets": []
|
|
26655
|
+
};
|
|
26656
|
+
/**
|
|
26657
|
+
* Checkov, matched on the check's own description.
|
|
26658
|
+
*
|
|
26659
|
+
* Checkov's rule set is open and numbers in the thousands, so an exhaustive
|
|
26660
|
+
* id map is not available the way it is for our own rules. These phrases are
|
|
26661
|
+
* matched against the check title checkov itself writes ("Ensure all data
|
|
26662
|
+
* stored in the RDS is securely encrypted at rest"), and they are deliberately
|
|
26663
|
+
* narrow: a title matching nothing yields no control, which is the safe
|
|
26664
|
+
* direction to be wrong in.
|
|
26665
|
+
*
|
|
26666
|
+
* Exported so `policy-map.test.ts` validates THESE ids against the registry
|
|
26667
|
+
* too. They are the half most likely to drift, and an id-map that only
|
|
26668
|
+
* validates the closed half is a claim the tests do not support.
|
|
26669
|
+
*/
|
|
26670
|
+
const IAC_PHRASES = [
|
|
26671
|
+
{
|
|
26672
|
+
pattern: /encrypt\w*\s+at\s+rest|at\s+rest.*encrypt|securely encrypted/i,
|
|
26673
|
+
controls: ["SEC-3.1"],
|
|
26674
|
+
why: "at-rest encryption"
|
|
26675
|
+
},
|
|
26676
|
+
{
|
|
26677
|
+
pattern: /\bKMS\b|customer[- ]managed key|\bCMK\b/i,
|
|
26678
|
+
controls: ["SEC-3.3"],
|
|
26679
|
+
why: "key management"
|
|
26680
|
+
},
|
|
26681
|
+
{
|
|
26682
|
+
pattern: /in\s+transit|\bTLS\b|\bSSL\b|\bHTTPS\b/i,
|
|
26683
|
+
controls: ["SEC-3.2"],
|
|
26684
|
+
why: "transport encryption"
|
|
26685
|
+
},
|
|
26686
|
+
{
|
|
26687
|
+
pattern: /0\.0\.0\.0\/0|ingress from|security group/i,
|
|
26688
|
+
controls: ["CLD-7.2"],
|
|
26689
|
+
why: "network default-deny"
|
|
26690
|
+
},
|
|
26691
|
+
{
|
|
26692
|
+
pattern: /public(ly)?\s+(access|read|write|expos)|public access/i,
|
|
26693
|
+
controls: ["CLD-1.3"],
|
|
26694
|
+
why: "public exposure"
|
|
26695
|
+
},
|
|
26696
|
+
{
|
|
26697
|
+
pattern: /least privilege|wildcard|admin(istrator)? (policy|access)/i,
|
|
26698
|
+
controls: ["SEC-2.1"],
|
|
26699
|
+
why: "least privilege"
|
|
26700
|
+
},
|
|
26701
|
+
{
|
|
26702
|
+
pattern: /backup|point[- ]in[- ]time recovery/i,
|
|
26703
|
+
controls: ["OPS-4.1"],
|
|
26704
|
+
why: "backup"
|
|
26705
|
+
},
|
|
26706
|
+
{
|
|
26707
|
+
pattern: /audit log|cloudtrail|access logging|logging (is )?enabled/i,
|
|
26708
|
+
controls: ["SDLC-5.8"],
|
|
26709
|
+
why: "security logging"
|
|
26710
|
+
}
|
|
26711
|
+
];
|
|
26712
|
+
[.../* @__PURE__ */ new Set([
|
|
26713
|
+
...SECRET_CONTROLS,
|
|
26714
|
+
...DEPENDENCY_CONTROLS,
|
|
26715
|
+
...LICENSE_CONTROLS,
|
|
26716
|
+
...Object.values(SAST_CONTROLS).flat(),
|
|
26717
|
+
...IAC_PHRASES.flatMap((p) => p.controls)
|
|
26718
|
+
])];
|
|
26719
|
+
new RegExp(`^\\|\\s*(${CONTROL_ID_SOURCE})\\s*\\|\\s*(.*?)\\s*(?:\\||$)`);
|
|
26720
|
+
REALITY_DERIVED_DOCUMENT_TYPES.filter((t) => t !== "codebase_snapshot");
|
|
26721
|
+
//#endregion
|
|
26380
26722
|
//#region ../../packages/types/dist/custom-documents.js
|
|
26381
26723
|
/**
|
|
26382
26724
|
* Shared types for the custom-documents feature: customer-uploaded reference
|
|
@@ -26685,6 +27027,7 @@ const uploadGrant = object({
|
|
|
26685
27027
|
expiresAt: string$2()
|
|
26686
27028
|
});
|
|
26687
27029
|
object({ version: number$1().int().min(1) });
|
|
27030
|
+
object({ documentId: string$2().uuid() });
|
|
26688
27031
|
/** The uploads a product's move could not re-file, named while they fit the answer (PRO-3497). */
|
|
26689
27032
|
const failedDocuments = array(object({
|
|
26690
27033
|
id: string$2(),
|
|
@@ -26811,18 +27154,30 @@ strictObject({
|
|
|
26811
27154
|
"replanning",
|
|
26812
27155
|
"after_step",
|
|
26813
27156
|
"after_round",
|
|
26814
|
-
"held"
|
|
26815
|
-
|
|
27157
|
+
"held",
|
|
27158
|
+
"sent"
|
|
27159
|
+
]).optional() }), object({ files: array(object({
|
|
27160
|
+
id: string$2(),
|
|
27161
|
+
url: string$2().url()
|
|
27162
|
+
})) });
|
|
27163
|
+
object({ images: array(object({
|
|
27164
|
+
name: string$2(),
|
|
27165
|
+
url: string$2().url().nullable()
|
|
27166
|
+
})) });
|
|
26816
27167
|
strictObject({
|
|
26817
27168
|
exit: _enum(REQUEST_EXITS),
|
|
26818
27169
|
comment: string$2().trim().min(1).max(PLAN_REJECTION_COMMENT_MAX_CHARS).optional(),
|
|
26819
|
-
force: boolean().optional()
|
|
27170
|
+
force: boolean().optional(),
|
|
27171
|
+
seen: array(string$2().uuid()).max(REVIEW_SEEN_MAX).optional()
|
|
26820
27172
|
}).refine((answer) => answer.exit === "reject" === (answer.comment !== void 0), {
|
|
26821
27173
|
message: "A rejection takes a comment for the planner, and no other answer takes one.",
|
|
26822
27174
|
path: ["comment"]
|
|
26823
27175
|
}).refine((answer) => answer.exit === "replan" || answer.force === void 0, {
|
|
26824
27176
|
message: "Only Plan again can plan anyway.",
|
|
26825
27177
|
path: ["force"]
|
|
27178
|
+
}).refine((answer) => answer.exit === "reviewed" || answer.seen === void 0, {
|
|
27179
|
+
message: "Only Mark reviewed says what was shown.",
|
|
27180
|
+
path: ["seen"]
|
|
26826
27181
|
});
|
|
26827
27182
|
strictObject({ type: productDocumentType }), object({
|
|
26828
27183
|
version: number$1().int().min(1),
|
|
@@ -26830,7 +27185,7 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26830
27185
|
shown: array(string$2()),
|
|
26831
27186
|
count: number$1().int().min(0)
|
|
26832
27187
|
})
|
|
26833
|
-
}), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }), object({
|
|
27188
|
+
}), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }), strictObject({}), object({ started: number$1().int().min(0) }), strictObject({
|
|
26834
27189
|
githubRepositoryId: uuid.optional(),
|
|
26835
27190
|
/** A GitLab project, by the connection whose bot reaches it and GitLab's numeric id (P-PRO-150). */
|
|
26836
27191
|
gitlabConnectionId: uuid.optional(),
|
|
@@ -26852,13 +27207,19 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26852
27207
|
enforcementLevel: string$2().nullable()
|
|
26853
27208
|
}).nullable()
|
|
26854
27209
|
}), strictObject({
|
|
26855
|
-
license: string$2().trim().min(1).max(
|
|
27210
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX),
|
|
26856
27211
|
ignoreReason: _enum(MAINTENANCE_IGNORE_REASONS),
|
|
26857
27212
|
ignoreNote,
|
|
26858
27213
|
ignoreExpiresAt
|
|
26859
27214
|
}).refine((args) => expiresInFuture(args.ignoreExpiresAt), EXPIRY_IN_FUTURE), strictObject({
|
|
26860
|
-
license: string$2().trim().min(1).max(
|
|
27215
|
+
license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX),
|
|
26861
27216
|
findingIds: array(uuid).min(1).optional()
|
|
27217
|
+
}), strictObject({ license: string$2().trim().min(1).max(MAINTENANCE_LICENSE_FILTER_MAX) }), object({
|
|
27218
|
+
backlogItemIds: array(string$2()),
|
|
27219
|
+
findingCount: number$1()
|
|
27220
|
+
}), strictObject({}), object({
|
|
27221
|
+
backlogItemId: string$2(),
|
|
27222
|
+
created: boolean()
|
|
26862
27223
|
}), 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
27224
|
type: _enum(AGENT_JOB_TYPES),
|
|
26864
27225
|
documentType: string$2().max(30).optional(),
|
|
@@ -26902,6 +27263,14 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26902
27263
|
}), strictObject({
|
|
26903
27264
|
...policyFixOptions,
|
|
26904
27265
|
controlId: string$2().min(1).max(40)
|
|
27266
|
+
}), strictObject({
|
|
27267
|
+
controlId: string$2().min(1).max(40),
|
|
27268
|
+
validMonths: union([
|
|
27269
|
+
literal(3),
|
|
27270
|
+
literal(6),
|
|
27271
|
+
literal(12)
|
|
27272
|
+
]),
|
|
27273
|
+
note: string$2().trim().max(1e3).optional()
|
|
26905
27274
|
}), strictObject({ environments: array(string$2()).default(["development"]) }), strictObject({
|
|
26906
27275
|
connection: object({
|
|
26907
27276
|
name: string$2().min(1).max(150),
|
|
@@ -26919,13 +27288,97 @@ strictObject({ type: productDocumentType }), object({
|
|
|
26919
27288
|
isProtected: boolean().default(false)
|
|
26920
27289
|
}).optional()
|
|
26921
27290
|
});
|
|
27291
|
+
object({
|
|
27292
|
+
url: string$2().url(),
|
|
27293
|
+
expiresAt: string$2()
|
|
27294
|
+
});
|
|
27295
|
+
object({
|
|
27296
|
+
opens: number$1().int().nonnegative(),
|
|
27297
|
+
closes: number$1().int().nonnegative(),
|
|
27298
|
+
products: array(object({
|
|
27299
|
+
productId: string$2().uuid(),
|
|
27300
|
+
opens: number$1().int().nonnegative(),
|
|
27301
|
+
closes: number$1().int().nonnegative()
|
|
27302
|
+
}))
|
|
27303
|
+
});
|
|
26922
27304
|
object({
|
|
26923
27305
|
status: customDocumentStatus.nullable(),
|
|
26924
27306
|
/** The retry itself threw; absent when it went through. */
|
|
26925
27307
|
error: string$2().optional()
|
|
26926
27308
|
});
|
|
26927
|
-
|
|
26928
|
-
|
|
27309
|
+
/** A factory document type, its legacy name read as the current one. */
|
|
27310
|
+
const factoryDocumentType = _enum([...FACTORY_DOCUMENT_TYPES, LEGACY_FACTORY_INSTRUCTIONS_TYPE]).transform((type) => type === "team_instructions" ? FACTORY_INSTRUCTIONS_TYPE : type);
|
|
27311
|
+
strictObject({ type: factoryDocumentType }), strictObject({ type: factoryDocumentType });
|
|
27312
|
+
/**
|
|
27313
|
+
* A registry scope: an npm scope ("@acme") or a Go module path prefix
|
|
27314
|
+
* ("github.com/acme"), as `POST /registries` takes one (PRO-3670). Which
|
|
27315
|
+
* ecosystem takes which is the service's rule.
|
|
27316
|
+
*/
|
|
27317
|
+
const namedSegments = (value) => value.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
|
|
27318
|
+
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");
|
|
27319
|
+
/** The package a registry's check fetches, in the union of the ecosystems' grammars. */
|
|
27320
|
+
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");
|
|
27321
|
+
/** KMS direct-encrypt takes 4096 bytes; the raw secret stays well under. */
|
|
27322
|
+
const REGISTRY_CREDENTIAL_MAX_BYTES = 3500;
|
|
27323
|
+
/** A registry's write-only credential: exactly one of a token, a GCP key or an AWS role, as the route takes it. */
|
|
27324
|
+
const registryCredential = strictObject({
|
|
27325
|
+
token: string$2().min(1).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
|
|
27326
|
+
username: string$2().min(1).max(256).regex(/^[^:\s]+$/, "Username must not contain a colon or whitespace").optional(),
|
|
27327
|
+
credentialsJson: string$2().min(2).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
|
|
27328
|
+
awsRoleArn: string$2().max(600).regex(AWS_ROLE_ARN_PATTERN, "Not an IAM role ARN").optional()
|
|
27329
|
+
}).refine((c) => [
|
|
27330
|
+
c.token,
|
|
27331
|
+
c.credentialsJson,
|
|
27332
|
+
c.awsRoleArn
|
|
27333
|
+
].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)" });
|
|
27334
|
+
/** An instant as an ISO string, which the service reads as a date. */
|
|
27335
|
+
const isoInstant = string$2().datetime({ offset: true });
|
|
27336
|
+
object({
|
|
27337
|
+
status: _enum([
|
|
27338
|
+
"unvalidated",
|
|
27339
|
+
"valid",
|
|
27340
|
+
"auth_failed",
|
|
27341
|
+
"unreachable"
|
|
27342
|
+
]),
|
|
27343
|
+
lastValidatedAt: string$2(),
|
|
27344
|
+
detail: string$2().nullable()
|
|
27345
|
+
});
|
|
27346
|
+
strictObject({
|
|
27347
|
+
displayName: string$2().min(1).max(255).optional(),
|
|
27348
|
+
registryUrl: string$2().url().max(512).optional(),
|
|
27349
|
+
scopes: array(registryScope).max(50).optional(),
|
|
27350
|
+
expiresAt: isoInstant.nullable().optional(),
|
|
27351
|
+
credential: registryCredential.optional(),
|
|
27352
|
+
testPackage: registryTestPackage.nullable().optional()
|
|
27353
|
+
});
|
|
27354
|
+
object({
|
|
27355
|
+
outcome: _enum([
|
|
27356
|
+
"uninstalled",
|
|
27357
|
+
"left_in_use",
|
|
27358
|
+
"uninstall_failed"
|
|
27359
|
+
]),
|
|
27360
|
+
manageUrl: string$2().nullable()
|
|
27361
|
+
});
|
|
27362
|
+
strictObject({
|
|
27363
|
+
displayName: string$2().min(1).max(255),
|
|
27364
|
+
ecosystem: _enum(REGISTRY_ECOSYSTEMS).optional(),
|
|
27365
|
+
vendor: _enum(REGISTRY_VENDORS),
|
|
27366
|
+
registryUrl: string$2().url().max(512),
|
|
27367
|
+
scopes: array(registryScope).max(50).optional(),
|
|
27368
|
+
expiresAt: isoInstant.nullable().optional(),
|
|
27369
|
+
credential: registryCredential,
|
|
27370
|
+
testPackage: registryTestPackage.optional()
|
|
27371
|
+
}), strictObject({ hostId: uuid.optional() }), object({ installUrl: string$2().url() }), strictObject({
|
|
27372
|
+
subdomain: string$2().min(1).max(39).optional(),
|
|
27373
|
+
serverUrl: string$2().min(1).max(2048).optional(),
|
|
27374
|
+
caBundlePem: string$2().max(65536).optional(),
|
|
27375
|
+
orgLogin: string$2().min(1).max(39),
|
|
27376
|
+
displayName: string$2().max(255).optional()
|
|
27377
|
+
}), object({
|
|
27378
|
+
manifest: record(string$2(), unknown$1()),
|
|
27379
|
+
postUrl: string$2().url(),
|
|
27380
|
+
state: string$2()
|
|
27381
|
+
}), 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
27382
|
name: string$2().min(1).max(50),
|
|
26930
27383
|
action: _enum(["accept", "decline"])
|
|
26931
27384
|
}), object({
|
|
@@ -26964,7 +27417,17 @@ strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({
|
|
|
26964
27417
|
}), strictObject({ ids: array(uuid).min(1).max(25) }), object({ referenced: array(string$2()) }), strictObject({
|
|
26965
27418
|
approve: array(string$2().min(1).max(100)).max(200).default([]),
|
|
26966
27419
|
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() })
|
|
27420
|
+
}).refine((b) => b.approve.length + b.remove.length > 0, { message: "Name a license to approve or remove" }), object({ version: number$1().nullable() }), strictObject({
|
|
27421
|
+
approve: string$2().trim().min(1).max(100).optional(),
|
|
27422
|
+
remove: string$2().trim().min(1).max(100).optional()
|
|
27423
|
+
}).refine((a) => Boolean(a.approve) !== Boolean(a.remove), { message: "Name one license to approve or remove" }), object({
|
|
27424
|
+
enrolled: number$1().int().nonnegative(),
|
|
27425
|
+
total: number$1().int().nonnegative()
|
|
27426
|
+
});
|
|
27427
|
+
strictObject({ kind: _enum(["sbom", "notices"]) }), object({
|
|
27428
|
+
url: string$2().url().nullable(),
|
|
27429
|
+
expiresAt: string$2().nullable()
|
|
27430
|
+
});
|
|
26968
27431
|
strictObject({
|
|
26969
27432
|
ignoreReason: _enum(MAINTENANCE_IGNORE_REASONS),
|
|
26970
27433
|
ignoreNote,
|
|
@@ -27167,6 +27630,7 @@ const TOOLSET_OF = {
|
|
|
27167
27630
|
taiga_fix_finding: "operate",
|
|
27168
27631
|
taiga_fix_policy_violation: "operate",
|
|
27169
27632
|
taiga_ignore_finding: "operate",
|
|
27633
|
+
taiga_allow_license: "operate",
|
|
27170
27634
|
taiga_resolve_action: "operate",
|
|
27171
27635
|
taiga_create_factory: "setup",
|
|
27172
27636
|
taiga_rename_factory: "setup",
|
|
@@ -27191,6 +27655,7 @@ const TOOLSET_OF = {
|
|
|
27191
27655
|
taiga_pause_line: "setup",
|
|
27192
27656
|
taiga_create_environment: "setup",
|
|
27193
27657
|
taiga_update_environment: "setup",
|
|
27658
|
+
taiga_ship_notices: "setup",
|
|
27194
27659
|
taiga_get_deployments: "observe",
|
|
27195
27660
|
taiga_get_monitoring: "observe",
|
|
27196
27661
|
taiga_get_delivery_metrics: "observe",
|
|
@@ -27242,6 +27707,7 @@ const DELIVERY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
27242
27707
|
"taiga_request_initiatives",
|
|
27243
27708
|
"taiga_fix_finding",
|
|
27244
27709
|
"taiga_fix_policy_violation",
|
|
27710
|
+
"taiga_ship_notices",
|
|
27245
27711
|
"taiga_set_auto_build",
|
|
27246
27712
|
"taiga_set_auto_merge",
|
|
27247
27713
|
"taiga_pause_line",
|
|
@@ -27491,7 +27957,6 @@ function setupNextStep(product) {
|
|
|
27491
27957
|
if (product.discoveryStep === void 0) return null;
|
|
27492
27958
|
switch (product.discoveryStep) {
|
|
27493
27959
|
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
27960
|
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
27961
|
}
|
|
27497
27962
|
}
|
|
@@ -27520,8 +27985,16 @@ const BY_ARGUMENT = {
|
|
|
27520
27985
|
knowledgeId: "taiga_list_knowledge lists the knowledge files.",
|
|
27521
27986
|
productId: "taiga_list_products lists the products the key can see; a name is not an ID (taiga_get_product takes a slug)."
|
|
27522
27987
|
};
|
|
27988
|
+
/**
|
|
27989
|
+
* A licence a product does not hold, as against a product the key cannot see
|
|
27990
|
+
* (PRO-3879). Any other 404 (the routes do not exist where the licenses
|
|
27991
|
+
* feature is off) blames neither.
|
|
27992
|
+
*/
|
|
27993
|
+
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
27994
|
/** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
|
|
27524
27995
|
const BY_TOOL = {
|
|
27996
|
+
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."),
|
|
27997
|
+
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
27998
|
taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
|
|
27526
27999
|
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
28000
|
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 +28471,8 @@ const PACKAGE_ROW_FIELDS = [
|
|
|
27998
28471
|
"scopeReason",
|
|
27999
28472
|
"parentReason",
|
|
28000
28473
|
"sourcePath",
|
|
28474
|
+
"baseImage",
|
|
28475
|
+
"imageLayer",
|
|
28001
28476
|
"productRepositoryId",
|
|
28002
28477
|
"detectedAt"
|
|
28003
28478
|
];
|
|
@@ -28019,7 +28494,10 @@ const FINDING_ROW_FIELDS = [
|
|
|
28019
28494
|
"tier",
|
|
28020
28495
|
"kevListedAt",
|
|
28021
28496
|
"malicious",
|
|
28497
|
+
"baseMove",
|
|
28498
|
+
"baseImageMove",
|
|
28022
28499
|
"removal",
|
|
28500
|
+
"replaceable",
|
|
28023
28501
|
"status",
|
|
28024
28502
|
"title",
|
|
28025
28503
|
"ecosystem",
|
|
@@ -28148,11 +28626,19 @@ function withFixability(row) {
|
|
|
28148
28626
|
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."
|
|
28149
28627
|
};
|
|
28150
28628
|
}
|
|
28151
|
-
if (row["kind"] === "license_violation") return {
|
|
28629
|
+
if (row["kind"] === "license_violation" && row["replaceable"] === true) return {
|
|
28152
28630
|
...row,
|
|
28153
|
-
fixable:
|
|
28154
|
-
fixNote: "
|
|
28631
|
+
fixable: true,
|
|
28632
|
+
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
28633
|
};
|
|
28634
|
+
if (row["kind"] === "license_violation") {
|
|
28635
|
+
const status = String(row["status"]);
|
|
28636
|
+
return {
|
|
28637
|
+
...row,
|
|
28638
|
+
fixable: false,
|
|
28639
|
+
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}.`
|
|
28640
|
+
};
|
|
28641
|
+
}
|
|
28156
28642
|
const dependency = row["kind"] === "vulnerable_dependency" || row["kind"] === "outdated_dependency";
|
|
28157
28643
|
const signals = {
|
|
28158
28644
|
malicious: typeof row["malicious"] === "boolean" ? row["malicious"] : null,
|
|
@@ -28176,6 +28662,15 @@ function withFixability(row) {
|
|
|
28176
28662
|
fixable: true,
|
|
28177
28663
|
fixNote: "A malicious package: taiga_fix_finding removes it (or replaces it), with every advisory on that version."
|
|
28178
28664
|
} : row;
|
|
28665
|
+
if (typeof row["baseMove"] === "boolean" && isAgentRemediable({ remediableBy: typeof row["remediableBy"] === "string" ? row["remediableBy"] : null })) return row["baseMove"] === true && row["status"] === "open" ? {
|
|
28666
|
+
...row,
|
|
28667
|
+
fixable: true,
|
|
28668
|
+
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."
|
|
28669
|
+
} : {
|
|
28670
|
+
...row,
|
|
28671
|
+
fixable: false,
|
|
28672
|
+
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."
|
|
28673
|
+
};
|
|
28179
28674
|
if (dependency && !row["recommendedVersion"] && row["remediableBy"] === "coding_agent") return {
|
|
28180
28675
|
...row,
|
|
28181
28676
|
fixable: false,
|
|
@@ -28253,7 +28748,10 @@ const ADVISORY_KEPT = [
|
|
|
28253
28748
|
"summary",
|
|
28254
28749
|
"details",
|
|
28255
28750
|
"severity",
|
|
28256
|
-
"
|
|
28751
|
+
"severitySource",
|
|
28752
|
+
"cvssScore",
|
|
28753
|
+
"published",
|
|
28754
|
+
"baseImage"
|
|
28257
28755
|
];
|
|
28258
28756
|
const ADVISORY_TEXT_LIMIT = 1500;
|
|
28259
28757
|
/** The packages of a licence finding shown without full: true (PRO-3394); the rest are counted. */
|
|
@@ -28294,6 +28792,36 @@ const SWEEP_FIELDS = [
|
|
|
28294
28792
|
"createdAt",
|
|
28295
28793
|
"completedAt"
|
|
28296
28794
|
];
|
|
28795
|
+
/**
|
|
28796
|
+
* The images a sweep's shipped images build on that it could not read, and
|
|
28797
|
+
* why (PRO-4073): a base nobody read is no clean base. Only where the API
|
|
28798
|
+
* returns the list (null with the containerImages flag off, or from a sweep
|
|
28799
|
+
* that listed none).
|
|
28800
|
+
*/
|
|
28801
|
+
const BASE_IMAGES_SHOWN = 10;
|
|
28802
|
+
function notReadBases(sweep) {
|
|
28803
|
+
const bases = sweep.baseImages ?? [];
|
|
28804
|
+
const notRead = bases.filter((b) => b.state === "not_read" || b.packages?.state === "not_read");
|
|
28805
|
+
const read = bases.flatMap((b) => b.packages?.state === "read" ? [{
|
|
28806
|
+
image: b.image,
|
|
28807
|
+
path: b.path,
|
|
28808
|
+
os: b.os ?? null,
|
|
28809
|
+
vulnerabilities: b.packages.vulnerabilities,
|
|
28810
|
+
removedByMoving: b.packages.candidate?.removes ?? 0,
|
|
28811
|
+
noDistroFix: b.packages.noDistroFix ?? null,
|
|
28812
|
+
movesTo: b.packages.candidate?.image ?? null
|
|
28813
|
+
}] : []);
|
|
28814
|
+
return {
|
|
28815
|
+
...read.length > 0 ? {
|
|
28816
|
+
baseImages: read.slice(0, BASE_IMAGES_SHOWN),
|
|
28817
|
+
...read.length > BASE_IMAGES_SHOWN ? { baseImagesCount: read.length } : {}
|
|
28818
|
+
} : {},
|
|
28819
|
+
...notRead.length > 0 ? {
|
|
28820
|
+
baseImagesNotRead: notRead.slice(0, BASE_IMAGES_SHOWN),
|
|
28821
|
+
...notRead.length > BASE_IMAGES_SHOWN ? { baseImagesNotReadCount: notRead.length } : {}
|
|
28822
|
+
} : {}
|
|
28823
|
+
};
|
|
28824
|
+
}
|
|
28297
28825
|
/** A policy violation in a list: what it is, how bad, and what is being done about it. */
|
|
28298
28826
|
const VIOLATION_FIELDS = [
|
|
28299
28827
|
"id",
|
|
@@ -28350,11 +28878,51 @@ function standingSummary(standing) {
|
|
|
28350
28878
|
resolvedViolations: resolved
|
|
28351
28879
|
};
|
|
28352
28880
|
}
|
|
28881
|
+
/**
|
|
28882
|
+
* How the backlog covers the controls that need a feature (PRO-3860), one row
|
|
28883
|
+
* each: the live initiatives that build it, or why it is deferred, and whether
|
|
28884
|
+
* nothing covers it. Empty before a backlog answered for any. It says what is
|
|
28885
|
+
* planned, never that a control is met.
|
|
28886
|
+
*/
|
|
28887
|
+
function controlCoverageList(controls) {
|
|
28888
|
+
return (Array.isArray(controls) ? controls : []).flatMap((control) => {
|
|
28889
|
+
const coverage = control["coverage"];
|
|
28890
|
+
if (!coverage || typeof coverage !== "object") return [];
|
|
28891
|
+
const initiatives = Array.isArray(coverage["initiatives"]) ? coverage["initiatives"] : [];
|
|
28892
|
+
return [{
|
|
28893
|
+
controlId: control["controlId"],
|
|
28894
|
+
state: control["state"],
|
|
28895
|
+
planned: plannedFor(coverage),
|
|
28896
|
+
uncovered: coverage["uncovered"] === true,
|
|
28897
|
+
...initiatives.length > 0 ? { initiatives: initiatives.map((item) => ({
|
|
28898
|
+
id: item["id"],
|
|
28899
|
+
title: item["title"]
|
|
28900
|
+
})) } : {},
|
|
28901
|
+
...coverage["deferredReason"] ? { deferredReason: coverage["deferredReason"] } : {}
|
|
28902
|
+
}];
|
|
28903
|
+
});
|
|
28904
|
+
}
|
|
28905
|
+
/**
|
|
28906
|
+
* Whether the backlog plans a control: yes when a live initiative builds it, later when it deferred it with a
|
|
28907
|
+
* reason, no otherwise. A deferred control reads "planned later" rather than only "not assessed" (PRO-3860).
|
|
28908
|
+
*/
|
|
28909
|
+
function plannedFor(coverage) {
|
|
28910
|
+
if (Array.isArray(coverage["initiatives"]) && coverage["initiatives"].length > 0) return "yes";
|
|
28911
|
+
return coverage["deferredReason"] ? "later" : "no";
|
|
28912
|
+
}
|
|
28913
|
+
/** The coverage, when the backlog answered for any control, for the default and full answers. */
|
|
28914
|
+
function controlCoveragePart(controls) {
|
|
28915
|
+
const coverage = controlCoverageList(controls);
|
|
28916
|
+
return coverage.length > 0 ? { controlCoverage: coverage } : {};
|
|
28917
|
+
}
|
|
28353
28918
|
const findingId = idInput("finding", "taiga_list_findings gives it");
|
|
28354
28919
|
/** Open findings by severity and kind, and the latest sweeps, as the first page of findings carries them. */
|
|
28355
28920
|
async function sweepOverview(client, productId) {
|
|
28356
28921
|
const [summary, sweeps] = await Promise.all([client.getMaintenanceSummary(productId), client.listMaintenanceSweeps(productId, { limit: 3 })]);
|
|
28357
|
-
const latest =
|
|
28922
|
+
const latest = sweeps.data.map((sweep) => ({
|
|
28923
|
+
...pickFields([sweep], SWEEP_FIELDS)[0],
|
|
28924
|
+
...notReadBases(sweep)
|
|
28925
|
+
}));
|
|
28358
28926
|
const { lastRun: _lastRun, lastRuns: _lastRuns, topFindings: _top, violatedControls: _controls, ...counts } = summary.data;
|
|
28359
28927
|
return {
|
|
28360
28928
|
...counts,
|
|
@@ -28362,6 +28930,20 @@ async function sweepOverview(client, productId) {
|
|
|
28362
28930
|
...latest.length === 0 ? { note: "This product has never been swept: no findings is not an all-clear." } : {}
|
|
28363
28931
|
};
|
|
28364
28932
|
}
|
|
28933
|
+
/** The organization's licence read takes an id this long at most, as `organizationLicenseQuery` does. */
|
|
28934
|
+
const ORGANIZATION_LICENSE_MAX = 200;
|
|
28935
|
+
/**
|
|
28936
|
+
* Why a license is allowed in a product, in a license's words, as the web's
|
|
28937
|
+
* Allow dialog offers them (PRO-3727); each is stored as the ignore reason
|
|
28938
|
+
* beside it.
|
|
28939
|
+
*/
|
|
28940
|
+
const LICENSE_ALLOW_REASON_TO_IGNORE = {
|
|
28941
|
+
accepted: "accepted_risk",
|
|
28942
|
+
not_handed_out: "not_reachable",
|
|
28943
|
+
no_alternative: "wont_fix",
|
|
28944
|
+
read_wrong: "false_positive"
|
|
28945
|
+
};
|
|
28946
|
+
const LICENSE_ALLOW_REASONS = Object.keys(LICENSE_ALLOW_REASON_TO_IGNORE);
|
|
28365
28947
|
function registerMaintenanceTools(server, client) {
|
|
28366
28948
|
if (client.hasCapability("tenant:settings")) server.registerTool("taiga_change_license", {
|
|
28367
28949
|
title: "Approve or remove a license",
|
|
@@ -28449,10 +29031,18 @@ function registerMaintenanceTools(server, client) {
|
|
|
28449
29031
|
}));
|
|
28450
29032
|
server.registerTool("taiga_list_licenses", {
|
|
28451
29033
|
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({
|
|
29034
|
+
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.",
|
|
29035
|
+
inputSchema: object({
|
|
29036
|
+
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"),
|
|
29037
|
+
productId: productId.optional().describe("With license: that license in this product only (taiga_list_products gives it)")
|
|
29038
|
+
}),
|
|
28454
29039
|
annotations: READ_ONLY
|
|
28455
|
-
}, (params) => runTool(async () =>
|
|
29040
|
+
}, (params) => runTool(async () => {
|
|
29041
|
+
if (params.productId !== void 0 && params.license === void 0) return toolError("VALIDATION_ERROR", "Name the license to read in this product (license).");
|
|
29042
|
+
if (params.license === void 0) return toolResult((await client.getOrganizationLicenses()).data);
|
|
29043
|
+
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.`);
|
|
29044
|
+
return toolResult(params.productId !== void 0 ? (await client.getProductLicense(params.productId, params.license)).data : (await client.getOrganizationLicense(params.license)).data);
|
|
29045
|
+
}));
|
|
28456
29046
|
server.registerTool("taiga_get_sbom", {
|
|
28457
29047
|
title: "Download a repository SBOM",
|
|
28458
29048
|
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 +29109,8 @@ function registerMaintenanceTools(server, client) {
|
|
|
28519
29109
|
const own = violations.filter((violation) => String(violation["controlId"]).toUpperCase() === wanted);
|
|
28520
29110
|
return toolResult({
|
|
28521
29111
|
...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." } : {},
|
|
29112
|
+
...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." } : {},
|
|
29113
|
+
...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
29114
|
...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
29115
|
...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
29116
|
violations: pickFields(own, VIOLATION_FIELDS),
|
|
@@ -28542,6 +29133,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
28542
29133
|
...wording,
|
|
28543
29134
|
...standing,
|
|
28544
29135
|
controls: listed,
|
|
29136
|
+
...controlCoveragePart(controls),
|
|
28545
29137
|
divergences: cutLongText(violations),
|
|
28546
29138
|
resolvedDivergences: cutLongText(resolvedAll.slice(0, FULL_RESOLVED)),
|
|
28547
29139
|
...resolvedAll.length > FULL_RESOLVED ? { resolvedNote: `The first ${FULL_RESOLVED} of ${resolvedAll.length} resolved violations.` } : {}
|
|
@@ -28565,7 +29157,8 @@ function registerMaintenanceTools(server, client) {
|
|
|
28565
29157
|
"findingsByTier",
|
|
28566
29158
|
"plannedCount"
|
|
28567
29159
|
]),
|
|
28568
|
-
violations: pickFields(violations, VIOLATION_FIELDS)
|
|
29160
|
+
violations: pickFields(violations, VIOLATION_FIELDS),
|
|
29161
|
+
...controlCoveragePart(controls)
|
|
28569
29162
|
});
|
|
28570
29163
|
}));
|
|
28571
29164
|
if (!client.hasCapability("products:write")) return;
|
|
@@ -28591,9 +29184,31 @@ function registerMaintenanceTools(server, client) {
|
|
|
28591
29184
|
const finding = (await client.updateFinding(params.findingId, body)).data;
|
|
28592
29185
|
return toolResult(findingRows([finding], ["ignoreNote"])[0]);
|
|
28593
29186
|
}));
|
|
29187
|
+
server.registerTool("taiga_allow_license", {
|
|
29188
|
+
title: "Allow or reopen a license in a product",
|
|
29189
|
+
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.",
|
|
29190
|
+
inputSchema: object({
|
|
29191
|
+
productId,
|
|
29192
|
+
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"),
|
|
29193
|
+
allowed: boolean().describe("true allows the license in this product, false reopens its findings"),
|
|
29194
|
+
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"),
|
|
29195
|
+
note: string$2().trim().max(280).optional().describe("A short justification, kept for audit"),
|
|
29196
|
+
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)")
|
|
29197
|
+
}),
|
|
29198
|
+
annotations: OPERATE_SETTING
|
|
29199
|
+
}, (params) => runTool(async () => {
|
|
29200
|
+
if (!params.allowed) return toolResult((await client.reopenProductLicense(params.productId, params.license)).data);
|
|
29201
|
+
if (params.reason === void 0) return toolError("VALIDATION_ERROR", "Say why the license is allowed (reason).");
|
|
29202
|
+
return toolResult((await client.allowProductLicense(params.productId, {
|
|
29203
|
+
license: params.license,
|
|
29204
|
+
ignoreReason: LICENSE_ALLOW_REASON_TO_IGNORE[params.reason],
|
|
29205
|
+
...params.note !== void 0 ? { ignoreNote: params.note } : {},
|
|
29206
|
+
...params.expiresAt !== void 0 ? { ignoreExpiresAt: params.expiresAt } : {}
|
|
29207
|
+
})).data);
|
|
29208
|
+
}));
|
|
28594
29209
|
server.registerTool("taiga_fix_finding", {
|
|
28595
29210
|
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).",
|
|
29211
|
+
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
29212
|
inputSchema: object({
|
|
28598
29213
|
productId,
|
|
28599
29214
|
findingId,
|
|
@@ -28632,6 +29247,13 @@ function registerMaintenanceTools(server, client) {
|
|
|
28632
29247
|
const body = { refuseIfFixedSinceAssessment: params.fixAgain !== true };
|
|
28633
29248
|
return toolResult((params.controlId ? await client.remediatePolicyControl(params.productId, params.controlId, body) : await client.remediatePolicyViolation(params.productId, params.violationId, body)).data);
|
|
28634
29249
|
}));
|
|
29250
|
+
if (!client.hasCapability("backlog:write")) return;
|
|
29251
|
+
server.registerTool("taiga_ship_notices", {
|
|
29252
|
+
title: "Ship open-source notices",
|
|
29253
|
+
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.",
|
|
29254
|
+
inputSchema: object({ productId }),
|
|
29255
|
+
annotations: OPERATE
|
|
29256
|
+
}, (params) => runTool(async () => toolResult((await client.shipNotices(params.productId)).data)));
|
|
28635
29257
|
}
|
|
28636
29258
|
//#endregion
|
|
28637
29259
|
//#region src/tools/sections.ts
|
|
@@ -28957,20 +29579,17 @@ async function setupReadiness(client, product, links) {
|
|
|
28957
29579
|
const settings = product["settings"] ?? {};
|
|
28958
29580
|
const importState = imported ? IMPORT_STATE[String(settings.importStatus)] ?? "not started" : void 0;
|
|
28959
29581
|
const missing = blockers ? blockers.missingDocuments : null;
|
|
28960
|
-
const openChecks = blockers ? blockers.openChecks.map((check) => check.criterion) : null;
|
|
28961
29582
|
return {
|
|
28962
|
-
ready: blockers ? missing.length === 0
|
|
29583
|
+
ready: blockers ? missing.length === 0 : null,
|
|
28963
29584
|
repository,
|
|
28964
29585
|
...imported ? { import: typeof settings.importStatus === "string" ? settings.importStatus : "pending" } : {},
|
|
28965
29586
|
...imported ? {} : { discoveryStep: full["discoveryStep"] ?? null },
|
|
28966
29587
|
missingDocuments: missing,
|
|
28967
|
-
openChecks,
|
|
28968
29588
|
nextStep: nextStepFor({
|
|
28969
29589
|
imported,
|
|
28970
29590
|
importState,
|
|
28971
29591
|
linked: repository?.linked ?? null,
|
|
28972
29592
|
missing,
|
|
28973
|
-
openChecks,
|
|
28974
29593
|
product: full
|
|
28975
29594
|
})
|
|
28976
29595
|
};
|
|
@@ -28992,7 +29611,6 @@ function nextStepFor(state) {
|
|
|
28992
29611
|
if (!state.missing) return setupNextStep(state.product);
|
|
28993
29612
|
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
29613
|
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
29614
|
return `Every required document is published: ${finish}`;
|
|
28997
29615
|
}
|
|
28998
29616
|
//#endregion
|
|
@@ -30076,7 +30694,8 @@ const READY_BLOCKER_SAID = {
|
|
|
30076
30694
|
bar_lowering: "a fix lowered a check instead of fixing the code",
|
|
30077
30695
|
bar_lowering_unchecked: "Taiga could not check whether a fix lowered a check",
|
|
30078
30696
|
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"
|
|
30697
|
+
clean_checkout_failed: "the branch does not install or pass its tests from a clean checkout",
|
|
30698
|
+
license_not_approved: "a package it adds has a licence that is not on your approved list"
|
|
30080
30699
|
};
|
|
30081
30700
|
/** The first sentence of a run's error, short enough for a row. */
|
|
30082
30701
|
function errorSaid(error) {
|
|
@@ -30834,6 +31453,10 @@ function standing(item) {
|
|
|
30834
31453
|
const life = item["lifecycle"];
|
|
30835
31454
|
const run = life?.latestBuild;
|
|
30836
31455
|
const situation = situationOf(item);
|
|
31456
|
+
const open = (life?.openPullRequests ?? []).flatMap((pull) => typeof pull.repositoryFullName === "string" && typeof pull.prNumber === "number" ? [{
|
|
31457
|
+
repository: pull.repositoryFullName,
|
|
31458
|
+
prNumber: pull.prNumber
|
|
31459
|
+
}] : []);
|
|
30837
31460
|
return {
|
|
30838
31461
|
column: typeof item["state"] === "string" ? item["state"] : null,
|
|
30839
31462
|
turn: typeof life?.turn === "string" ? life.turn : null,
|
|
@@ -30841,7 +31464,8 @@ function standing(item) {
|
|
|
30841
31464
|
latestRun: run && typeof run.id === "string" ? {
|
|
30842
31465
|
id: run.id,
|
|
30843
31466
|
...typeof run.prNumber === "number" ? { prNumber: run.prNumber } : {}
|
|
30844
|
-
} : null
|
|
31467
|
+
} : null,
|
|
31468
|
+
...open.length > 1 ? { pullRequests: open } : {}
|
|
30845
31469
|
};
|
|
30846
31470
|
}
|
|
30847
31471
|
/** The move each call to action is, in the tools' words. */
|
|
@@ -31024,7 +31648,8 @@ const PLAN_SUMMARY_FIELDS = [
|
|
|
31024
31648
|
"backlogItemId",
|
|
31025
31649
|
"version",
|
|
31026
31650
|
"status",
|
|
31027
|
-
"createdAt"
|
|
31651
|
+
"createdAt",
|
|
31652
|
+
"controls"
|
|
31028
31653
|
];
|
|
31029
31654
|
const PLAN_STEP_SUMMARY_FIELDS = [
|
|
31030
31655
|
"id",
|
|
@@ -31171,10 +31796,12 @@ function registerBacklogReads(server, client) {
|
|
|
31171
31796
|
...revised
|
|
31172
31797
|
});
|
|
31173
31798
|
const steps = Array.isArray(plan.steps) ? plan.steps : [];
|
|
31799
|
+
const several = (plan.repositories?.length ?? 0) > 1;
|
|
31174
31800
|
return toolResult({
|
|
31175
31801
|
...pickFields([plan], PLAN_SUMMARY_FIELDS)[0],
|
|
31802
|
+
...several ? { repositories: plan.repositories } : {},
|
|
31176
31803
|
...revised,
|
|
31177
|
-
steps: pickFields(steps, PLAN_STEP_SUMMARY_FIELDS)
|
|
31804
|
+
steps: several ? pickFields(steps, [...PLAN_STEP_SUMMARY_FIELDS, "repositoryName"]) : pickFields(steps, PLAN_STEP_SUMMARY_FIELDS)
|
|
31178
31805
|
});
|
|
31179
31806
|
}));
|
|
31180
31807
|
}
|
|
@@ -32130,7 +32757,7 @@ async function licenseReviews(client, productId, now) {
|
|
|
32130
32757
|
packages: data["packages"],
|
|
32131
32758
|
...Number.isNaN(created) ? {} : { waitingDays: Math.floor((now - created) / DAY_MS) },
|
|
32132
32759
|
readWith: "taiga_list_licenses (license)",
|
|
32133
|
-
decideWith: "taiga_change_license (license, action: 'approve'); dismissing it is only in the web"
|
|
32760
|
+
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
32761
|
};
|
|
32135
32762
|
}).filter((r) => productId === void 0 || r.productId === productId),
|
|
32136
32763
|
truncated: result.truncated === true
|
|
@@ -48897,7 +49524,7 @@ function registerSetupTools(server, client) {
|
|
|
48897
49524
|
}, (params) => runTool(async () => toolResult((await client.refreshRealityDocuments(params.productId, params.docTypes)).data)));
|
|
48898
49525
|
server.registerTool("taiga_finish_discovery", {
|
|
48899
49526
|
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)
|
|
49527
|
+
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
49528
|
inputSchema: object({ productId }),
|
|
48902
49529
|
annotations: OPERATE
|
|
48903
49530
|
}, (params) => runTool(async () => {
|
|
@@ -49162,6 +49789,7 @@ const PAID_WORK = /* @__PURE__ */ new Set([
|
|
|
49162
49789
|
"taiga_request_changes",
|
|
49163
49790
|
"taiga_request_initiatives",
|
|
49164
49791
|
"taiga_resume_initiative",
|
|
49792
|
+
"taiga_ship_notices",
|
|
49165
49793
|
"taiga_retry_knowledge",
|
|
49166
49794
|
"taiga_review_plan",
|
|
49167
49795
|
"taiga_set_auto_build",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.36.0
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Taiga for AI coding assistants: read products, policies, initiatives and runs, and operate the line, over MCP",
|
|
5
5
|
"mcpName": "ga.tai/mcp-server",
|
|
6
6
|
"license": "UNLICENSED",
|