data-olympus 0.7.2__tar.gz → 0.7.3__tar.gz

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.
Files changed (94) hide show
  1. {data_olympus-0.7.2 → data_olympus-0.7.3}/CHANGELOG.md +31 -1
  2. {data_olympus-0.7.2 → data_olympus-0.7.3}/PKG-INFO +1 -1
  3. {data_olympus-0.7.2 → data_olympus-0.7.3}/pyproject.toml +1 -1
  4. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/config.py +81 -2
  5. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/server.py +21 -1
  6. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/write_gate.py +10 -4
  7. {data_olympus-0.7.2 → data_olympus-0.7.3}/.gitignore +0 -0
  8. {data_olympus-0.7.2 → data_olympus-0.7.3}/LICENSE +0 -0
  9. {data_olympus-0.7.2 → data_olympus-0.7.3}/NOTICE +0 -0
  10. {data_olympus-0.7.2 → data_olympus-0.7.3}/README.md +0 -0
  11. {data_olympus-0.7.2 → data_olympus-0.7.3}/SPEC.md +0 -0
  12. {data_olympus-0.7.2 → data_olympus-0.7.3}/benchmarks/README.md +0 -0
  13. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/_kb_detect_workspace.sh +0 -0
  14. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/_kb_enforce.py +0 -0
  15. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/_kb_fallback.py +0 -0
  16. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/kb +0 -0
  17. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/kb-enforce-hook +0 -0
  18. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/kb-session-recap-hook +0 -0
  19. {data_olympus-0.7.2 → data_olympus-0.7.3}/bin/opencode/data-olympus-gate.ts +0 -0
  20. {data_olympus-0.7.2 → data_olympus-0.7.3}/deploy/k8s/README.md +0 -0
  21. {data_olympus-0.7.2 → data_olympus-0.7.3}/deploy/k8s/read-replica/README.md +0 -0
  22. {data_olympus-0.7.2 → data_olympus-0.7.3}/docs/releases/README.md +0 -0
  23. {data_olympus-0.7.2 → data_olympus-0.7.3}/example-bundle/projects/acme-app/README.md +0 -0
  24. {data_olympus-0.7.2 → data_olympus-0.7.3}/example-bundle/projects/acme-app/components/api/README.md +0 -0
  25. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/__init__.py +0 -0
  26. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/audit_log.py +0 -0
  27. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/audit_trailers.py +0 -0
  28. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/auth.py +0 -0
  29. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/__init__.py +0 -0
  30. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/import_cmd.py +0 -0
  31. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/indexgen.py +0 -0
  32. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/init_cmd.py +0 -0
  33. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/main.py +0 -0
  34. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/migrate_cmd.py +0 -0
  35. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/report_cmd.py +0 -0
  36. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/validity_report_cmd.py +0 -0
  37. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cli/verify_cmd.py +0 -0
  38. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/cooccurrence.py +0 -0
  39. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/dedup.py +0 -0
  40. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/durable.py +0 -0
  41. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/embeddings.py +0 -0
  42. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/enforce_policy.py +0 -0
  43. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/format/__init__.py +0 -0
  44. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/format/document.py +0 -0
  45. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/format/frontmatter.py +0 -0
  46. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/format/lint.py +0 -0
  47. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/format/validate.py +0 -0
  48. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/git_ops.py +0 -0
  49. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/governed_lane.py +0 -0
  50. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/health.py +0 -0
  51. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/__init__.py +0 -0
  52. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/adr.py +0 -0
  53. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/flat.py +0 -0
  54. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/model.py +0 -0
  55. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/okf.py +0 -0
  56. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/run.py +0 -0
  57. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/importer/stamp.py +0 -0
  58. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/index.py +0 -0
  59. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/maintenance.py +0 -0
  60. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/markdown_parse.py +0 -0
  61. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/metrics.py +0 -0
  62. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/models.py +0 -0
  63. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/onboarding.py +0 -0
  64. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/onboarding_inflight.py +0 -0
  65. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/onboarding_playbook.py +0 -0
  66. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/pending.py +0 -0
  67. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/principals.py +0 -0
  68. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/prompts.py +0 -0
  69. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/push_queue.py +0 -0
  70. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/query_expansion.py +0 -0
  71. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/rate_limit.py +0 -0
  72. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/refresh.py +0 -0
  73. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/report.py +0 -0
  74. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/rest_api.py +0 -0
  75. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/safe_id.py +0 -0
  76. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/scaffold.py +0 -0
  77. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/search_gate.py +0 -0
  78. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/search_shortcut.py +0 -0
  79. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/session_metrics.py +0 -0
  80. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/setup_wizard.py +0 -0
  81. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/status_migrate.py +0 -0
  82. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/thin_pointer.py +0 -0
  83. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tool_discovery.py +0 -0
  84. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_audit.py +0 -0
  85. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_enforce.py +0 -0
  86. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_onboarding.py +0 -0
  87. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_read.py +0 -0
  88. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_recap.py +0 -0
  89. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/tools_write.py +0 -0
  90. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/trigram.py +0 -0
  91. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/version_check.py +0 -0
  92. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/viewer/__init__.py +0 -0
  93. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/viewer/generator.py +0 -0
  94. {data_olympus-0.7.2 → data_olympus-0.7.3}/src/data_olympus/worktrees.py +0 -0
@@ -12,6 +12,35 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.7.3] - 2026-09-07
16
+
17
+ ### Fixed
18
+
19
+ * **A blank `KB_MAIN_PATH` no longer makes the server index its own working
20
+ directory.** `KB_MAIN_PATH=` and `KB_INDEX_PATH=` -- what an unsubstituted
21
+ compose, Helm or CI variable produces -- reached `Path("")`, which is
22
+ `Path(".")`, so the server silently indexed whatever happened to be in its
23
+ working directory. A blank or whitespace-only value now means unset and the
24
+ documented default applies, while a non-blank value is used verbatim. And
25
+ when the
26
+ corpus path does not resolve to a directory, startup now fails with a message
27
+ naming the setting, the path it resolved to, and whether that path came from
28
+ the environment or the built-in default, instead of
29
+ `KB root not a directory: /kb-main` -- a path the operator never configured
30
+ and no setting name to search for. An existing but empty corpus keeps
31
+ starting normally, as before. A deployment that relied on blank-means-cwd
32
+ changes behaviour; that reliance was on the silent defect being fixed here.
33
+ ([#244](https://github.com/knaisoma/data-olympus/issues/244))
34
+
35
+ * **The Kubernetes first-boot log no longer prints the configured git remote.**
36
+ The StatefulSet initContainer echoed the remote URL immediately before
37
+ cloning it. The remote is documented as "SSH or HTTPS", and an HTTPS remote
38
+ may carry a credential inside the URL, so an adopter using one wrote that
39
+ credential into the pod log on every first boot, where no application-level
40
+ redaction reaches it. The message now names no URL, matching the Docker
41
+ entrypoint. Nothing else about the clone changes.
42
+ ([#248](https://github.com/knaisoma/data-olympus/issues/248))
43
+
15
44
  ## [0.7.2] - 2026-09-07
16
45
 
17
46
  ### Changed
@@ -1983,7 +2012,8 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1983
2012
  - `docs/adoption.md`: bring-your-own-KB guide (author, lint, index, serve, wire an agent).
1984
2013
  - `docs/comparison.md`: how data-olympus relates to OKF, enterprise catalogs, markdown KB tools, agent-context conventions, RAG, and ADR tooling.
1985
2014
 
1986
- [Unreleased]: https://github.com/knaisoma/data-olympus/compare/v0.6.0...HEAD
2015
+ [Unreleased]: https://github.com/knaisoma/data-olympus/compare/v0.7.3...HEAD
2016
+ [0.7.3]: https://github.com/knaisoma/data-olympus/compare/v0.7.2...v0.7.3
1987
2017
  [0.6.0]: https://github.com/knaisoma/data-olympus/compare/v0.5.0...v0.6.0
1988
2018
  [0.5.0]: https://github.com/knaisoma/data-olympus/compare/v0.4.2...v0.5.0
1989
2019
  [0.4.1]: https://github.com/knaisoma/data-olympus/compare/v0.4.0...v0.4.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: data-olympus
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Governance-grade knowledge-base format (OKF-compatible) plus CLI and single-writer MCP server
5
5
  Project-URL: Homepage, https://github.com/knaisoma/data-olympus
6
6
  Project-URL: Repository, https://github.com/knaisoma/data-olympus
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "data-olympus"
3
- version = "0.7.2"
3
+ version = "0.7.3"
4
4
  description = "Governance-grade knowledge-base format (OKF-compatible) plus CLI and single-writer MCP server"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -11,6 +11,12 @@ from data_olympus.tool_discovery import ToolDiscoveryMode, load_tool_discovery_m
11
11
 
12
12
  _log = logging.getLogger("data_olympus.config")
13
13
 
14
+ # How a path setting got its value, for error messages that tell the operator
15
+ # where to look. A path that came from the environment is the operator's to fix;
16
+ # a path that came from the default means the setting was never supplied.
17
+ PATH_SOURCE_ENV = "environment"
18
+ PATH_SOURCE_DEFAULT = "default"
19
+
14
20
 
15
21
  @dataclass(frozen=True, slots=True)
16
22
  class Config:
@@ -90,6 +96,14 @@ class Config:
90
96
  # intentionally reverses the #114 "never guess status" stance: for the narrow
91
97
  # legacy-upgrade case, a seamless default beats conservative flagging.
92
98
  status_autofill: bool = True
99
+ # Where kb_main_path / kb_index_path came from: PATH_SOURCE_ENV when the
100
+ # operator supplied a non-blank value, PATH_SOURCE_DEFAULT when the setting
101
+ # was absent or blank. Used only to make a startup failure name the setting
102
+ # and say whether the operator ever configured it. Defaults to
103
+ # PATH_SOURCE_DEFAULT so a programmatic caller that passes paths directly
104
+ # (build_app in tests) does not have to supply provenance it does not have.
105
+ kb_main_path_source: str = PATH_SOURCE_DEFAULT
106
+ kb_index_path_source: str = PATH_SOURCE_DEFAULT
93
107
  # Streamable-http session reaping: terminate transports idle beyond this many
94
108
  # seconds to bound _server_instances (see session_metrics). 0 disables the
95
109
  # reaper (observability-only). The scan runs every session_reap_interval_sec.
@@ -200,6 +214,65 @@ def _load_status_weights(raw: str) -> dict[str, float] | None:
200
214
  f"got {type(data).__name__}"
201
215
  )
202
216
  return {str(k): float(v) for k, v in data.items()}
217
+
218
+
219
+ def _env_path(name: str, default: str) -> tuple[Path, str]:
220
+ """Resolve a filesystem path setting, treating blank as unset.
221
+
222
+ ``os.environ.get(name, default)`` only falls back when the variable is
223
+ ABSENT. A variable that is set but empty -- which is exactly what an
224
+ unsubstituted compose/Helm/CI variable produces -- returns ``""``, and
225
+ ``Path("")`` is ``Path(".")``. So a blank ``KB_MAIN_PATH`` used to make the
226
+ server silently index its own working directory instead of the corpus, with
227
+ no error and no log line naming the setting.
228
+
229
+ A whitespace-only value is the same mistake with a space in it, so it is
230
+ treated as unset too. A value that is NOT blank is used verbatim: a path may
231
+ legitimately carry leading or trailing whitespace, and silently rewriting an
232
+ operator's path is a different bug from the one being fixed. A padded path
233
+ that does not exist now fails through :func:`corpus_path_problem`, which
234
+ names the setting and the exact resolved value.
235
+
236
+ Returns the resolved path and which of :data:`PATH_SOURCE_ENV` /
237
+ :data:`PATH_SOURCE_DEFAULT` it came from.
238
+ """
239
+ raw = os.environ.get(name)
240
+ if raw is None or not raw.strip():
241
+ return Path(default), PATH_SOURCE_DEFAULT
242
+ return Path(raw), PATH_SOURCE_ENV
243
+
244
+
245
+ def corpus_path_problem(config: Config) -> str | None:
246
+ """Return an actionable message when the configured corpus is unusable.
247
+
248
+ ``None`` when ``kb_main_path`` is a directory. The message names the
249
+ setting, the path it resolved to, and whether that path came from the
250
+ environment or the built-in default -- the three things the previous
251
+ ``NotADirectoryError: KB root not a directory: /kb-main`` left the operator
252
+ to guess, since it printed a path they had never configured and no setting
253
+ name to search for.
254
+ """
255
+ path = config.kb_main_path
256
+ if path.is_dir():
257
+ return None
258
+ if config.kb_main_path_source == PATH_SOURCE_ENV:
259
+ origin = "That path came from the KB_MAIN_PATH environment variable."
260
+ fix = "Point KB_MAIN_PATH at the knowledge-base checkout."
261
+ else:
262
+ origin = (
263
+ "KB_MAIN_PATH is unset or blank, so that path is the built-in "
264
+ "default."
265
+ )
266
+ fix = (
267
+ f"Set KB_MAIN_PATH to the knowledge-base checkout, or mount the "
268
+ f"corpus at {path}."
269
+ )
270
+ what = "does not exist" if not path.exists() else "is not a directory"
271
+ return (
272
+ f"KB_MAIN_PATH resolves to {path}, which {what}. {origin} {fix}"
273
+ )
274
+
275
+
203
276
  def _env_bool(raw: str) -> bool:
204
277
  """Parse a truthy env string. True for 1/true/yes/on (case-insensitive);
205
278
  everything else (including empty) is False."""
@@ -299,9 +372,15 @@ def load_config() -> Config:
299
372
  version_check_interval_sec = int(
300
373
  os.getenv("KB_VERSION_CHECK_INTERVAL_SEC", "86400")
301
374
  )
375
+ kb_main_path, kb_main_path_source = _env_path("KB_MAIN_PATH", "/kb-main")
376
+ kb_index_path, kb_index_path_source = _env_path(
377
+ "KB_INDEX_PATH", "/index/kb.db"
378
+ )
302
379
  return Config(
303
- kb_main_path=Path(os.environ.get("KB_MAIN_PATH", "/kb-main")),
304
- kb_index_path=Path(os.environ.get("KB_INDEX_PATH", "/index/kb.db")),
380
+ kb_main_path=kb_main_path,
381
+ kb_index_path=kb_index_path,
382
+ kb_main_path_source=kb_main_path_source,
383
+ kb_index_path_source=kb_index_path_source,
305
384
  kb_remote_url=os.environ.get("KB_REMOTE_URL", ""),
306
385
  sync_interval_sec=int(os.environ.get("KB_SYNC_INTERVAL_SEC", "60")),
307
386
  staleness_degraded_sec=int(os.environ.get("KB_STALENESS_DEGRADED_SEC", "600")),
@@ -23,7 +23,12 @@ if TYPE_CHECKING:
23
23
 
24
24
  from data_olympus.audit_log import AuditLog
25
25
  from data_olympus.auth import PathBlocklist
26
- from data_olympus.config import Config, load_config
26
+ from data_olympus.config import (
27
+ PATH_SOURCE_DEFAULT,
28
+ Config,
29
+ corpus_path_problem,
30
+ load_config,
31
+ )
27
32
  from data_olympus.cooccurrence import (
28
33
  DEFAULT_MAX_TERMS,
29
34
  compose_expanders,
@@ -447,6 +452,8 @@ def build_app(
447
452
  maintenance_recently_expired_days: int = 30,
448
453
  maintenance_expiring_soon_days: int = 30,
449
454
  status_autofill: bool = True,
455
+ kb_main_path_source: str = PATH_SOURCE_DEFAULT,
456
+ kb_index_path_source: str = PATH_SOURCE_DEFAULT,
450
457
  ) -> FastMCP:
451
458
  """Construct a FastMCP app with the read tools registered.
452
459
 
@@ -500,6 +507,8 @@ def build_app(
500
507
  maintenance_recently_expired_days=maintenance_recently_expired_days,
501
508
  maintenance_expiring_soon_days=maintenance_expiring_soon_days,
502
509
  status_autofill=status_autofill,
510
+ kb_main_path_source=kb_main_path_source,
511
+ kb_index_path_source=kb_index_path_source,
503
512
  )
504
513
  if audit_log_path is not None:
505
514
  config_kwargs["audit_log_path"] = audit_log_path
@@ -1185,7 +1194,16 @@ def build_app_from_config(config: Config, *, bootstrap_now: bool = True) -> Fast
1185
1194
  integration tests that need env-driven config to reach the app state.
1186
1195
  Every Config field is threaded through to build_app, so no value is silently
1187
1196
  dropped or overridden by a hardcoded default.
1197
+
1198
+ When bootstrapping, the corpus path is checked first so the failure names
1199
+ KB_MAIN_PATH, the path it resolved to, and whether the operator supplied it.
1200
+ Index.build would otherwise raise ``KB root not a directory: /kb-main``,
1201
+ naming a path the operator never configured and no setting to search for.
1188
1202
  """
1203
+ if bootstrap_now:
1204
+ problem = corpus_path_problem(config)
1205
+ if problem is not None:
1206
+ raise NotADirectoryError(problem)
1189
1207
  return build_app(
1190
1208
  kb_main_path=config.kb_main_path,
1191
1209
  kb_index_path=config.kb_index_path,
@@ -1229,6 +1247,8 @@ def build_app_from_config(config: Config, *, bootstrap_now: bool = True) -> Fast
1229
1247
  maintenance_recently_expired_days=config.maintenance_recently_expired_days,
1230
1248
  maintenance_expiring_soon_days=config.maintenance_expiring_soon_days,
1231
1249
  status_autofill=config.status_autofill,
1250
+ kb_main_path_source=config.kb_main_path_source,
1251
+ kb_index_path_source=config.kb_index_path_source,
1232
1252
  )
1233
1253
 
1234
1254
 
@@ -536,15 +536,21 @@ _LLM_PROVIDER_KEY_RE = re.compile(
536
536
  r")(?![A-Za-z0-9])"
537
537
  )
538
538
  # Google API key. Not LLM-specific -- the same shape authenticates Gemini, Maps
539
- # and every other Google API -- so it gets its own class rather than being
540
- # folded into the one above.
539
+ # and the other Google APIs that accept an API key -- so it gets its own class
540
+ # rather than being folded into the one above. It is not every Google API:
541
+ # many require OAuth or a service account and reject an API key outright, so
542
+ # this class says the credential is Google-shaped, not that it opens any given
543
+ # Google service.
541
544
  #
542
545
  # The tail is a negative lookahead rather than ``\b``. The body is a fixed
543
546
  # ``{35}`` with nothing to backtrack into, so a trailing ``\b`` would demand a
544
547
  # word character in the 35th position: a key whose last character is ``-``
545
548
  # (roughly one in 64, since the alphabet includes it) would fail to match at
546
- # all. The lookahead asserts the key is not a prefix of a longer token without
547
- # constraining its final character.
549
+ # all. The lookahead refuses a continuation in ``[A-Za-z0-9-]`` without
550
+ # constraining the key's own final character. It is deliberately weaker than
551
+ # "not a prefix of a longer token": ``_`` is in the key alphabet and is NOT
552
+ # excluded, for the reason the next paragraph gives. Tightening the lookahead
553
+ # to cover ``_`` would satisfy the stronger reading and destroy that detection.
548
554
  # The trailing exclusion omits ``_`` deliberately. A key is exactly 39
549
555
  # characters, so a following ``_`` is either a delimiter or proof the token was
550
556
  # never a key; treating it as a delimiter costs a possible needless flag and
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes