specguard-mcp 0.1.11 → 0.1.13
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/README.md +120 -56
- package/dist/src/config.d.ts +100 -18
- package/dist/src/config.js +125 -5
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +7 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/support/specguard-api.d.ts +2 -2
- package/dist/src/support/specguard-api.js +11 -10
- package/dist/src/support/specguard-api.js.map +1 -1
- package/dist/src/tools/add-repository.js +3 -2
- package/dist/src/tools/add-repository.js.map +1 -1
- package/dist/src/tools/index.d.ts +4 -3
- package/dist/src/tools/index.js +4 -3
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/list-repositories.d.ts +75 -24
- package/dist/src/tools/list-repositories.js +180 -45
- package/dist/src/tools/list-repositories.js.map +1 -1
- package/dist/src/tools/list-repository-members.js +3 -2
- package/dist/src/tools/list-repository-members.js.map +1 -1
- package/dist/src/tools/near-duplicate-clusters.d.ts +20 -8
- package/dist/src/tools/near-duplicate-clusters.js +70 -23
- package/dist/src/tools/near-duplicate-clusters.js.map +1 -1
- package/dist/src/tools/registrable-repositories.d.ts +7 -2
- package/dist/src/tools/registrable-repositories.js +9 -4
- package/dist/src/tools/registrable-repositories.js.map +1 -1
- package/dist/src/tools/repository-overview.d.ts +41 -1
- package/dist/src/tools/repository-overview.js +107 -6
- package/dist/src/tools/repository-overview.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,8 +33,9 @@ refuses to boot and takes the tools that needed no configuration down with it.
|
|
|
33
33
|
| Variable | Needed by | Default | What it is |
|
|
34
34
|
| --- | --- | --- | --- |
|
|
35
35
|
| `SPECGUARD_ENDPOINT` | `get_repository_overview`, `list_repositories`, `add_repository`, `registrable_repositories` | — | your SpecGuard instance's root URL, **including the scheme** — e.g. `https://specguard.example.com`, or `http://localhost:3000`. A value with no scheme is refused by name (`SPECGUARD_ENDPOINT is not a usable URL: "sg.example.com"`) rather than surfacing later as an opaque failure. `SPECGUARD_URL` is accepted as an alias, and is the name every message uses when it is the one you set. A blank value counts as unset, so leaving `SPECGUARD_ENDPOINT` empty in a templated config falls through to `SPECGUARD_URL` instead of suppressing it |
|
|
36
|
-
| `SPECGUARD_API_KEY` | `get_repository_overview` | — | an agent/CI API key (`sgk_…`) issued by that deployment |
|
|
37
|
-
| `SPECGUARD_USER_API_KEY` | `list_repositories
|
|
36
|
+
| `SPECGUARD_API_KEY` | `get_repository_overview`, `near_duplicate_clusters` (default calls) | — | an agent/CI API key (`sgk_…`) issued by that deployment — a **per-repository** key, which is the single repository those tools answer about by default |
|
|
37
|
+
| `SPECGUARD_USER_API_KEY` | `list_repositories` (fallback), `add_repository`, `registrable_repositories`, `remove_repository`, `create_repository_api_key`, `revoke_repository_api_key`, `list_repository_agent_keys`, `revoke_repository_agent_key`, `list_repository_agent_keys_presented_revoked`, `list_repository_members`, `add_repository_member`, `update_repository_member_permissions`, `remove_repository_member`, `rename_repository` | — | a **user** API key (`sgu_…`), minted from that deployment's account page. A different credential from the one above, not a second place to put the same value: SpecGuard decides which of them a request may use from the token's prefix, before it reads anything, and answers `401` for the other one. Set whichever your tools need — both, if you use both |
|
|
38
|
+
| `SPECGUARD_AGENT_API_KEY` | `list_repositories` (preferred), `get_repository_overview` / `near_duplicate_clusters` **with** `repository` | — | an **agent** API key (`sga_…`), minted from that deployment's account page (Agent keys panel) with an explicit set of repositories and permissions. It speaks for nobody: its reach is exactly the set granted onto it, fixed at mint time, and every read is bounded by that set server-side. This is the credential to give an automated agent — one key, many repositories, none of a person's rights. When it and `SPECGUARD_USER_API_KEY` are both set, `list_repositories` uses **this** one, so discovery stays inside the set the other tools can reach |
|
|
38
39
|
| `SPECGUARD_LINT_COMMAND` | `lint_intent_annotations` | `specguard-lint` | the command that runs the linter. Most Ruby projects need `bundle exec specguard-lint` |
|
|
39
40
|
| `SPECGUARD_TIMEOUT_MS` | HTTP tools | `30000` | how long a call to SpecGuard may take |
|
|
40
41
|
|
|
@@ -56,6 +57,7 @@ Register it with your MCP client — for Claude Code:
|
|
|
56
57
|
"SPECGUARD_ENDPOINT": "https://specguard.example.com",
|
|
57
58
|
"SPECGUARD_API_KEY": "sgk_…",
|
|
58
59
|
"SPECGUARD_USER_API_KEY": "sgu_…",
|
|
60
|
+
"SPECGUARD_AGENT_API_KEY": "sga_…",
|
|
59
61
|
"SPECGUARD_LINT_COMMAND": "bundle exec specguard-lint"
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -101,8 +103,27 @@ history, and the branches that have runs. Pass `branch` for two more: which test
|
|
|
101
103
|
rather than consistently (the cross-run flakiness ranking) and how the areas moved across the whole
|
|
102
104
|
branch window rather than between the last two runs.
|
|
103
105
|
|
|
106
|
+
Everything above is answered about **one repository**, and which one has two spellings. By default
|
|
107
|
+
it is the repository the configured `sgk_…` key resolves to — the key *is* the subject. Pass
|
|
108
|
+
`repository` (the numeric id `list_repositories` reports) and the call goes to the **plural**
|
|
109
|
+
endpoint, `GET /api/v1/repositories/:id`, under the **agent key** (`SPECGUARD_AGENT_API_KEY`,
|
|
110
|
+
`sga_…`): same overview body, same ladder, every parameter below honoured identically — with one
|
|
111
|
+
deliberate omission this path owns: `api_key` is **absent** from the plural body rather than nulled,
|
|
112
|
+
because that block describes the credential that made the request and this request was not made
|
|
113
|
+
with a repository key (an absent key there is the surface's shape, never a dropped block). Other
|
|
114
|
+
than that, only the subject moves. That is what makes the *other* repositories visible: an `sgk_`
|
|
115
|
+
key is one repository
|
|
116
|
+
by design, so an agent holding only that could never ask about a second one, while the agent key's
|
|
117
|
+
own granted repository set is exactly the boundary the plural endpoint looks ids up inside (a
|
|
118
|
+
repository outside the set answers 404, indistinguishable from one that does not exist —
|
|
119
|
+
`list_repositories` under the same key lists the set that is reachable). SpecGuard refuses each
|
|
120
|
+
credential kind in the other's place before it reads anything, so the pairing is not mixable: the
|
|
121
|
+
default call wants the `sgk_` key, the `repository` call wants the agent key, and a call made with
|
|
122
|
+
the wrong one is refused *here*, naming the variable to set, before any request is made.
|
|
123
|
+
|
|
104
124
|
| argument | |
|
|
105
125
|
| --- | --- |
|
|
126
|
+
| `repository` | ask about THIS repository (its numeric id from `list_repositories`) under the **agent key**, instead of the one the `sgk_…` key resolves to — omit it for the default, `sgk_`-keyed call |
|
|
106
127
|
| `branch` | narrow the run **history** to one branch, for a real growth series — and unlock `unstable_tests` and `directory_growth`, which read the same window |
|
|
107
128
|
| `spec_directory` | open ONE of the heaviest directories and list the spec files inside it |
|
|
108
129
|
| `spec_file` | open ONE of the heaviest spec files and list the individual examples inside it |
|
|
@@ -326,33 +347,50 @@ because a zero would read as a measurement that was taken.
|
|
|
326
347
|
|
|
327
348
|
### `list_repositories`
|
|
328
349
|
|
|
329
|
-
Lists the SpecGuard repositories
|
|
330
|
-
|
|
331
|
-
|
|
350
|
+
Lists the SpecGuard repositories this server's key may open — *what can I ask about*, which is the
|
|
351
|
+
one question no other tool here can answer. `get_repository_overview` without `repository` takes no
|
|
352
|
+
repository because its `sgk_…` key **is** the repository, so without this an agent can only report
|
|
332
353
|
on a repository somebody already named for it.
|
|
333
354
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
355
|
+
The credential decides which repositories are *in* the answer — a person's own-plus-shared set
|
|
356
|
+
under an `sgu_…` key, the agent key's mint-time granted set under an `sga_…` one; the three
|
|
357
|
+
optional asks below narrow **within** whichever set answered — never around it. The endpoint has
|
|
358
|
+
served them since SpecGuard's SPGD-940 (`ef6236d`), composed through the same `RepositoryNarrowing`
|
|
359
|
+
concern the web grid reads, and this tool forwards them rather than re-deriving them. A repository
|
|
360
|
+
the credential does not admit never enters the response, so no ask can filter it *in* either. A
|
|
361
|
+
blank value is no ask: passing an empty string and omitting the argument make the identical request.
|
|
362
|
+
|
|
363
|
+
| argument | |
|
|
364
|
+
| --- | --- |
|
|
365
|
+
| `q` | keep only repositories whose `full_name` (`org/repo`) contains this substring, case-insensitively. A plain substring, not a pattern — the LIKE wildcards `%`/`_` are escaped server-side, so `org/my_repo` matches itself. A match-less ask is an empty list, not an error |
|
|
366
|
+
| `role` | `"owned"` or `"shared"` — one half of the list's mix: the repositories this person owns, or the ones shared with them. Mind the spelling: the *ask* values are `owned`/`shared`, while each entry's `role` field reads `owner`/`member` — `role: "owner"` is not a valid ask and settles to no ask (full list, no error). Under the `sga_…` agent key the ask settles to no ask for every value: ownership is a person fact and the key speaks for nobody, so the full granted list is served |
|
|
367
|
+
| `sort` | `"stale"` — re-order stalest-first: repositories CI has never ingested a run for first, then least-recently-ingested, with `full_name` breaking ties so two calls agree element for element. The same entries, a different order; the default order stays `full_name` ascending |
|
|
339
368
|
|
|
340
369
|
The body comes back as SpecGuard serves it — `{"repositories": […]}`, each entry carrying `id`,
|
|
341
|
-
`full_name`, `name`, `registered_at` and `role`, ordered by `full_name` ascending
|
|
370
|
+
`full_name`, `name`, `registered_at` and `role`, ordered by `full_name` ascending unless `sort`
|
|
371
|
+
asks otherwise. The first four are
|
|
342
372
|
deliberately the same four fields, under the same names, that `get_repository_overview` serves in its
|
|
343
|
-
own `repository` block, so a client that reads one reads the other. `role`
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
373
|
+
own `repository` block, so a client that reads one reads the other. `role` has one value per
|
|
374
|
+
credential kind: under a **user** key (`sgu_…`) it is `owner` or `member` — the list mixes
|
|
375
|
+
repositories this person owns with repositories somebody shared with them, and nothing else tells
|
|
376
|
+
them apart — while under an **agent** key (`sga_…`) every entry is `agent`, the value that says the
|
|
377
|
+
ownership question does not apply because the key speaks for nobody (branching on owner/member
|
|
378
|
+
correctly reads false for both). Read it before assuming a repository is one you may administer. An
|
|
379
|
+
empty list means no access, not an error.
|
|
380
|
+
|
|
381
|
+
**It reads a different key from `get_repository_overview` — either of two, whichever is set.**
|
|
382
|
+
`SPECGUARD_AGENT_API_KEY` (`sga_…`) when it is set: the answer is then the repository set granted
|
|
383
|
+
onto that key at mint time, the same set every other agent-keyed call answers inside, so discovery
|
|
384
|
+
and reach always agree. `SPECGUARD_USER_API_KEY` (`sgu_…`) when no agent key is set: the answer is
|
|
385
|
+
what that person may open. With both set the **agent** key wins. Neither is ever a stand-in for
|
|
386
|
+
`SPECGUARD_API_KEY` (`sgk_…`) — SpecGuard refuses each credential kind in the other's place, the
|
|
387
|
+
prefix deciding which table is consulted before any of them is read — and every message this tool
|
|
388
|
+
produces names the variable *it* read, so a `401` here never sends you to check a key this tool
|
|
389
|
+
never used.
|
|
390
|
+
|
|
391
|
+
Registering a repository is `add_repository`, below — it reads `SPECGUARD_USER_API_KEY` (an
|
|
392
|
+
`sgu_…` key, which this tool also accepts but does not require) and takes the `full_name` this
|
|
393
|
+
tool reports. Removal and the key lifecycle (`remove_repository`,
|
|
356
394
|
`create_repository_api_key`, `revoke_repository_api_key`) are below too, on the same key; a tool
|
|
357
395
|
here is a promise the agent will act on, so each waits for the capability rather than the other way
|
|
358
396
|
round.
|
|
@@ -396,8 +434,9 @@ non-blank string; SpecGuard validates the name and refuses an unusable one in it
|
|
|
396
434
|
format rule on this side would be free to drift from the one that actually decides, and would surface
|
|
397
435
|
as this bridge rejecting a name the platform would have accepted.
|
|
398
436
|
|
|
399
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
400
|
-
different one from the `sgk_…` key `get_repository_overview`
|
|
437
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — a **person** key, which `list_repositories` also
|
|
438
|
+
accepts but does not require — and a different one from the `sgk_…` key `get_repository_overview`
|
|
439
|
+
uses.
|
|
401
440
|
|
|
402
441
|
### `registrable_repositories`
|
|
403
442
|
|
|
@@ -431,8 +470,9 @@ was one (first-time setup), a populated grant with `stale: true` means an existi
|
|
|
431
470
|
parameters; which repositories are in the answer is decided by SpecGuard from the person the key
|
|
432
471
|
speaks for.
|
|
433
472
|
|
|
434
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
435
|
-
|
|
473
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
474
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
475
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
436
476
|
|
|
437
477
|
### `near_duplicate_clusters`
|
|
438
478
|
|
|
@@ -448,10 +488,20 @@ and answers `near_duplicates: null` on the plain overview. Calling this tool **i
|
|
|
448
488
|
`get_repository_overview` never sends it, so an agent reading the overview cannot pay the census by
|
|
449
489
|
accident.
|
|
450
490
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
491
|
+
Nothing about the **census** is choosable — the clusters are the repository's, computed over
|
|
492
|
+
every run; one call returns them all. (The server reads only that the `near_duplicates` key is
|
|
493
|
+
*present* — `=false` would open it too — so there is no value for the ask to carry.) Which
|
|
494
|
+
**repository** is censused is the one choice there is:
|
|
495
|
+
|
|
496
|
+
| argument | |
|
|
497
|
+
| --- | --- |
|
|
498
|
+
| `repository` | census THIS repository (its numeric id from `list_repositories`) under the **agent key** (`SPECGUARD_AGENT_API_KEY`, `sga_…`), instead of the one the `sgk_…` key resolves to — omit it for the default, `sgk_`-keyed census |
|
|
499
|
+
|
|
500
|
+
The credential changes with `repository`, exactly as on `get_repository_overview`: the plural
|
|
501
|
+
endpoint answers under the agent key, whose mint-time granted repository set is the boundary the id
|
|
502
|
+
is looked up inside, and the two credential kinds refuse each other's tokens — so a
|
|
503
|
+
`repository` ask without the agent key is refused *here*, naming the variable, before any request
|
|
504
|
+
is made.
|
|
455
505
|
|
|
456
506
|
Read the response with its own rules in mind:
|
|
457
507
|
|
|
@@ -471,9 +521,13 @@ Read the response with its own rules in mind:
|
|
|
471
521
|
three silences — nothing ingested, nothing embedded, nothing alike — are kept distinguishable by
|
|
472
522
|
`recorded_count` / `identity_count` / the list itself.
|
|
473
523
|
|
|
474
|
-
|
|
475
|
-
`GET /api/v1/repository`); the response is that
|
|
476
|
-
block opened, passed through unmodified.
|
|
524
|
+
Without `repository`, same credential and endpoint as `get_repository_overview`'s default
|
|
525
|
+
(`sgk_…` repository key on `GET /api/v1/repository`); the response is that body with the
|
|
526
|
+
`near_duplicates` block opened, passed through unmodified. With `repository`, the plural endpoint
|
|
527
|
+
answers under the agent key, exactly as on `get_repository_overview` — and carries that path's
|
|
528
|
+
one deliberate omission: `api_key` is **absent** from the plural body rather than nulled, because
|
|
529
|
+
the block describes the credential that made the request and this request was not made with a
|
|
530
|
+
repository key (an absent key there is the surface's shape, never a dropped block).
|
|
477
531
|
|
|
478
532
|
### `remove_repository`
|
|
479
533
|
|
|
@@ -489,8 +543,9 @@ Authorization is the `repo.delete` capability at **either surface** — an owner
|
|
|
489
543
|
`repo.delete`, may remove the repository. A member without it is refused `403` with SpecGuard's own
|
|
490
544
|
sentence, verbatim. The repository's CI keys stop authenticating the moment it succeeds.
|
|
491
545
|
|
|
492
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
493
|
-
|
|
546
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
547
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
548
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
494
549
|
|
|
495
550
|
### `create_repository_api_key`
|
|
496
551
|
|
|
@@ -514,8 +569,9 @@ The body comes back as SpecGuard serves it: an `api_key` block (`name`, `token`,
|
|
|
514
569
|
Authorization is the `keys_manage` capability; a member without it is refused `403` with SpecGuard's
|
|
515
570
|
own sentence, verbatim.
|
|
516
571
|
|
|
517
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
518
|
-
|
|
572
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
573
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
574
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
519
575
|
|
|
520
576
|
### `revoke_repository_api_key`
|
|
521
577
|
|
|
@@ -536,8 +592,9 @@ replacement with `create_repository_api_key` and deploy it BEFORE revoking the o
|
|
|
536
592
|
first and the repository's CI is locked out until a human mints a new key in a browser. A `204`
|
|
537
593
|
means the key is revoked.
|
|
538
594
|
|
|
539
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
540
|
-
|
|
595
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
596
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
597
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
541
598
|
|
|
542
599
|
### `list_repository_agent_keys`
|
|
543
600
|
|
|
@@ -634,8 +691,9 @@ Authorization is the `members.manage` capability: a caller who is not a member i
|
|
|
634
691
|
(the repository's existence stays hidden), and a member without `members.manage` is refused `403`
|
|
635
692
|
with SpecGuard's own sentence, verbatim.
|
|
636
693
|
|
|
637
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
638
|
-
|
|
694
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
695
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
696
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
639
697
|
|
|
640
698
|
### `add_repository_member`
|
|
641
699
|
|
|
@@ -659,8 +717,9 @@ On success (`201`) the response carries a `member` block whose **`id` is the mem
|
|
|
659
717
|
`members.manage` capability; a member without it is refused `403` with SpecGuard's own sentence,
|
|
660
718
|
verbatim.
|
|
661
719
|
|
|
662
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
663
|
-
|
|
720
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
721
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
722
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
664
723
|
|
|
665
724
|
### `update_repository_member_permissions`
|
|
666
725
|
|
|
@@ -684,8 +743,9 @@ ids are scoped to the repository (a foreign id is refused `404`). Authorization
|
|
|
684
743
|
`members.manage` capability; a member without it is refused `403` with SpecGuard's own sentence,
|
|
685
744
|
verbatim.
|
|
686
745
|
|
|
687
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
688
|
-
|
|
746
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
747
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
748
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
689
749
|
|
|
690
750
|
### `remove_repository_member`
|
|
691
751
|
|
|
@@ -707,8 +767,9 @@ number on the wire, so pass it as a string, not as a number (see the edit tool a
|
|
|
707
767
|
Authorization is the `members.manage` capability; a member without it is refused `403` with
|
|
708
768
|
SpecGuard's own sentence, verbatim.
|
|
709
769
|
|
|
710
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
711
|
-
|
|
770
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
771
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
772
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
712
773
|
|
|
713
774
|
### `rename_repository`
|
|
714
775
|
|
|
@@ -728,8 +789,9 @@ the repository but may not rename it. The owner check also redeems a browser-iss
|
|
|
728
789
|
(re-grant via the browser). A name another repository already holds is refused `400` — `taken`, not
|
|
729
790
|
`409` — verbatim. The `200` body is `{repository: …}` in the same shape `list_repositories` serves.
|
|
730
791
|
|
|
731
|
-
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`)
|
|
732
|
-
|
|
792
|
+
It reads `SPECGUARD_USER_API_KEY` (`sgu_…`) — the same **person** key `add_repository` reads, and
|
|
793
|
+
a different one from the `sgk_…` key `get_repository_overview` uses; `list_repositories` also
|
|
794
|
+
accepts that key but does not require it (its agent key wins when both are set).
|
|
733
795
|
|
|
734
796
|
## How it works
|
|
735
797
|
|
|
@@ -744,11 +806,13 @@ reshapes no response — every tool returns the shape of the capability it wraps
|
|
|
744
806
|
upstream reaches the agent without a release here.
|
|
745
807
|
|
|
746
808
|
Authorization and project scoping are enforced by SpecGuard, never by this bridge, using keys you
|
|
747
|
-
issue there — the same `sgk_…` keys CI uses to ingest runs,
|
|
748
|
-
person rather than to a repository, an `
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
809
|
+
issue there — the same `sgk_…` keys CI uses to ingest runs, an `sgu_…` user key for the tools that
|
|
810
|
+
answer to a person rather than to a repository, and an `sga_…` agent key for the tools that answer
|
|
811
|
+
for an automated agent — it speaks for nobody, and its reach is the repository set granted onto it
|
|
812
|
+
at mint time. Which of the three a request may carry is SpecGuard's decision and it is taken from
|
|
813
|
+
the token's prefix, before any credential is looked up, so the bridge cannot widen any key's reach:
|
|
814
|
+
it forwards the key the tool's own variable holds and reports what came back. It adds no
|
|
815
|
+
credentials of its own and stores nothing.
|
|
752
816
|
|
|
753
817
|
No argument ever reaches a shell: subprocesses are spawned with an argument list, so a path from a
|
|
754
818
|
model is a path that does not exist rather than a command.
|
package/dist/src/config.d.ts
CHANGED
|
@@ -27,22 +27,26 @@
|
|
|
27
27
|
* `SPECGUARD_ENDPOINT` wins when both are set and disagree, because it is the
|
|
28
28
|
* one the rest of the toolchain is already reading.
|
|
29
29
|
*
|
|
30
|
-
* ==
|
|
31
|
-
* == other
|
|
30
|
+
* == THREE KEY VARIABLES, because SpecGuard has three credentials that refuse
|
|
31
|
+
* == each other
|
|
32
32
|
*
|
|
33
33
|
* `Api::BaseController` discriminates on the token's PREFIX *before any table is
|
|
34
34
|
* read*, and answers 401 on a mismatch without a lookup: `sgk_` names ONE
|
|
35
35
|
* repository (`GET /api/v1/repository`, `POST /api/v1/ingest`), `sgu_` names a
|
|
36
|
-
* PERSON (`GET /api/v1/repositories`)
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* PERSON (`GET /api/v1/repositories` and its writes), and — SPGD-952 — `sga_`
|
|
37
|
+
* names AN AGENT: nobody's account and no single repository, but an explicit
|
|
38
|
+
* set of repositories with a permission set, both fixed at mint time
|
|
39
|
+
* (`AgentApiKey::TOKEN_PREFIX` says all three prefixes are deliberately the same
|
|
40
|
+
* length so neither can be a prefix of another — the mutual refusal is designed,
|
|
41
|
+
* not incidental).
|
|
39
42
|
*
|
|
40
|
-
* One variable therefore cannot serve
|
|
41
|
-
*
|
|
42
|
-
* is required — an operator who only ever calls the
|
|
43
|
-
* `SPECGUARD_API_KEY`, exactly as before
|
|
44
|
-
* single variable was the alternative and it
|
|
45
|
-
*
|
|
43
|
+
* One variable therefore cannot serve two kinds, let alone three: whichever
|
|
44
|
+
* kind it holds, the tools needing another kind 401. So there are three, read
|
|
45
|
+
* independently, and none is required — an operator who only ever calls the
|
|
46
|
+
* repository tool sets only `SPECGUARD_API_KEY`, exactly as before any of this
|
|
47
|
+
* existed. Prefix-dispatching over a single variable was the alternative and it
|
|
48
|
+
* cannot work: an operator wanting several kinds of tool needs several keys
|
|
49
|
+
* present at once.
|
|
46
50
|
*
|
|
47
51
|
* Which variable a tool reads is then carried onto `ApiConfig` alongside the
|
|
48
52
|
* value — see `Credential` — for the same reason `endpointVariable` is: a
|
|
@@ -74,6 +78,18 @@ export interface Config {
|
|
|
74
78
|
* accept the other's token, so the two values live in two places here too.
|
|
75
79
|
*/
|
|
76
80
|
readonly userApiKey: string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* An `sga_…` agent API key, from `SPECGUARD_AGENT_API_KEY`. `undefined` when unset.
|
|
83
|
+
*
|
|
84
|
+
* A THIRD slot, and the same argument as the second one — see the note at the
|
|
85
|
+
* top of this file. It is minted from the account page's Agent keys panel
|
|
86
|
+
* (`/account`) and speaks for NOBODY: its reach is the repository set and
|
|
87
|
+
* permission set granted onto it at mint time, which is what makes it the
|
|
88
|
+
* credential an automated agent can hold without borrowing a person's rights.
|
|
89
|
+
* Neither deployment endpoint will accept the other token's kind, so the
|
|
90
|
+
* three values live in three places here too.
|
|
91
|
+
*/
|
|
92
|
+
readonly agentApiKey: string | undefined;
|
|
77
93
|
/**
|
|
78
94
|
* The command that runs the `@intent` linter, already tokenised.
|
|
79
95
|
*
|
|
@@ -126,23 +142,23 @@ export interface ApiConfig {
|
|
|
126
142
|
readonly requestTimeoutMs: number;
|
|
127
143
|
}
|
|
128
144
|
export type EndpointVariable = "SPECGUARD_ENDPOINT" | "SPECGUARD_URL";
|
|
129
|
-
export type ApiKeyVariable = "SPECGUARD_API_KEY" | "SPECGUARD_USER_API_KEY";
|
|
145
|
+
export type ApiKeyVariable = "SPECGUARD_API_KEY" | "SPECGUARD_USER_API_KEY" | "SPECGUARD_AGENT_API_KEY";
|
|
130
146
|
/**
|
|
131
|
-
* One of SpecGuard's
|
|
147
|
+
* One of SpecGuard's three credential kinds, described well enough that a message
|
|
132
148
|
* about it can be written without knowing which one it is.
|
|
133
149
|
*
|
|
134
150
|
* The two prose fields are sentence FRAGMENTS rather than whole messages on
|
|
135
151
|
* purpose: the surrounding wording — "is not set in the MCP server's
|
|
136
|
-
* environment", "SpecGuard rejected the API key (401)" — is the same for
|
|
137
|
-
*
|
|
138
|
-
* parts that genuinely differ between an `sgk_` key
|
|
139
|
-
* here.
|
|
152
|
+
* environment", "SpecGuard rejected the API key (401)" — is the same for every
|
|
153
|
+
* kind and is written once, at the site that knows the situation. Only the
|
|
154
|
+
* parts that genuinely differ between an `sgk_` key, an `sgu_` key and an
|
|
155
|
+
* `sga_` key live here.
|
|
140
156
|
*/
|
|
141
157
|
export interface Credential {
|
|
142
158
|
/** The environment variable this kind of key is read from. */
|
|
143
159
|
readonly variable: ApiKeyVariable;
|
|
144
160
|
/** The prefix SpecGuard requires of it, checked before any table is read. */
|
|
145
|
-
readonly prefix: "sgk_" | "sgu_";
|
|
161
|
+
readonly prefix: "sgk_" | "sgu_" | "sga_";
|
|
146
162
|
/** Completes "… issued from ___" in a message about the variable being unset. */
|
|
147
163
|
readonly issuedFrom: string;
|
|
148
164
|
/** Completes "… issued by <deployment> ___" in a message about a 401. */
|
|
@@ -165,6 +181,27 @@ export declare const REPOSITORY_CREDENTIAL: Credential;
|
|
|
165
181
|
* than assuming it is lying around.
|
|
166
182
|
*/
|
|
167
183
|
export declare const USER_CREDENTIAL: Credential;
|
|
184
|
+
/**
|
|
185
|
+
* The `sga_` key: an AGENT, and a credential that speaks for nobody.
|
|
186
|
+
*
|
|
187
|
+
* Like the `sgu_` key it is minted from the account page, but the two are not
|
|
188
|
+
* the same gesture and the message must not blur them: a person key speaks with
|
|
189
|
+
* the person's whole grantable surface, while an agent key reaches exactly the
|
|
190
|
+
* repository set and permission set granted onto it at mint time — which is the
|
|
191
|
+
* whole reason an automated agent holds this one rather than a person's. The
|
|
192
|
+
* `issuedFrom` fragment names the panel, because an operator sent to "the
|
|
193
|
+
* account page" for an `sga_` key lands on the person-key panel one scroll
|
|
194
|
+
* earlier and mints the wrong kind.
|
|
195
|
+
*
|
|
196
|
+
* The `rejection` fragment names the two refusals specific to this kind: the
|
|
197
|
+
* mutual prefix refusal (both other key kinds are refused here without a
|
|
198
|
+
* lookup), and the fact that a key whose grant was too narrow does NOT read as
|
|
199
|
+
* a 401 — a repository outside the key's set answers 404, deliberately
|
|
200
|
+
* indistinguishable from a nonexistent one, so a 401 here is about the KEY
|
|
201
|
+
* itself (wrong kind, revoked, or the owner archived) and never about which
|
|
202
|
+
* repositories it names.
|
|
203
|
+
*/
|
|
204
|
+
export declare const AGENT_CREDENTIAL: Credential;
|
|
168
205
|
/**
|
|
169
206
|
* What a tool needing the `sgk_` REPOSITORY key requires — the endpoint and
|
|
170
207
|
* `SPECGUARD_API_KEY`.
|
|
@@ -187,6 +224,51 @@ export declare function requireApiConfig(config: Config): ApiConfig;
|
|
|
187
224
|
* situation differently.
|
|
188
225
|
*/
|
|
189
226
|
export declare function requireUserApiConfig(config: Config): ApiConfig;
|
|
227
|
+
/**
|
|
228
|
+
* What a tool needing the `sga_` AGENT key requires — the endpoint and
|
|
229
|
+
* `SPECGUARD_AGENT_API_KEY`.
|
|
230
|
+
*
|
|
231
|
+
* The third sibling over the one shared body, for the reason the file's own
|
|
232
|
+
* header anticipated: a tool asks for what IT needs, startup still validates
|
|
233
|
+
* nothing, and a fix to the diagnostics reaches all three because they cannot
|
|
234
|
+
* drift into describing the same situation differently. A repository-scoped
|
|
235
|
+
* tool asked about a repository its `sgk_` slot does not name calls this, and
|
|
236
|
+
* reads the plural endpoints the agent credential is served by.
|
|
237
|
+
*/
|
|
238
|
+
export declare function requireAgentApiConfig(config: Config): ApiConfig;
|
|
239
|
+
/**
|
|
240
|
+
* What a tool EITHER credential serves requires — the endpoint and at least one
|
|
241
|
+
* of `SPECGUARD_AGENT_API_KEY` / `SPECGUARD_USER_API_KEY`.
|
|
242
|
+
*
|
|
243
|
+
* The plural endpoints accept both key kinds and bound the answer by whichever
|
|
244
|
+
* was presented — the person's `accessible_by` set for an `sgu_` key, the key's
|
|
245
|
+
* own granted set for an `sga_` one — so the tool's question ("which
|
|
246
|
+
* repositories may I ask about") has a true answer under either credential, and
|
|
247
|
+
* refusing until the operator picks one would be inventing a requirement the
|
|
248
|
+
* deployment does not have.
|
|
249
|
+
*
|
|
250
|
+
* When BOTH are set, the agent key wins, and the reason is consistency of
|
|
251
|
+
* scope rather than preference: every other agent-keyed tool answers inside the
|
|
252
|
+
* key's granted set, so a listing served from the person's wider set would
|
|
253
|
+
* advertise repositories the agent then cannot open — discovery promising more
|
|
254
|
+
* than the tools that follow it can deliver. The agent set is a subset of its
|
|
255
|
+
* owner's by mint-time validation, so this never widens what the answer names,
|
|
256
|
+
* only keeps it to what the agent can actually act on. The variable is new, so
|
|
257
|
+
* no existing operator's behavior changes: today nobody sets it, and setting it
|
|
258
|
+
* is a deliberate act this precedence honours.
|
|
259
|
+
*
|
|
260
|
+
* When NEITHER is set, the one-message rule applies at double width: both
|
|
261
|
+
* variables are named, because telling an operator who is expected to choose
|
|
262
|
+
* between two spellings about only one of them would have them fix a variable,
|
|
263
|
+
* re-call, and be told about the other — the round-trip waste the shared body's
|
|
264
|
+
* note above exists to prevent. That sentence is NOT written here: the helper
|
|
265
|
+
* falls through to the shared body with the credentials it should name, so the
|
|
266
|
+
* wording is produced once, by the same code the other two entry points use,
|
|
267
|
+
* and the one invariant this file asserts about its diagnostics — a fix reaches
|
|
268
|
+
* every caller because no caller describes the situation itself — holds for
|
|
269
|
+
* this helper too. The endpoint joins the same sentence when it is missing too.
|
|
270
|
+
*/
|
|
271
|
+
export declare function requireUserOrAgentApiConfig(config: Config): ApiConfig;
|
|
190
272
|
/**
|
|
191
273
|
* Splits a configured command into argv WITHOUT a shell, honouring single and
|
|
192
274
|
* double quotes so a path with a space survives.
|