org-knowledge-layer 0.3.0__tar.gz → 0.3.1__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 (116) hide show
  1. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.claude/settings.local.json +16 -1
  2. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/CHANGELOG.md +11 -0
  3. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/PKG-INFO +1 -1
  4. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/pyproject.toml +1 -1
  5. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/service.py +9 -2
  6. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/tests/test_okl.py +38 -0
  7. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.claude/hooks/stop-okl-encode.sh +0 -0
  8. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.claude/hooks/userpromptsubmit-okl-check.sh +0 -0
  9. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.claude/settings.json +0 -0
  10. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.github/pull_request_template.md +0 -0
  13. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.github/workflows/ci.yml +0 -0
  14. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.github/workflows/okl-verify.yml +0 -0
  15. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/.gitignore +0 -0
  16. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/AGENTS.md +0 -0
  17. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/CLAUDE.md +0 -0
  18. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/CONTRIBUTING.md +0 -0
  19. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/LICENSE +0 -0
  20. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/README.md +0 -0
  21. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/SECURITY.md +0 -0
  22. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/ci/okl-verify.yml +0 -0
  23. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/DEPLOY.md +0 -0
  24. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/ab-results-chart.png +0 -0
  25. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/ab-results-chart.svg +0 -0
  26. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +0 -0
  27. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +0 -0
  28. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/okl-sixth-surface.excalidraw +0 -0
  29. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/okl-sixth-surface.svg +0 -0
  30. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/posts/01-memory-that-outlives-the-run.md +0 -0
  31. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/posts/02-dont-let-a-step-grade-itself.md +0 -0
  32. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/docs/posts/03-enforcement-or-good-intentions.md +0 -0
  33. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/README.md +0 -0
  34. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/REPORT.md +0 -0
  35. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/ab_harness.py +0 -0
  36. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/ab-20260829-2300.json +0 -0
  37. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/ab-20260829-2315.json +0 -0
  38. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/ab-20260830-0003.json +0 -0
  39. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/ab-20260830-0148.json +0 -0
  40. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/ab-20260901-0133.json +0 -0
  41. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/README.md +0 -0
  42. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +0 -0
  43. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/control-lint.yml +0 -0
  44. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/hook.log +0 -0
  45. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/service-record-500.log +0 -0
  46. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/session-briefed-pretooluse.txt +0 -0
  47. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +0 -0
  48. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/results/e2e-20260830/session-control.txt +0 -0
  49. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/evals/tasks.jsonl +0 -0
  50. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/hooks/stop-okl-encode.sh +0 -0
  51. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/hooks/userpromptsubmit-okl-check.sh +0 -0
  52. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/dotnet-canon.json +0 -0
  53. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/dotnet-decisions.json +0 -0
  54. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/dotnet-defects.json +0 -0
  55. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/dotnet-review-surfaces.json +0 -0
  56. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/frontend-canon.json +0 -0
  57. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/geospatial-deeptime-defects.json +0 -0
  58. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/geospatial-defects.json +0 -0
  59. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/geospatial-enforcement-defects.json +0 -0
  60. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/geospatial-eval-defects.json +0 -0
  61. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/rag-defects.json +0 -0
  62. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/seed/react-defects.json +0 -0
  63. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/__init__.py +0 -0
  64. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/__main__.py +0 -0
  65. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/bootstrap.py +0 -0
  66. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/cli.py +0 -0
  67. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/client.py +0 -0
  68. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/core.py +0 -0
  69. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/drift.py +0 -0
  70. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/mcp_server.py +0 -0
  71. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/MANIFEST.md +0 -0
  72. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/ci/method-gates.yml +0 -0
  73. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/ci/okl-verify.yml +0 -0
  74. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/agents/architecture-reviewer.md +0 -0
  75. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/commands/check-rules.md +0 -0
  76. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/commands/feature-spec.md +0 -0
  77. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/commands/seed-from-codebase.md +0 -0
  78. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/rules/example-area.md +0 -0
  79. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +0 -0
  80. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +0 -0
  81. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +0 -0
  82. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/evals/README.md +0 -0
  83. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/evals/cases.jsonl +0 -0
  84. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/evals/run_evals.py +0 -0
  85. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-canon-size.sh +0 -0
  86. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-diagram-pairs.sh +0 -0
  87. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-doc-orphans.sh +0 -0
  88. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-links.sh +0 -0
  89. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-retractions.sh +0 -0
  90. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/check-tombstones.sh +0 -0
  91. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/gates/run-gates.sh +0 -0
  92. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/hooks/hooks.json +0 -0
  93. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/hooks/stop-okl-encode.sh +0 -0
  94. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/hooks/userpromptsubmit-okl-check.sh +0 -0
  95. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/plugin/plugin.json +0 -0
  96. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/dotnet/README.md +0 -0
  97. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/dotnet/rules/architecture.md +0 -0
  98. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/dotnet/rules/messaging.md +0 -0
  99. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +0 -0
  100. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/dotnet/rules/security.md +0 -0
  101. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/geospatial/README.md +0 -0
  102. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +0 -0
  103. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/python-rag/README.md +0 -0
  104. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +0 -0
  105. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +0 -0
  106. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +0 -0
  107. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/react/README.md +0 -0
  108. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/profiles/react/rules/frontend.md +0 -0
  109. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/registries/RETRACTIONS.md +0 -0
  110. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/registries/tombstones.txt +0 -0
  111. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/root/CLAUDE.md +0 -0
  112. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold/root/METHOD.md +0 -0
  113. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/scaffold_cmd.py +0 -0
  114. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/seed.py +0 -0
  115. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/src/okl/store.py +0 -0
  116. {org_knowledge_layer-0.3.0 → org_knowledge_layer-0.3.1}/tests/test_scaffold.py +0 -0
@@ -155,7 +155,22 @@
155
155
  "Bash(pkill -f uvicorn)",
156
156
  "Bash(pg_ctl -D /tmp/oklpg/data stop -m immediate)",
157
157
  "Bash(tmutil listlocalsnapshots *)",
158
- "Bash(python3 -c \"import json,sys; print\\(', '.join\\(sorted\\(json.load\\(sys.stdin\\)['releases']\\)\\)\\)\")"
158
+ "Bash(python3 -c \"import json,sys; print\\(', '.join\\(sorted\\(json.load\\(sys.stdin\\)['releases']\\)\\)\\)\")",
159
+ "Bash(awk 'NR==1 || $2 ~ /Gi|Ti/')",
160
+ "Bash(rm -rf /tmp/vt2)",
161
+ "Bash(python3 -m venv /tmp/vt2)",
162
+ "Bash(/tmp/vt2/bin/pip install *)",
163
+ "Bash(/tmp/vt2/bin/python -c ' *)",
164
+ "Bash(python3 -m pytest -q --collect-only)",
165
+ "Bash(curl -s https://pypi.org/simple/org-knowledge-layer/)",
166
+ "Bash(rm -rf /tmp/pv)",
167
+ "Bash(python3 -m venv /tmp/pv)",
168
+ "Bash(/tmp/pv/bin/pip install *)",
169
+ "Bash(/tmp/pv/bin/python -c \"import importlib.metadata as m; print\\(m.version\\('org-knowledge-layer'\\)\\)\")",
170
+ "Bash(OKL_DATABASE_URL=sqlite:///:memory: OKL_TOKEN=t /tmp/pv/bin/python -c ' *)",
171
+ "Bash(OKL_DATABASE_URL=sqlite:///:memory: /tmp/pv/bin/python -c ' *)",
172
+ "Bash(OKL_DATABASE_URL=sqlite:///:memory: OKL_TOKEN=t python3 -c ' *)",
173
+ "Bash(python3 -m pytest -q -k \"openapi_spec_and_docs\")"
159
174
  ],
160
175
  "additionalDirectories": [
161
176
  "/Users/joshuadell/Dev/okl/e2e/scratch-briefed/.okl",
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Security
6
+
7
+ - **Setting `OKL_TOKEN` now also removes `/openapi.json`, `/docs` and `/redoc`.** They
8
+ were left serving 200 to anonymous callers by the 0.3.0 work that closed every data
9
+ route, because FastAPI mounts them itself — they are not handlers, so the per-handler
10
+ auth sweep could not reach them. They leak no records, but they publish the endpoint
11
+ list, every schema, and which routes want a credential. With no token set the
12
+ interactive docs remain available, since that case is a developer's laptop.
13
+
3
14
  ## 0.3.0
4
15
 
5
16
  Everything here came from running three things that had been written but never
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: org-knowledge-layer
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Org Knowledge Layer — an installable sixth surface that carries encoded engineering lessons across repos.
5
5
  Author: Joshua Dell
6
6
  License: MIT
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
  # Distribution name only. The CLI command, the import package, and the repo are all `okl`;
7
7
  # PyPI rejects `okl` as too similar to the existing `oki` (l/i are treated as confusable).
8
8
  name = "org-knowledge-layer"
9
- version = "0.3.0"
9
+ version = "0.3.1"
10
10
  description = "Org Knowledge Layer — an installable sixth surface that carries encoded engineering lessons across repos."
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -66,8 +66,6 @@ class VerifyReq(BaseModel):
66
66
 
67
67
 
68
68
  def create_app(store: Store | None = None) -> FastAPI:
69
- app = FastAPI(title="OKL — the sixth surface", version="0.1.0")
70
- _store = store or Store(os.environ.get("OKL_DATABASE_URL"))
71
69
  # Optional shared-secret gate. If OKL_TOKEN is set it covers READS as well as
72
70
  # writes. Reads used to be open while writes were gated, which meant a deployed
73
71
  # service handed anyone who found the URL a `GET /nodes` dump of the org's entire
@@ -75,6 +73,15 @@ def create_app(store: Store | None = None) -> FastAPI:
75
73
  # decisions. That is a catalogue of where the org is weak, and it is exactly the
76
74
  # material the layer exists to collect. If you set a token, you want it private.
77
75
  token = os.environ.get("OKL_TOKEN")
76
+ # The same reasoning reaches the spec routes, and they were missed when the data
77
+ # routes were closed: FastAPI serves /openapi.json, /docs and /redoc to anyone by
78
+ # default, and no `_auth` call can protect them because the framework mounts them
79
+ # itself. They leak the endpoint list, every schema and where the credential is
80
+ # required — the map you would draw before attacking the routes. Setting the token
81
+ # is the signal that this instance is not a laptop, so the spec comes down with it.
82
+ docs = {} if token is None else {"openapi_url": None, "docs_url": None, "redoc_url": None}
83
+ app = FastAPI(title="OKL — the sixth surface", version="0.1.0", **docs)
84
+ _store = store or Store(os.environ.get("OKL_DATABASE_URL"))
78
85
 
79
86
  def _auth(authorization: str | None) -> None:
80
87
  if token and authorization != f"Bearer {token}":
@@ -355,6 +355,44 @@ def test_asgi_entrypoint_resolves_to_a_real_app_without_importing_the_database(m
355
355
  getattr(svc, "nonexistent_attribute") # noqa: B009 — the lookup IS the assertion
356
356
 
357
357
 
358
+ def test_token_also_takes_down_the_openapi_spec_and_docs_ui(monkeypatch):
359
+ """Setting OKL_TOKEN removes /openapi.json, /docs and /redoc; without it they stay.
360
+
361
+ These were missed when the data routes were closed, because they are the one part of
362
+ the surface `_auth` cannot reach — FastAPI mounts them itself, so adding a dependency
363
+ to every handler leaves them open. They do not leak records, but they publish the
364
+ endpoint list, every schema and exactly which routes want a credential: the map you
365
+ would draw before attacking the rest. Found by reading the store's own rule that
366
+ OpenAPI specs are dev-only, then testing whether this service obeyed it.
367
+ """
368
+ pytest.importorskip("fastapi")
369
+ pytest.importorskip("httpx")
370
+ from fastapi.testclient import TestClient
371
+
372
+ from okl.service import create_app
373
+
374
+ s = Store("sqlite:///:memory:")
375
+
376
+ # ASSERT (1) — with a token, the spec routes are gone entirely (404, not 401: the
377
+ # route does not exist, so there is nothing to probe or brute-force)
378
+ monkeypatch.setenv("OKL_TOKEN", "s3cret")
379
+ closed = TestClient(create_app(store=s))
380
+ for path in ("/openapi.json", "/docs", "/redoc"):
381
+ assert closed.get(path).status_code == 404, f"{path} still served with a token set"
382
+
383
+ # ASSERT (2) — the data routes still work with the credential, so removing the spec
384
+ # did not remove the API
385
+ assert closed.post("/check", json={"repo": "x", "task": "t"},
386
+ headers={"Authorization": "Bearer s3cret"}).status_code == 200
387
+
388
+ # ASSERT (3) — with no token this is someone's laptop, and the interactive docs are
389
+ # useful there. Taking them away unconditionally would be a worse tool for no gain.
390
+ monkeypatch.delenv("OKL_TOKEN", raising=False)
391
+ open_app = TestClient(create_app(store=s))
392
+ assert open_app.get("/openapi.json").status_code == 200
393
+ assert open_app.get("/docs").status_code == 200
394
+
395
+
358
396
  def test_cli_check_fails_closed_when_the_service_refuses_it(tmp_path, monkeypatch, capsys):
359
397
  """A 401 is a REFUSED check, not a clean one: exit 2, no reassuring output.
360
398