gitlab-api 27.3.0__py3-none-any.whl → 27.3.2__py3-none-any.whl

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.
@@ -0,0 +1,439 @@
1
+ connector: gitlab-api
2
+ ontology_source: gitlab
3
+ schema_version: '1'
4
+ resources:
5
+ - name: Branch
6
+ label: Branch
7
+ id_prefix: branch
8
+ relations: []
9
+ - name: CheckRun
10
+ label: Check Run
11
+ id_prefix: checkrun
12
+ relations: []
13
+ - name: Commit
14
+ label: Commit
15
+ id_prefix: commit
16
+ relations: []
17
+ - name: Epic
18
+ label: Epic
19
+ id_prefix: epic
20
+ relations: []
21
+ - name: GitLabGroup
22
+ label: GitLab Group
23
+ id_prefix: gitlabgroup
24
+ relations: []
25
+ - name: Issue
26
+ label: Issue
27
+ id_prefix: issue
28
+ relations: []
29
+ - name: Job
30
+ label: Job
31
+ id_prefix: job
32
+ relations:
33
+ - name: ranOnRunner
34
+ label: ran on runner
35
+ target: Runner
36
+ lpg_rel_type: ''
37
+ - name: MergeRequest
38
+ label: Merge Request
39
+ id_prefix: mergerequest
40
+ relations:
41
+ - name: closesIssue
42
+ label: closes issue
43
+ target: Issue
44
+ lpg_rel_type: ''
45
+ - name: targetsBranch
46
+ label: targets branch
47
+ target: Branch
48
+ lpg_rel_type: ''
49
+ - name: Milestone
50
+ label: Milestone
51
+ id_prefix: milestone
52
+ relations: []
53
+ - name: Pipeline
54
+ label: Pipeline
55
+ id_prefix: pipeline
56
+ relations:
57
+ - name: hasJob
58
+ label: has job
59
+ target: Job
60
+ lpg_rel_type: ''
61
+ - name: PipelineRun
62
+ label: Pipeline Run
63
+ id_prefix: pipelinerun
64
+ relations:
65
+ - name: ranFor
66
+ label: ran for
67
+ target: owl:Thing
68
+ lpg_rel_type: ''
69
+ - name: Project
70
+ label: Project
71
+ id_prefix: project
72
+ relations:
73
+ - name: partOfGroup
74
+ label: part of group
75
+ target: GitLabGroup
76
+ lpg_rel_type: ''
77
+ - name: Release
78
+ label: Release
79
+ id_prefix: release
80
+ relations: []
81
+ - name: Runner
82
+ label: Runner
83
+ id_prefix: runner
84
+ relations: []
85
+ actions:
86
+ - id: epistemic-answer
87
+ name: Epistemic Answer
88
+ description: 'Answers epistemic_status/why/what_changed queries over the shared knowledge graph: calibrated
89
+ confidence, evidence/source citations, belief justification trees, bitemporal valid/tx history, and
90
+ policy-redaction-aware provenance.'
91
+ label: ''
92
+ parameters: []
93
+ target_resource: null
94
+ conflict_policy: null
95
+ requires_approval: true
96
+ approval_class: unclassified
97
+ effects: []
98
+ - id: run_graph_flow
99
+ name: Graph Flow Execution
100
+ description: Execute a workflow through the agent's graph orchestration engine
101
+ label: ''
102
+ parameters: []
103
+ target_resource: null
104
+ conflict_policy: null
105
+ requires_approval: true
106
+ approval_class: unclassified
107
+ effects: []
108
+ events:
109
+ - name: gitlab-projects.updated
110
+ resource: repository
111
+ description: "Watermark event for 'gitlab-projects' \u2014 advances on last_activity_at (mcp_source_presets.json)."
112
+ identity:
113
+ id_field:
114
+ repository: id
115
+ title_field:
116
+ repository: path_with_namespace
117
+ text_field:
118
+ repository: description
119
+ updated_field:
120
+ repository: last_activity_at
121
+ permissions:
122
+ acl_fields: []
123
+ tenant_field: null
124
+ read_roles: []
125
+ schema_mappings:
126
+ Branch:
127
+ ontology_class: null
128
+ fields:
129
+ createdAt: xsd:dateTime
130
+ duration: xsd:decimal
131
+ failureReason: xsd:string
132
+ finishedAt: xsd:dateTime
133
+ iid: xsd:string
134
+ logUrl: xsd:string
135
+ pipelineStatus: xsd:string
136
+ ref: xsd:string
137
+ sha: xsd:string
138
+ startedAt: xsd:dateTime
139
+ state: xsd:string
140
+ triggerSource: xsd:string
141
+ webUrl: xsd:string
142
+ CheckRun:
143
+ ontology_class: null
144
+ fields:
145
+ createdAt: xsd:dateTime
146
+ duration: xsd:decimal
147
+ failureReason: xsd:string
148
+ finishedAt: xsd:dateTime
149
+ iid: xsd:string
150
+ logUrl: xsd:string
151
+ pipelineStatus: xsd:string
152
+ ref: xsd:string
153
+ sha: xsd:string
154
+ startedAt: xsd:dateTime
155
+ state: xsd:string
156
+ triggerSource: xsd:string
157
+ webUrl: xsd:string
158
+ Commit:
159
+ ontology_class: null
160
+ fields:
161
+ createdAt: xsd:dateTime
162
+ duration: xsd:decimal
163
+ failureReason: xsd:string
164
+ finishedAt: xsd:dateTime
165
+ iid: xsd:string
166
+ logUrl: xsd:string
167
+ pipelineStatus: xsd:string
168
+ ref: xsd:string
169
+ sha: xsd:string
170
+ startedAt: xsd:dateTime
171
+ state: xsd:string
172
+ triggerSource: xsd:string
173
+ webUrl: xsd:string
174
+ Epic:
175
+ ontology_class: null
176
+ fields:
177
+ createdAt: xsd:dateTime
178
+ duration: xsd:decimal
179
+ failureReason: xsd:string
180
+ finishedAt: xsd:dateTime
181
+ iid: xsd:string
182
+ logUrl: xsd:string
183
+ pipelineStatus: xsd:string
184
+ ref: xsd:string
185
+ sha: xsd:string
186
+ startedAt: xsd:dateTime
187
+ state: xsd:string
188
+ triggerSource: xsd:string
189
+ webUrl: xsd:string
190
+ GitLabGroup:
191
+ ontology_class: Organization
192
+ fields:
193
+ createdAt: xsd:dateTime
194
+ duration: xsd:decimal
195
+ failureReason: xsd:string
196
+ finishedAt: xsd:dateTime
197
+ iid: xsd:string
198
+ logUrl: xsd:string
199
+ pipelineStatus: xsd:string
200
+ ref: xsd:string
201
+ sha: xsd:string
202
+ startedAt: xsd:dateTime
203
+ state: xsd:string
204
+ triggerSource: xsd:string
205
+ webUrl: xsd:string
206
+ Issue:
207
+ ontology_class: Incident
208
+ fields:
209
+ createdAt: xsd:dateTime
210
+ duration: xsd:decimal
211
+ failureReason: xsd:string
212
+ finishedAt: xsd:dateTime
213
+ iid: xsd:string
214
+ logUrl: xsd:string
215
+ pipelineStatus: xsd:string
216
+ ref: xsd:string
217
+ sha: xsd:string
218
+ startedAt: xsd:dateTime
219
+ state: xsd:string
220
+ triggerSource: xsd:string
221
+ webUrl: xsd:string
222
+ Job:
223
+ ontology_class: Action
224
+ fields:
225
+ createdAt: xsd:dateTime
226
+ duration: xsd:decimal
227
+ failureReason: xsd:string
228
+ finishedAt: xsd:dateTime
229
+ iid: xsd:string
230
+ logUrl: xsd:string
231
+ pipelineStatus: xsd:string
232
+ ref: xsd:string
233
+ sha: xsd:string
234
+ startedAt: xsd:dateTime
235
+ state: xsd:string
236
+ triggerSource: xsd:string
237
+ webUrl: xsd:string
238
+ MergeRequest:
239
+ ontology_class: null
240
+ fields:
241
+ createdAt: xsd:dateTime
242
+ duration: xsd:decimal
243
+ failureReason: xsd:string
244
+ finishedAt: xsd:dateTime
245
+ iid: xsd:string
246
+ logUrl: xsd:string
247
+ pipelineStatus: xsd:string
248
+ ref: xsd:string
249
+ sha: xsd:string
250
+ startedAt: xsd:dateTime
251
+ state: xsd:string
252
+ triggerSource: xsd:string
253
+ webUrl: xsd:string
254
+ Milestone:
255
+ ontology_class: null
256
+ fields:
257
+ createdAt: xsd:dateTime
258
+ duration: xsd:decimal
259
+ failureReason: xsd:string
260
+ finishedAt: xsd:dateTime
261
+ iid: xsd:string
262
+ logUrl: xsd:string
263
+ pipelineStatus: xsd:string
264
+ ref: xsd:string
265
+ sha: xsd:string
266
+ startedAt: xsd:dateTime
267
+ state: xsd:string
268
+ triggerSource: xsd:string
269
+ webUrl: xsd:string
270
+ Pipeline:
271
+ ontology_class: null
272
+ fields:
273
+ createdAt: xsd:dateTime
274
+ duration: xsd:decimal
275
+ failureReason: xsd:string
276
+ finishedAt: xsd:dateTime
277
+ iid: xsd:string
278
+ logUrl: xsd:string
279
+ pipelineStatus: xsd:string
280
+ ref: xsd:string
281
+ sha: xsd:string
282
+ startedAt: xsd:dateTime
283
+ state: xsd:string
284
+ triggerSource: xsd:string
285
+ webUrl: xsd:string
286
+ PipelineRun:
287
+ ontology_class: null
288
+ fields:
289
+ createdAt: xsd:dateTime
290
+ duration: xsd:decimal
291
+ failureReason: xsd:string
292
+ finishedAt: xsd:dateTime
293
+ iid: xsd:string
294
+ logUrl: xsd:string
295
+ pipelineStatus: xsd:string
296
+ ref: xsd:string
297
+ sha: xsd:string
298
+ startedAt: xsd:dateTime
299
+ state: xsd:string
300
+ triggerSource: xsd:string
301
+ webUrl: xsd:string
302
+ Project:
303
+ ontology_class: SoftwareProject
304
+ fields:
305
+ createdAt: xsd:dateTime
306
+ duration: xsd:decimal
307
+ failureReason: xsd:string
308
+ finishedAt: xsd:dateTime
309
+ iid: xsd:string
310
+ logUrl: xsd:string
311
+ pipelineStatus: xsd:string
312
+ ref: xsd:string
313
+ sha: xsd:string
314
+ startedAt: xsd:dateTime
315
+ state: xsd:string
316
+ triggerSource: xsd:string
317
+ webUrl: xsd:string
318
+ Release:
319
+ ontology_class: null
320
+ fields:
321
+ createdAt: xsd:dateTime
322
+ duration: xsd:decimal
323
+ failureReason: xsd:string
324
+ finishedAt: xsd:dateTime
325
+ iid: xsd:string
326
+ logUrl: xsd:string
327
+ pipelineStatus: xsd:string
328
+ ref: xsd:string
329
+ sha: xsd:string
330
+ startedAt: xsd:dateTime
331
+ state: xsd:string
332
+ triggerSource: xsd:string
333
+ webUrl: xsd:string
334
+ Runner:
335
+ ontology_class: null
336
+ fields:
337
+ createdAt: xsd:dateTime
338
+ duration: xsd:decimal
339
+ failureReason: xsd:string
340
+ finishedAt: xsd:dateTime
341
+ iid: xsd:string
342
+ logUrl: xsd:string
343
+ pipelineStatus: xsd:string
344
+ ref: xsd:string
345
+ sha: xsd:string
346
+ startedAt: xsd:dateTime
347
+ state: xsd:string
348
+ triggerSource: xsd:string
349
+ webUrl: xsd:string
350
+ sync:
351
+ - preset: gitlab-projects
352
+ server: gitlab-mcp
353
+ tool: gitlab_get_projects
354
+ action: null
355
+ records_path: data
356
+ id_field: id
357
+ title_field: path_with_namespace
358
+ text_field: description
359
+ updated_field: last_activity_at
360
+ pagination: page
361
+ doc_type: repository
362
+ tool_schema_sha256: 18b7c440b1165616ba8455535985becf3c14d643a22f40c34d4f321c736c9e06
363
+ raw:
364
+ server: gitlab-mcp
365
+ tool: gitlab_get_projects
366
+ records_path: data
367
+ id_field: id
368
+ title_field: path_with_namespace
369
+ text_field: description
370
+ updated_field: last_activity_at
371
+ pagination: page
372
+ page_kind: number
373
+ page_param: page
374
+ page_size_param: per_page
375
+ doc_type: repository
376
+ params_style: args
377
+ provenance:
378
+ generated_by: scripts/generate_connector_manifests.py
379
+ generated_at: '2026-10-18T00:00:00Z'
380
+ source_artifacts:
381
+ - gitlab_api/connectors/mcp_source_presets.json
382
+ - gitlab_api/connectors/tool_schema_fingerprints.json
383
+ - gitlab_api/ontology/gitlab.ttl
384
+ integrity:
385
+ algorithm: urdna2015-sha256
386
+ hash: 832bf7f47b008ce2c318e1d89f2a2a306992994ec0f44d2ee102919eac7bedb4
387
+ triple_count: 113
388
+ signer: UNSIGNED-PREVIEW
389
+ signature_algorithm: none
390
+ signing_public_key: ''
391
+ signature: null
392
+ source_commit: 868761312fcdc217b3f596fcf4cfcc8a173752e6
393
+ policy:
394
+ pii_fields: {}
395
+ tenant_boundary: null
396
+ rls: []
397
+ review_todos:
398
+ - "policy.rls / policy.tenant_boundary are unset \u2014 no row-level-security predicate has been reviewed\
399
+ \ for this connector."
400
+ - "relation 'authoredBy' has no declared rdfs:domain resolving to a known resource in this connector's\
401
+ \ ontology \u2014 not attached to any resource; verify its true domain manually."
402
+ - "relation 'belongsToProject' has no declared rdfs:domain resolving to a known resource in this connector's\
403
+ \ ontology \u2014 not attached to any resource; verify its true domain manually."
404
+ - "relation 'hasMilestone' has no declared rdfs:domain resolving to a known resource in this connector's\
405
+ \ ontology \u2014 not attached to any resource; verify its true domain manually."
406
+ - "relation 'triggeredPipeline' has no declared rdfs:domain resolving to a known resource in this connector's\
407
+ \ ontology \u2014 not attached to any resource; verify its true domain manually."
408
+ - "schema_mappings.Branch.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
409
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
410
+ - "schema_mappings.CheckRun.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
411
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
412
+ - "schema_mappings.Commit.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
413
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
414
+ - "schema_mappings.Epic.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
415
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
416
+ - "schema_mappings.GitLabGroup.ontology_class = 'Organization' [DRAFT \u2014 nearest hub-canonical-class-by-name\
417
+ \ heuristic (D16 residue; human sign-off required before use)] \u2014 verify manually before relying\
418
+ \ on this crosswalk for reasoning/joins."
419
+ - "schema_mappings.Issue.ontology_class = 'Incident' [DRAFT \u2014 nearest hub-canonical-class-by-name\
420
+ \ heuristic (D16 residue; human sign-off required before use)] \u2014 verify manually before relying\
421
+ \ on this crosswalk for reasoning/joins."
422
+ - "schema_mappings.Job.ontology_class = 'Action' [DRAFT \u2014 nearest hub-canonical-class-by-name heuristic\
423
+ \ (D16 residue; human sign-off required before use)] \u2014 verify manually before relying on this crosswalk\
424
+ \ for reasoning/joins."
425
+ - "schema_mappings.MergeRequest.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
426
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
427
+ - "schema_mappings.Milestone.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
428
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
429
+ - "schema_mappings.Pipeline.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
430
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
431
+ - "schema_mappings.PipelineRun.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
432
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
433
+ - "schema_mappings.Project.ontology_class = 'SoftwareProject' [DRAFT \u2014 nearest hub-canonical-class-by-name\
434
+ \ heuristic (D16 residue; human sign-off required before use)] \u2014 verify manually before relying\
435
+ \ on this crosswalk for reasoning/joins."
436
+ - "schema_mappings.Release.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
437
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
438
+ - "schema_mappings.Runner.ontology_class = None [UNRESOLVED \u2014 no crosswalk found by any heuristic]\
439
+ \ \u2014 verify manually before relying on this crosswalk for reasoning/joins."
@@ -40,9 +40,14 @@ from pydantic import (
40
40
  model_validator,
41
41
  )
42
42
 
43
- PREP_CONTRACT_VERSION = "1"
44
- ARROW_IPC = "arrow_ipc"
45
- SOURCE = "gitlab-api"
43
+ # A typing.Literal takes literal values, not variable names: each constant
44
+ # has its own literal type, and the fields annotate with that type.
45
+ PrepContractVersion = Literal["1"]
46
+ ArrowIpc = Literal["arrow_ipc"]
47
+ SourceName = Literal["gitlab-api"]
48
+ PREP_CONTRACT_VERSION: PrepContractVersion = "1"
49
+ ARROW_IPC: ArrowIpc = "arrow_ipc"
50
+ SOURCE: SourceName = "gitlab-api"
46
51
  DOMAIN = "gitlab"
47
52
 
48
53
  MAX_PAGE_RECORDS = 100
@@ -76,7 +81,7 @@ class PrepErrorCode(StrEnum):
76
81
  FIELD_LIMIT = "field_limit"
77
82
  PAGE_LIMIT = "page_too_large"
78
83
  RECORD_LIMIT = "record_limit"
79
- SECRET = "sensitive_payload"
84
+ SECRET = "sensitive_payload" # nosec B105 # sanitizer:ignore - enum value naming a refusal reason, not a credential
80
85
  TEXT_LIMIT = "text_limit"
81
86
  DUPLICATE_ID = "duplicate_id"
82
87
  PATH_ABUSE = "path_or_url_abuse"
@@ -276,7 +281,7 @@ class NamespacePayload(_PrepModel):
276
281
  class GitLabProjectPayload(_PrepModel):
277
282
  """Versioned strict projection of the GitLab project response."""
278
283
 
279
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
284
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
280
285
  id: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
281
286
  name: StrictStr
282
287
  path_with_namespace: StrictStr
@@ -332,7 +337,7 @@ class GitLabProjectPayload(_PrepModel):
332
337
  class GitLabIssuePayload(_PrepModel):
333
338
  """Versioned strict projection of the GitLab issue response."""
334
339
 
335
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
340
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
336
341
  id: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
337
342
  iid: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
338
343
  project_id: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
@@ -390,7 +395,7 @@ class GitLabIssuePayload(_PrepModel):
390
395
  class GitLabMergeRequestPayload(_PrepModel):
391
396
  """Versioned strict projection of the GitLab merge-request response."""
392
397
 
393
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
398
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
394
399
  id: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
395
400
  iid: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
396
401
  project_id: StrictInt = Field(gt=0, le=MAX_IDENTIFIER)
@@ -515,9 +520,9 @@ class ArrowField(_PrepModel):
515
520
  class ArrowPrepPlan(_PrepModel):
516
521
  """A versioned declaration of the Arrow boundary, not an Arrow runtime."""
517
522
 
518
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
523
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
519
524
  record_kind: RecordKind
520
- handoff_format: Literal[ARROW_IPC] = ARROW_IPC
525
+ handoff_format: ArrowIpc = ARROW_IPC
521
526
  operations: tuple[StrictStr, ...]
522
527
  arrow_schema: tuple[ArrowField, ...]
523
528
  engine_dependencies: tuple[StrictStr, ...] = ()
@@ -577,8 +582,8 @@ def arrow_prep_plan(record_kind: RecordKind) -> ArrowPrepPlan:
577
582
  class PrepEvidence(_PrepModel):
578
583
  """Safe lineage and validation evidence; never stores a source row."""
579
584
 
580
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
581
- source: Literal[SOURCE] = SOURCE
585
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
586
+ source: SourceName = SOURCE
582
587
  record_kind: RecordKind
583
588
  record_ref: StrictStr
584
589
  disposition: PrepDisposition
@@ -754,7 +759,7 @@ class CheckpointOutcome(_PrepModel):
754
759
  class Checkpoint(_PrepModel):
755
760
  """A page digest plus safe terminal outcomes, suitable for CAS storage."""
756
761
 
757
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
762
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
758
763
  stream: StrictStr
759
764
  page: StrictInt = Field(ge=1, le=MAX_PAGES)
760
765
  cursor_digest: StrictStr | None = None
@@ -833,7 +838,7 @@ class MemoryCheckpointStore:
833
838
  class PrepResult(_PrepModel):
834
839
  """Page-level result with bounded evidence and checkpoint state."""
835
840
 
836
- schema_version: Literal[PREP_CONTRACT_VERSION] = PREP_CONTRACT_VERSION
841
+ schema_version: PrepContractVersion = PREP_CONTRACT_VERSION
837
842
  record_kind: RecordKind
838
843
  stream: StrictStr
839
844
  page: StrictInt = Field(ge=1, le=MAX_PAGES)
@@ -1181,7 +1186,9 @@ def _record_identity(model: BaseModel) -> tuple[object, ...]:
1181
1186
  return ("project", model.id)
1182
1187
  if isinstance(model, GitLabIssuePayload):
1183
1188
  return ("issue", model.project_id, model.iid)
1184
- return ("merge_request", model.project_id, model.iid)
1189
+ if isinstance(model, GitLabMergeRequestPayload):
1190
+ return ("merge_request", model.project_id, model.iid)
1191
+ raise TypeError(f"no record identity for {type(model).__name__}")
1185
1192
 
1186
1193
 
1187
1194
  class _PageRecordContext(NamedTuple):
@@ -1450,8 +1457,10 @@ class ConnectorPrep:
1450
1457
  if outcome is not None:
1451
1458
  outcomes.append(outcome)
1452
1459
  continue
1460
+ if item is None or identity is None:
1461
+ raise RuntimeError("an accepted record has no item or identity")
1453
1462
  seen.setdefault(identity, []).append(ordinal)
1454
- accepted.append(item) # type: ignore[arg-type]
1463
+ accepted.append(item)
1455
1464
  return outcomes, accepted, seen
1456
1465
 
1457
1466
  def _drop_duplicate_records(
gitlab_api/gitlab_gql.py CHANGED
@@ -817,7 +817,7 @@ class GraphQL:
817
817
  author_email: str | None = None,
818
818
  author_name: str | None = None,
819
819
  _stats: bool | None = False,
820
- force: bool | None = False,
820
+ _force: bool | None = False,
821
821
  ) -> dict[str, Any]:
822
822
  """
823
823
  Create a commit in a project.
@@ -1073,8 +1073,8 @@ class GraphQL:
1073
1073
  state: Optional merge request state (e.g., 'opened').
1074
1074
  labels: Optional list of labels (not supported in GraphQL).
1075
1075
  milestone: Optional milestone title (not supported in GraphQL).
1076
- author_username: Optional author username (not supported in GraphQL).
1077
- reviewer_username: Optional reviewer username (not supported in GraphQL).
1076
+ author_username: Optional author username filter.
1077
+ reviewer_username: Optional reviewer username filter.
1078
1078
  source_branch: Optional source branch (not supported in GraphQL).
1079
1079
  target_branch: Optional target branch (not supported in GraphQL).
1080
1080
  search: Optional search term (not supported in GraphQL).
@@ -1086,9 +1086,9 @@ class GraphQL:
1086
1086
  """
1087
1087
  MergeRequestModel(project_id=project_id, state=state) # type: ignore
1088
1088
  query = """
1089
- query ($fullPath: ID!, $state: MergeRequestState, $first: Int, $after: String) {
1089
+ query ($fullPath: ID!, $state: MergeRequestState, $authorUsername: String, $reviewerUsername: String, $first: Int, $after: String) {
1090
1090
  project(fullPath: $fullPath) {
1091
- mergeRequests(state: $state, first: $first, after: $after) {
1091
+ mergeRequests(state: $state, authorUsername: $authorUsername, reviewerUsername: $reviewerUsername, first: $first, after: $after) {
1092
1092
  nodes {
1093
1093
  iid
1094
1094
  title
@@ -1106,6 +1106,10 @@ class GraphQL:
1106
1106
  variables = {"fullPath": str(project_id), "first": first}
1107
1107
  if state:
1108
1108
  variables["state"] = state
1109
+ if author_username:
1110
+ variables["authorUsername"] = author_username
1111
+ if reviewer_username:
1112
+ variables["reviewerUsername"] = reviewer_username
1109
1113
  if after:
1110
1114
  variables["after"] = after
1111
1115
  return self.execute_gql(query, variables=variables)
@@ -1347,7 +1351,7 @@ class GraphQL:
1347
1351
  username: str | None = None,
1348
1352
  updated_after: str | None = None,
1349
1353
  updated_before: str | None = None,
1350
- order_by: str | None = None,
1354
+ _order_by: str | None = None,
1351
1355
  sort: str | None = None,
1352
1356
  first: int | None = 20,
1353
1357
  after: str | None = None,
@@ -1361,8 +1365,8 @@ class GraphQL:
1361
1365
  status: Optional pipeline status filter (not supported in GraphQL).
1362
1366
  source: Optional source filter (not supported in GraphQL).
1363
1367
  username: Optional username filter (not supported in GraphQL).
1364
- updated_after: Optional updated after filter (not supported in GraphQL).
1365
- updated_before: Optional updated before filter (not supported in GraphQL).
1368
+ updated_after: Optional filter: pipelines updated after this time.
1369
+ updated_before: Optional filter: pipelines updated before this time.
1366
1370
  order_by: Optional order by field (not supported in GraphQL).
1367
1371
  sort: Optional sort order (not supported in GraphQL).
1368
1372
  first: Number of pipelines to fetch.
@@ -1373,9 +1377,9 @@ class GraphQL:
1373
1377
  """
1374
1378
  PipelineModel(project_id=project_id)
1375
1379
  query = """
1376
- query ($fullPath: ID!, $first: Int, $after: String) {
1380
+ query ($fullPath: ID!, $updatedAfter: Time, $updatedBefore: Time, $first: Int, $after: String) {
1377
1381
  project(fullPath: $fullPath) {
1378
- pipelines(first: $first, after: $after) {
1382
+ pipelines(updatedAfter: $updatedAfter, updatedBefore: $updatedBefore, first: $first, after: $after) {
1379
1383
  nodes {
1380
1384
  id
1381
1385
  status
@@ -1390,6 +1394,10 @@ class GraphQL:
1390
1394
  }
1391
1395
  """
1392
1396
  variables = {"fullPath": str(project_id), "first": first}
1397
+ if updated_after:
1398
+ variables["updatedAfter"] = updated_after
1399
+ if updated_before:
1400
+ variables["updatedBefore"] = updated_before
1393
1401
  if after:
1394
1402
  variables["after"] = after
1395
1403
  return self.execute_gql(query, variables=variables)
@@ -2622,9 +2630,9 @@ class GraphQL:
2622
2630
  """
2623
2631
  IssueModel(project_id=project_id) # type: ignore
2624
2632
  query = """
2625
- query ($fullPath: ID!, $state: IssueState, $labels: [String!], $assigneeUsernames: [String!], $search: String, $first: Int, $after: String) {
2633
+ query ($fullPath: ID!, $state: IssueState, $labels: [String!], $authorUsername: String, $assigneeUsernames: [String!], $search: String, $first: Int, $after: String) {
2626
2634
  project(fullPath: $fullPath) {
2627
- issues(state: $state, labels: $labels, assigneeUsernames: $assigneeUsernames, search: $search, first: $first, after: $after) {
2635
+ issues(state: $state, labels: $labels, authorUsername: $authorUsername, assigneeUsernames: $assigneeUsernames, search: $search, first: $first, after: $after) {
2628
2636
  nodes {
2629
2637
  iid
2630
2638
  title
@@ -2643,6 +2651,8 @@ class GraphQL:
2643
2651
  variables["state"] = state
2644
2652
  if labels:
2645
2653
  variables["labels"] = labels # type: ignore
2654
+ if author_username:
2655
+ variables["authorUsername"] = author_username
2646
2656
  if assignee_username:
2647
2657
  variables["assigneeUsernames"] = [assignee_username] # type: ignore
2648
2658
  if search:
gitlab_api/mcp_server.py CHANGED
@@ -35,7 +35,7 @@ from agent_connector_sdk.mcp.tool_surface import register_tool_surface
35
35
  from gitlab_api.api_client import Api
36
36
  from gitlab_api.auth import get_client
37
37
 
38
- __version__ = "27.3.0"
38
+ __version__ = "27.3.2"
39
39
  print(f"Gitlab MCP v{__version__}", file=sys.stderr)
40
40
 
41
41
  logger = get_logger(name="mcp_server")
@@ -354,10 +354,10 @@ _RUNNERS_ACTIONS: dict[str, Any] = {
354
354
  "register": "register_new_runner",
355
355
  "delete": "delete_runner",
356
356
  "verify_auth": "verify_runner_authentication",
357
- "reset_gitlab_token": "reset_gitlab_runner_token",
358
- "reset_project_token": "reset_project_runner_token",
359
- "reset_group_token": "reset_group_runner_token",
360
- "reset_token": "reset_token",
357
+ "reset_gitlab_token": "reset_gitlab_runner_token", # nosec B105 - an action name, not a credential
358
+ "reset_project_token": "reset_project_runner_token", # nosec B105 - an action name, not a credential
359
+ "reset_group_token": "reset_group_runner_token", # nosec B105 - an action name, not a credential
360
+ "reset_token": "reset_token", # nosec B105 - an action name, not a credential
361
361
  }
362
362
 
363
363
  #: Action -> Api method for the ``gitlab_tags`` tool.
@@ -0,0 +1,19 @@
1
+ {
2
+ "connector": "gitlab-api",
3
+ "sources": [
4
+ {
5
+ "source_id": "projects",
6
+ "record_type": "Project",
7
+ "tools": [
8
+ "gitlab_get_projects"
9
+ ],
10
+ "records_path": "data",
11
+ "id_field": "id",
12
+ "title_field": "path_with_namespace",
13
+ "text_field": "description",
14
+ "cursor_field": "last_activity_at",
15
+ "incremental": true,
16
+ "description": "GitLab projects visible to the configured token."
17
+ }
18
+ ]
19
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlab-api
3
- Version: 27.3.0
3
+ Version: 27.3.2
4
4
  Summary: GitLab API + MCP Server
5
5
  Author-email: Project Maintainers <maintainers@users.noreply.github.com>
6
6
  License: MIT
@@ -12,11 +12,11 @@ Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.12
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.0
15
+ Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.5
16
16
  Requires-Dist: python-dotenv>=1.2.4
17
17
  Requires-Dist: gql[requests]>=4.4.0
18
18
  Provides-Extra: mcp
19
- Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.0; extra == "mcp"
19
+ Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.5; extra == "mcp"
20
20
  Provides-Extra: gql
21
21
  Requires-Dist: gql[requests]>=4.4.0; extra == "gql"
22
22
  Provides-Extra: all
@@ -50,7 +50,7 @@ Dynamic: license-file
50
50
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/gitlab-api)
51
51
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/gitlab-api)
52
52
 
53
- *Version: 27.3.0*
53
+ *Version: 27.3.2*
54
54
 
55
55
  > **Documentation** — Installation, deployment, usage across the API, CLI, and MCP
56
56
  > backing GitLab instance are maintained in the
@@ -2,14 +2,15 @@ gitlab_api/__init__.py,sha256=AL1xZwPaU1Gzu53VrWYsDptCebPdfucmt3A6yStoPkc,2811
2
2
  gitlab_api/__main__.py,sha256=MlKO_lzO6h18UmnI-D20s27-Ej94gQn8Pa0EqOlwFOM,90
3
3
  gitlab_api/api_client.py,sha256=Id5iu7ID3fjc-sIAu3QhKyGCUCPy_uXxfZi5VxDkmUc,1087
4
4
  gitlab_api/auth.py,sha256=OOY6o1XWDG-ysfgUuOFb_W1bIb9Ub-734ezGVkSirJw,8919
5
- gitlab_api/connector_prep.py,sha256=9ZZv5-URrGEcgPvHYjXPIqO5x2zMdUa_ijZC6z3zqD8,59658
6
- gitlab_api/gitlab_gql.py,sha256=86Pd_JjlbMBu58gjzj_wsErXRIyikiLWVG-YvaJl_WI,105610
5
+ gitlab_api/connector_manifest.yml,sha256=mUypgulRxSm7w64CBrvpTg9cxm1f-2WclOLZJaFa8TA,13588
6
+ gitlab_api/connector_prep.py,sha256=f_awZY5i9rvTLNde9iqVA1XLs_iqt5UmK4yLXPSO0Cw,60174
7
+ gitlab_api/gitlab_gql.py,sha256=nBEU7K1RzLmZeZ1qD1U0-9NcRBwMu0xt_I0wDfRkGw8,106270
7
8
  gitlab_api/gitlab_input_models.py,sha256=35_ySA8E2GEbuGEjBtFeyiWiuqJMMEp3D2vRELiKwwE,115437
8
9
  gitlab_api/gitlab_response_models.py,sha256=m0jU8Xtwbq2y0AogCptVCslWlvi6SztLMDQZLSkefs8,150449
9
10
  gitlab_api/instances.py,sha256=RW7aKRvHWMQAQ5ZRRcjpKgpPgq_5YoncXt-_rC4ReXk,5423
10
11
  gitlab_api/kg_ingest.py,sha256=Verv5y0OtMYDXmzMoUD5EDUC8PZBiVfTddU5nND38b0,10750
11
12
  gitlab_api/mcp_config.json,sha256=2OOXrwO1sDLyHQqpZwhvDHizPIe3by6YmK4KFE333gI,23
12
- gitlab_api/mcp_server.py,sha256=aSVb3p2glE_Q4IbsDmLVbBKYuDNK4ggI10PapEJ_xzs,51654
13
+ gitlab_api/mcp_server.py,sha256=0Cq9QStZ_YDvciiSNArNJPwALsNWUQbdcE6ffSKRvE8,51850
13
14
  gitlab_api/api/__init__.py,sha256=MQblcZQoMdWyzpfnlAOEmyVlP7pbNcz_BQgAs5JINOI,27
14
15
  gitlab_api/api/api_client_base.py,sha256=0jdaCaV0tCxbNop6mdtq4nXgbrxEXu-ENoOxVmlaORE,8670
15
16
  gitlab_api/api/api_client_environments.py,sha256=2OQ88aStucaKO-VNc6NbXJxQdB761h0aJnPcVaHwBxo,26403
@@ -28,6 +29,7 @@ gitlab_api/connectors/tool_schema_fingerprints.json,sha256=Xnko-DbeAdxlLCbv89u9v
28
29
  gitlab_api/ontology/__init__.py,sha256=508Z6ZA4AHEfTFVfTLsenl0Lj_3sfI-MZG8icDzlOSo,568
29
30
  gitlab_api/ontology/certification.json,sha256=1Gfxi5Nu-EdYhtsHJYaHW2dqblLhclfVCzaWgM5Cs4w,1650
30
31
  gitlab_api/ontology/gitlab.ttl,sha256=qJvCWigVMhMF80HpMwegHGC3cG5pAtC8jBESD4BchXU,8028
32
+ gitlab_api/ontology/ingest_sources.json,sha256=ss1dw8bXIQ4NJLlfji1mZhKTK9zsXxUwUR4zCu3BsPk,457
31
33
  gitlab_api/ontology/fixtures/records.json,sha256=XwQXggBTlBkz8Vs_RZdQdT9OjjTPHnhT_QMMEUM1a0A,553
32
34
  gitlab_api/ontology/mappings/source.yaml,sha256=FnIGtTh_fKl84C4PFRZPj-QfBrQTtZHQAIwp6fCpXn8,545
33
35
  gitlab_api/ontology/migrations/manifest.json,sha256=RCw8U98LQshm7vclWxiJuK2m-fnFElytZda1DcY2Kj4,116
@@ -48,32 +50,34 @@ gitlab_api/skills/gitlab-pipelines/SKILL.md,sha256=OuEk9sRMjJQQzCE2eZtRq9-wgtWRO
48
50
  gitlab_api/skills/gitlab-pipelines/WORKFLOW.md,sha256=6C61qo-byMs-7bvry3NKCtyzdRWOEjhC-mOG0ZJvsL0,4314
49
51
  gitlab_api/skills/gitlab-vulnerabilities/SKILL.md,sha256=acBODboYBGccTtuCAv5fv5cY_gEXedMSmepur3q9f9Q,5212
50
52
  gitlab_api/skills/gitlab-vulnerabilities/WORKFLOW.md,sha256=OKnEKILIrhsnnNQTu0MLSQAWR6jL2w6PuX6ztu0Nhdk,5041
51
- gitlab_api-27.3.0.dist-info/licenses/LICENSE,sha256=9eAPQLU4cIYQNvAIt6PNoiVQozaVoPRhMV_N7_UHG7A,1068
53
+ gitlab_api-27.3.2.dist-info/licenses/LICENSE,sha256=9eAPQLU4cIYQNvAIt6PNoiVQozaVoPRhMV_N7_UHG7A,1068
52
54
  scripts/check_import_safety.py,sha256=IjaMe7nWwK261tNPO8vmLdDy-skX1mpMACQKJhvp0ik,13503
53
55
  scripts/security_contract.py,sha256=3517zbvFsLEaWhZOiBwjb9lQiNvH8C-8HzlK4i4SBF8,25137
54
- scripts/security_sanitizer.py,sha256=gE65xtYMGx43oYUnK0kozBm73Hx97mCdrQUM7GGzIbk,9449
55
- scripts/verify_api_integration.py,sha256=3AK8HKW52o1yxCDaEUtXngnh7CjYw6NiG9Eb3EIoXNE,10256
56
+ scripts/security_sanitizer.py,sha256=W1LGgsq_uksvJ-oqwHIIcE4szNCCDAWA_PheBYSPnXg,9813
57
+ scripts/verify_api_integration.py,sha256=hblaw-oAf8zas-t-OGgm6XOF-wfSHZihFw7uLO3YzfA,10347
56
58
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
59
  tests/conftest.py,sha256=srq80Qcj_VFRxMFCYZ6pXtEDfXO1PzVY-HEI4kmm29s,2090
58
60
  tests/test_api_wrapper.py,sha256=jUXDVR4lKOZLTvE3vAS8Xjkc4q57smd6MJr7u_eBU9U,5717
59
- tests/test_auth.py,sha256=4Fn0JFvGW5wxUu3-gJQEZO0agLrtOw8LEQ6HH5uYKLk,4420
60
- tests/test_connector_prep.py,sha256=m4cGbZlfmZLcai7ShUiiip5a-QvNVy8WQ-P9jaVcAFM,10714
61
+ tests/test_auth.py,sha256=dpiDD9Z5SfN6re59tb22PqkbsY8A8pWOBX9KuoXoTTc,4404
62
+ tests/test_connector_prep.py,sha256=YgPoTq0nFqsZ4HA1Iq7F_jJlKxscePvgQJREZB3bczk,10770
61
63
  tests/test_gitlab_a2a_validation.py,sha256=Wxu7zSIxnmT5bOg-BwFvR6z9TprSzObG5mIEC4NBwwM,5130
62
64
  tests/test_gitlab_api_brute_force_coverage.py,sha256=yS3LoPE6JCC2jcI2agYmNI8kpSBK9jAZsmGnrYq-mPU,14294
63
65
  tests/test_gitlab_gql.py,sha256=UVMGoYCkaB3EjyGouADp98AYzkyRv7iR-NweHhMrwtQ,11198
64
66
  tests/test_gitlab_mcp_validation.py,sha256=Ma2e3ZchIVUHF0OJPPAW1DRYEG6X-I6dUFRpLE31Rwk,2423
65
67
  tests/test_gitlab_models.py,sha256=9buJwE0VmioDXRcKlCfcKYgg6PdQnx7HvDRBPnMC3Ak,542220
66
68
  tests/test_identity_context_scoping.py,sha256=wY_xb37E7qd91F5NNhf0g2Hv1uRyrM0wnCO_6WEEcW4,2269
69
+ tests/test_ingest_sources.py,sha256=XCPTezZIdauzQdrIR9P5KiXG9ZuB1YfvIXGVuAo6Hks,1347
67
70
  tests/test_init_dynamics.py,sha256=fsfRhuT6yCY91JXoHd7gbHfuPjZfLW0A7aJgyXptL6E,1693
68
71
  tests/test_instances.py,sha256=C8csQIJPv1Wdbh4Le2JBg1pM7iFo5rYfL-vp_aGwOtA,4167
69
72
  tests/test_kg_ingest.py,sha256=sgo6KNYKs7xbvbmKZDOXpHDhb3YkXvxjWsIELpxcAGI,7633
70
73
  tests/test_mock_coverage.py,sha256=yO5A8ZP3UxTx1fO-T15p2Dy74EZaXRHgUyBXfhqN96k,11053
74
+ tests/test_security_sanitizer.py,sha256=l7huytJgfHmS8fmTiNPkX85eRrcRrnqu3fOJYKugD4E,1070
71
75
  tests/test_startup.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
76
  tests/test_tool_modes.py,sha256=aiAaQE0yXp9SKGz9BVgik4HIjE5kar8FBD9uVOK5pwA,721
73
77
  tests/test_verify_agent.py,sha256=XdW2NtR5643rgjDYLu6RajWu-4wqrZjCUsRehfsKCqQ,6465
74
78
  tests/verify_a2a_queries.py,sha256=JMKBexwzo6c59NpVrBfYgW4HnmSG8-qqfdeku5YFtwc,3613
75
- gitlab_api-27.3.0.dist-info/METADATA,sha256=3i4OzlUaSNYLDO6R_3mdL5fAMCbRiCLj5uUrodvjAew,40527
76
- gitlab_api-27.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
77
- gitlab_api-27.3.0.dist-info/entry_points.txt,sha256=-FUZqPa8FPHty3mGFhNlFRajrMh6fgSgV8fcxAI1KTA,366
78
- gitlab_api-27.3.0.dist-info/top_level.txt,sha256=fJ-T_zkuttbq8Kn1dq0mBvyjtKqPdMic01oCdbzxh_k,48
79
- gitlab_api-27.3.0.dist-info/RECORD,,
79
+ gitlab_api-27.3.2.dist-info/METADATA,sha256=umVBzzDghYU6_Ey2fpEuH4PPnDm1BiMgxKBcNXZ_cUs,40527
80
+ gitlab_api-27.3.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
81
+ gitlab_api-27.3.2.dist-info/entry_points.txt,sha256=-FUZqPa8FPHty3mGFhNlFRajrMh6fgSgV8fcxAI1KTA,366
82
+ gitlab_api-27.3.2.dist-info/top_level.txt,sha256=fJ-T_zkuttbq8Kn1dq0mBvyjtKqPdMic01oCdbzxh_k,48
83
+ gitlab_api-27.3.2.dist-info/RECORD,,
@@ -232,7 +232,15 @@ def _should_secret_scan(file_path: Path) -> bool:
232
232
  return True
233
233
 
234
234
 
235
+ #: The fleet's one inline exemption: ``# sanitizer:ignore - <reason>`` on the
236
+ #: exact line of a reviewed synthetic literal. A bare marker without a reason
237
+ #: exempts nothing; there is no file-, path- or value-level exemption.
238
+ SANITIZER_IGNORE_RE = re.compile(r"#\s*sanitizer:ignore\s*[-\u2014:]\s*\S")
239
+
240
+
235
241
  def _secret_violations_in_line(line: str, rel_path: Path, idx: int):
242
+ if SANITIZER_IGNORE_RE.search(line):
243
+ return []
236
244
  violations = []
237
245
  for label, pattern in SECRET_PATTERNS:
238
246
  for match in pattern.findall(line):
@@ -3,6 +3,7 @@ import os
3
3
  import ast
4
4
  import glob
5
5
  import sys
6
+ from typing import TypeGuard
6
7
 
7
8
  BASELINES = {
8
9
  "adguard-home-agent": 89.2,
@@ -35,7 +36,9 @@ def _is_api_client_class(node: ast.ClassDef) -> bool:
35
36
  return "api" in class_name or "client" in class_name or node.name == "Api"
36
37
 
37
38
 
38
- def _is_public_client_method(item) -> bool:
39
+ def _is_public_client_method(
40
+ item: ast.stmt,
41
+ ) -> TypeGuard[ast.FunctionDef | ast.AsyncFunctionDef]:
39
42
  return (
40
43
  isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef))
41
44
  and not item.name.startswith("_")
tests/test_auth.py CHANGED
@@ -44,7 +44,7 @@ def test_get_client_oidc_delegation(monkeypatch):
44
44
  monkeypatch.setattr(
45
45
  delegation,
46
46
  "exchange_token",
47
- lambda settings, *, subject_token, http_client, resolver=None: AccessToken(
47
+ lambda settings, *, subject_token, http_client, **_unused: AccessToken(
48
48
  value="delegated_tok", ttl_seconds=3600, expires_at=0.0
49
49
  ),
50
50
  )
@@ -63,7 +63,7 @@ def test_get_client_oidc_delegation_failed(monkeypatch):
63
63
 
64
64
  monkeypatch.setattr(delegation, "current_user_token", lambda: "caller-token")
65
65
 
66
- def _boom(settings, *, subject_token, http_client, resolver=None):
66
+ def _boom(settings, *, subject_token, http_client, **_unused):
67
67
  raise ValueError("Exchange failed")
68
68
 
69
69
  monkeypatch.setattr(delegation, "exchange_token", _boom)
@@ -98,7 +98,7 @@ def test_get_graphql_client_oidc_delegation(monkeypatch):
98
98
  monkeypatch.setattr(
99
99
  delegation,
100
100
  "exchange_token",
101
- lambda settings, *, subject_token, http_client, resolver=None: AccessToken(
101
+ lambda settings, *, subject_token, http_client, **_unused: AccessToken(
102
102
  value="delegated_tok", ttl_seconds=3600, expires_at=0.0
103
103
  ),
104
104
  )
@@ -116,7 +116,7 @@ def test_get_graphql_client_oidc_delegation_failed(monkeypatch):
116
116
 
117
117
  monkeypatch.setattr(delegation, "current_user_token", lambda: "caller-token")
118
118
 
119
- def _boom(settings, *, subject_token, http_client, resolver=None):
119
+ def _boom(settings, *, subject_token, http_client, **_unused):
120
120
  raise ValueError("Exchange failed")
121
121
 
122
122
  monkeypatch.setattr(delegation, "exchange_token", _boom)
@@ -217,7 +217,7 @@ def test_malformed_records_are_quarantined_without_commit(
217
217
 
218
218
  def test_secret_payload_is_redacted_from_evidence(context: PrepContext) -> None:
219
219
  prep, recorder = _prep()
220
- secret = "glpat-should-never-be-retained"
220
+ secret = "glpat-should-never-be-retained" # sanitizer:ignore - synthetic token proving redaction
221
221
  result = prep.process_page(
222
222
  "project",
223
223
  [{**_project(), "private_token": secret}],
@@ -0,0 +1,35 @@
1
+ """The packaged ingestible-source declaration (CONN-GITLAB-API-R001)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+ import yaml
9
+ from agent_connector_sdk.ingest.sources import load_ingest_sources
10
+ from agent_connector_sdk.mcp.method_tools import operation_annotations, operation_verb
11
+ from agent_connector_sdk.mcp.tool_surface import operation_prefix
12
+
13
+ import gitlab_api
14
+ from gitlab_api.api_client import Api
15
+
16
+
17
+ @pytest.mark.spec("CONN-GITLAB-API-R001")
18
+ def test_declares_ingestible_sources() -> None:
19
+ declaration = load_ingest_sources(Path(gitlab_api.__file__).parent)
20
+ assert declaration is not None
21
+ assert declaration.connector == "gitlab-api"
22
+ source = declaration.source("projects")
23
+ assert source is not None and source.record_type == "Project"
24
+ prefix = operation_prefix("gitlab-api")
25
+ assert source.tools == (f"{prefix}_get_projects",)
26
+ # The engine admits a record only under the pack mapping of its type.
27
+ manifest = yaml.safe_load(
28
+ (Path(gitlab_api.__file__).parent / "connector_manifest.yml").read_text(
29
+ "utf-8"
30
+ )
31
+ )
32
+ assert source.record_type in manifest["schema_mappings"]
33
+ method = getattr(Api, "get_projects")
34
+ annotations = operation_annotations("get_projects", operation_verb(method))
35
+ assert annotations.read_only_hint is True
@@ -0,0 +1,30 @@
1
+ """The sanitizer's inline exemption marker is narrow (one justified line)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ from pathlib import Path
7
+
8
+ _SPEC = importlib.util.spec_from_file_location(
9
+ "security_sanitizer", Path(__file__).parents[1] / "scripts" / "security_sanitizer.py"
10
+ )
11
+ assert _SPEC is not None and _SPEC.loader is not None
12
+ sanitizer = importlib.util.module_from_spec(_SPEC)
13
+ _SPEC.loader.exec_module(sanitizer)
14
+
15
+ TOKEN = "glpat-" + "abcdefghijklmnopqrst" # sanitizer:ignore - synthetic test token
16
+
17
+
18
+ def test_unmarked_secret_is_reported() -> None:
19
+ line = f'token = "{TOKEN}"'
20
+ assert sanitizer._secret_violations_in_line(line, Path("x.py"), 1)
21
+
22
+
23
+ def test_justified_marker_exempts_only_its_line() -> None:
24
+ marked = f'token = "{TOKEN}" # sanitizer:ignore - synthetic fixture'
25
+ assert sanitizer._secret_violations_in_line(marked, Path("x.py"), 1) == []
26
+
27
+
28
+ def test_bare_marker_exempts_nothing() -> None:
29
+ bare = f'token = "{TOKEN}" # sanitizer:ignore'
30
+ assert sanitizer._secret_violations_in_line(bare, Path("x.py"), 1)