Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/spec-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: spec-drift

# Glean publishes its OpenAPI specs independently of this repo. The conformance
# test (tests/test_spec_conformance.py) checks client.py against a committed
# manifest so the suite stays offline; this job is what notices the manifest has
# fallen behind the published spec.
#
# Deliberately NOT part of the release workflow: a spec update is an external
# event, and it should not fail an unrelated pull request.

on:
schedule:
- cron: "0 7 * * 1" # Mondays, 07:00 UTC
workflow_dispatch:

permissions:
contents: read

jobs:
check:
name: is tests/spec_manifest.json still current?
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

# PyYAML is a tooling-only dependency: the package and the test suite
# never import it, so the zero-dependency guarantee is unaffected.
- name: Install the tooling dependency
run: python -m pip install --quiet pyyaml

- name: Compare the manifest against the published spec
run: python3 tools/refresh_spec_manifest.py --check

- name: What to do if this failed
if: failure()
run: |
echo "::notice::Glean has published a spec change."
echo "Refresh the manifest and let the conformance test show what moved:"
echo " python3 -m pip install pyyaml"
echo " python3 tools/refresh_spec_manifest.py"
echo " python3 -m unittest tests.test_spec_conformance -v"
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ python3 -m unittest tests.test_commands.TestParseArgs.test_flag_with_value

There is no build step, no `pip install`, no linter config, and no `pyproject.toml`/`setup.py`. Tests use only the stdlib (`unittest`, `unittest.mock`) and make no network calls.

`tools/refresh_spec_manifest.py` is the single exception: it fetches Glean's OpenAPI
specs and needs PyYAML. It is tooling only — never imported by the package or the
tests — so the zero-dependency guarantee still holds. The scheduled `spec-drift`
workflow runs it with `--check` weekly.

`install.py` is the only thing that produces an artifact: it stages the package in a temp
directory and emits a zipapp, so nothing is ever built inside the repo. On macOS set
`PYTHONPYCACHEPREFIX=~/.cache/python` to keep `__pycache__` out of the working tree —
Expand Down Expand Up @@ -83,6 +88,7 @@ Client API (`/rest/api/v1`, `api_token`) and Indexing API (`/api/index/v1`, `ind
4. Add a `DOCS` entry in help_docs.py (summary, usage, params, examples, endpoint) or the command is hidden from `/help` and the planner.
5. If it writes/deletes/changes auth, add the dotted name to `_NL_DESTRUCTIVE` in commands.py. A command that takes its verb positionally (`/config set`, `/personal purge`, `/flow purge`) goes in `_NL_DESTRUCTIVE_SUBS` instead, keyed by command name with the mutating sub-verbs.
6. Add tests (mock-mode handler test + client/mock test). Reuse the `_mock_session()` helper pattern in the test files.
7. Confirm the path exists in the spec. `tests/test_spec_conformance.py` asserts every REST path and method in `client.py` appears in `tests/spec_manifest.json`, which is generated from Glean's published OpenAPI specs by `tools/refresh_spec_manifest.py`. If the test fails, the endpoint is not in the spec — check the path before assuming the manifest is stale. Twelve calls once drifted precisely because mock responses are keyed on the same path strings the live client posts to, so a wrong path was wrong consistently and the suite stayed green.

**Token safety** — never let real secrets reach disk or the history buffer. Secure refs (`token.secure.client` / `token.secure.indexing`) are stored verbatim and resolved from `$GLEAN_CLIENT_TOKEN` / `$GLEAN_INDEXING_TOKEN` at request time via `resolve_secure`. `_sanitize_for_history` masks `--token`/`--indexing-token` values and `/config set <token-key>` values before they enter `command_history`. `_display_token` masks literals to `***1234`. Preserve all of this when adding any command that handles a token.

Expand Down
Binary file added assets/glean_code_cli_header.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 5 additions & 23 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1385,7 +1385,7 @@ List callable tools exposed to agents in the current workspace.

**Output** — Tool names and descriptions.

**Endpoint** — `POST /rest/api/v1/tools/list`
**Endpoint** — `GET /rest/api/v1/tools/list`

---

Expand Down Expand Up @@ -1461,7 +1461,7 @@ Fetch the permission list for a document.

**Mock mode** — The document's author comes back as `owner` and the rest of the [people roster](MOCK_CORPUS.md#the-people-roster) as `viewer`, e.g. `/docs.permissions doc_plan_process` → `priya.raman@acme.com` as owner.

**Endpoint** — `POST /rest/api/v1/getdocumentpermissions`
**Endpoint** — `POST /rest/api/v1/getdocpermissions`

---

Expand Down Expand Up @@ -1521,24 +1521,6 @@ Look up a person's full profile by email address.

---

#### /announcements.list

List all current announcements in the workspace.

```text
/announcements.list
```

```text
/announcements.list
```

**Output** — Announcement ids, titles, and metadata.

**Endpoint** — `POST /rest/api/v1/announcements/list`

---

#### /announcements.create

Create a new workspace announcement.
Expand All @@ -1560,7 +1542,7 @@ Create a new workspace announcement.

**Output** — The new announcement id and creation status.

**Endpoint** — `POST /rest/api/v1/announcements/create`
**Endpoint** — `POST /rest/api/v1/createannouncement`

---

Expand All @@ -1574,15 +1556,15 @@ Delete an announcement by id.

| Parameter | Description |
| --- | --- |
| `id` | The announcement id (from `/announcements.list`). |
| `id` | The announcement id (returned by `/announcements.create`). |

```text
/announcements.delete ann_123
```

**Output** — Confirms the announcement was deleted.

**Endpoint** — `POST /rest/api/v1/announcements/delete`
**Endpoint** — `POST /rest/api/v1/deleteannouncement`

---

Expand Down
2 changes: 1 addition & 1 deletion docs/COMMAND_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Every command, grouped by surface.
| Insights & activity | `/insights` `/activity.report` |
| Agents and tools | `/agents.list` `/agents.run` `/tools.list` `/tools.call` |
| Docs and people | `/docs.get` `/docs.permissions` `/entities.list` `/people.get` |
| Announcements, collections, pins | `/announcements.list` `/announcements.create` `/announcements.delete` `/collections.list` `/collections.create` `/collections.delete` `/pins.list` `/pins.create` `/pins.delete` |
| Announcements, collections, pins | `/announcements.create` `/announcements.delete` `/collections.list` `/collections.create` `/collections.delete` `/pins.list` `/pins.create` `/pins.delete` |
| Shortcuts (Go Links) | `/shortcuts.list` `/shortcuts.get` `/shortcuts.create` `/shortcuts.update` `/shortcuts.delete` |
| Answers | `/answers.list` `/answers.get` `/answers.create` `/answers.update` `/answers.delete` |
| Verification | `/verification.list` `/verification.verify` `/verification.remind` |
Expand Down
2 changes: 1 addition & 1 deletion docs/MOCK_CORPUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ Queries the corpus is written to answer well:
| [`/recommendations`](COMMANDS.md#recommendations) | `/recommendations` | The freshest documents, in full result shape |
| [`/datasources.list`](COMMANDS.md#datasourceslist) | `/search` (facets) | The five datasource names and their index counts |
| [`/docs.get`](COMMANDS.md#docsget) | `/getdocuments` | Lookup by id or URL; unknown specs echo back |
| [`/docs.permissions`](COMMANDS.md#docspermissions) | `/getdocumentpermissions` | Author as `owner`, roster as `viewer` |
| [`/docs.permissions`](COMMANDS.md#docspermissions) | `/getdocpermissions` | Author as `owner`, roster as `viewer` |
| [`/summarize`](COMMANDS.md#summarize) | `/summarize` | A summary built from that document's body, author, and age |
| [`/entities.list`](COMMANDS.md#entitieslist) | `/listentities` | The people roster |
| [`/people.get`](COMMANDS.md#peopleget) | `/people` | Profile for a roster email |
Expand Down
9 changes: 4 additions & 5 deletions docs/REST_PATHS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ POST /feedback
POST /agents/search
POST /agents/runs/wait
POST /agents/runs/stream
POST /tools/list
GET /tools/list
POST /tools/call
POST /getdocuments
POST /getdocumentpermissions
POST /getdocpermissions
POST /listentities
POST /people
POST /announcements/list
POST /announcements/create
POST /announcements/delete
POST /createannouncement
POST /deleteannouncement
POST /listcollections
POST /createcollection
POST /listpins
Expand Down
32 changes: 32 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,35 @@ The module patches out the mock client's simulated 0.25s network latency; withou
- A corrupt database returns an error string rather than a traceback

The linking fixture deliberately uses five documents rather than two. With two, every shared phrase appears in every document, so IDF cannot distinguish "topically shared" from "common vocabulary" and nothing can score above the threshold — a property of IDF at that scale, not a bug worth distorting the scoring to hide.

## Spec conformance

`tests/test_spec_conformance.py` reads `glean_code/client.py` with `ast` and asserts
that every REST path and HTTP method it calls exists in Glean's published OpenAPI
spec. It compares against `tests/spec_manifest.json` rather than the live spec, so
it stays offline like the rest of the suite.

Regenerate the manifest when Glean ships a spec update:

```bash
python3 -m pip install pyyaml # tooling-only dependency
python3 tools/refresh_spec_manifest.py
python3 -m unittest tests.test_spec_conformance -v
```

The check exists because twelve Client API calls once drifted from the spec
undetected: `_mock_response` is keyed on the same path strings the live client
posts to, so a wrong path was wrong in both places and every test passed. It
catches wrong paths, wrong methods, and invented endpoints.

Two deliberate limits:

- It does **not** require the client to call every documented endpoint. Plenty are
intentionally unimplemented.
- It does not check request *body* fields. Those are covered by
`TestSpecConformantBodies` in `tests/test_client_extended.py`.

Endpoints the spec declares but leaves undocumented (currently
`/indexemployeelist`) are tracked in the manifest's `undocumented` section and
allowed, but the test fails if one becomes documented again — the signal to drop
the exception.
Loading
Loading