keystone-cli 0.1.26__tar.gz → 0.1.27__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 (130) hide show
  1. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/PKG-INFO +1 -1
  2. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/pyproject.toml +2 -2
  3. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/auth/token_store.py +44 -1
  4. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/login.py +9 -1
  5. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/PKG-INFO +1 -1
  6. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/SOURCES.txt +1 -0
  7. keystone_cli-0.1.27/tests/test_config_env_mismatch.py +102 -0
  8. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/setup.cfg +0 -0
  9. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/__init__.py +0 -0
  10. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/__main__.py +0 -0
  11. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/_build_env.py +0 -0
  12. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/_stdio.py +0 -0
  13. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/_version.py +0 -0
  14. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/auth/__init__.py +0 -0
  15. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/auth/device_flow.py +0 -0
  16. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/__init__.py +0 -0
  17. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/agent.py +0 -0
  18. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/dev.py +0 -0
  19. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/kb.py +0 -0
  20. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/tool_credentials.py +0 -0
  21. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/commands/workspace.py +0 -0
  22. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/devloop.py +0 -0
  23. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/platform_client.py +0 -0
  24. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/runner.py +0 -0
  25. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/scaffold.py +0 -0
  26. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/README.md.tmpl +0 -0
  27. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/agent.yaml.tmpl +0 -0
  28. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/env.tmpl +0 -0
  29. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/gitignore.tmpl +0 -0
  30. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/pkg/__init__.py.tmpl +0 -0
  31. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/pkg/graph.py.tmpl +0 -0
  32. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/blank/pyproject.toml.tmpl +0 -0
  33. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/README.md.tmpl +0 -0
  34. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/agent.yaml.tmpl +0 -0
  35. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/env.tmpl +0 -0
  36. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/gitignore.tmpl +0 -0
  37. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/pkg/__init__.py.tmpl +0 -0
  38. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/pkg/graph.py.tmpl +0 -0
  39. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/guardrails/pyproject.toml.tmpl +0 -0
  40. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/README.md.tmpl +0 -0
  41. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/agent.yaml.tmpl +0 -0
  42. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/env.tmpl +0 -0
  43. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/gitignore.tmpl +0 -0
  44. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/pkg/__init__.py.tmpl +0 -0
  45. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/pkg/graph.py.tmpl +0 -0
  46. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/hitl/pyproject.toml.tmpl +0 -0
  47. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/README.md.tmpl +0 -0
  48. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/agent.yaml.tmpl +0 -0
  49. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/env.tmpl +0 -0
  50. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/gitignore.tmpl +0 -0
  51. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/pkg/__init__.py.tmpl +0 -0
  52. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/pkg/graph.py.tmpl +0 -0
  53. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/kb-builder/pyproject.toml.tmpl +0 -0
  54. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/README.md.tmpl +0 -0
  55. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/agent.yaml.tmpl +0 -0
  56. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/env.tmpl +0 -0
  57. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/gitignore.tmpl +0 -0
  58. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/pkg/__init__.py.tmpl +0 -0
  59. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/pkg/graph.py.tmpl +0 -0
  60. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm/pyproject.toml.tmpl +0 -0
  61. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/README.md.tmpl +0 -0
  62. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/agent.yaml.tmpl +0 -0
  63. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/env.tmpl +0 -0
  64. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/gitignore.tmpl +0 -0
  65. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/pkg/__init__.py.tmpl +0 -0
  66. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/pkg/graph.py.tmpl +0 -0
  67. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/llm-streaming/pyproject.toml.tmpl +0 -0
  68. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/README.md.tmpl +0 -0
  69. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/agent.yaml.tmpl +0 -0
  70. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/env.tmpl +0 -0
  71. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/gitignore.tmpl +0 -0
  72. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/pkg/__init__.py.tmpl +0 -0
  73. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/pkg/graph.py.tmpl +0 -0
  74. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory/pyproject.toml.tmpl +0 -0
  75. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/README.md.tmpl +0 -0
  76. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/agent.yaml.tmpl +0 -0
  77. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/env.tmpl +0 -0
  78. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/gitignore.tmpl +0 -0
  79. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/pkg/__init__.py.tmpl +0 -0
  80. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/pkg/graph.py.tmpl +0 -0
  81. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/memory-service/pyproject.toml.tmpl +0 -0
  82. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/README.md.tmpl +0 -0
  83. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/agent.yaml.tmpl +0 -0
  84. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/env.tmpl +0 -0
  85. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/gitignore.tmpl +0 -0
  86. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/pkg/__init__.py.tmpl +0 -0
  87. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/pkg/graph.py.tmpl +0 -0
  88. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/rag-qa/pyproject.toml.tmpl +0 -0
  89. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/README.md.tmpl +0 -0
  90. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/agent.yaml.tmpl +0 -0
  91. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/env.tmpl +0 -0
  92. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/gitignore.tmpl +0 -0
  93. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/pkg/__init__.py.tmpl +0 -0
  94. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/pkg/graph.py.tmpl +0 -0
  95. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/skill/pyproject.toml.tmpl +0 -0
  96. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/README.md.tmpl +0 -0
  97. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/agent.yaml.tmpl +0 -0
  98. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/env.tmpl +0 -0
  99. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/gitignore.tmpl +0 -0
  100. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/pkg/__init__.py.tmpl +0 -0
  101. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/pkg/graph.py.tmpl +0 -0
  102. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli/templates/tool-agent/pyproject.toml.tmpl +0 -0
  103. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/dependency_links.txt +0 -0
  104. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/entry_points.txt +0 -0
  105. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/requires.txt +0 -0
  106. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/src/keystone_cli.egg-info/top_level.txt +0 -0
  107. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_agent.py +0 -0
  108. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_assistant_provenance_and_flags.py +0 -0
  109. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_auth.py +0 -0
  110. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_build_progress.py +0 -0
  111. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_ci_token.py +0 -0
  112. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_deploy.py +0 -0
  113. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_dev.py +0 -0
  114. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_doctor.py +0 -0
  115. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_encoding.py +0 -0
  116. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_init.py +0 -0
  117. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_invoke.py +0 -0
  118. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_login_command.py +0 -0
  119. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_offline.py +0 -0
  120. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_platform_client.py +0 -0
  121. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_publish.py +0 -0
  122. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_retire_delete.py +0 -0
  123. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_runs.py +0 -0
  124. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_status.py +0 -0
  125. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_stdio.py +0 -0
  126. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_threads.py +0 -0
  127. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_tool_credentials.py +0 -0
  128. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_traces.py +0 -0
  129. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_version.py +0 -0
  130. {keystone_cli-0.1.26 → keystone_cli-0.1.27}/tests/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keystone-cli
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: Keystone pro-code agent CLI — author, validate, and run agents locally, then deploy and invoke them on the Keystone platform.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: keystone-agent-sdk<0.11,>=0.10
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "keystone-cli"
7
- version = "0.1.26"
7
+ version = "0.1.27"
8
8
  description = "Keystone pro-code agent CLI — author, validate, and run agents locally, then deploy and invoke them on the Keystone platform."
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [
@@ -45,7 +45,7 @@ where = ["src"]
45
45
  keystone_cli = ["templates/*/*.tmpl", "templates/*/pkg/*.tmpl"]
46
46
 
47
47
  [tool.ruff]
48
- target-version = "0.1.26"
48
+ target-version = "0.1.27"
49
49
  line-length = 120
50
50
 
51
51
  [tool.ruff.lint]
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import json
11
11
  import os
12
+ import sys
12
13
  from dataclasses import asdict, dataclass, fields
13
14
  from pathlib import Path
14
15
 
@@ -85,6 +86,46 @@ class AuthConfig:
85
86
  env: str = ""
86
87
 
87
88
 
89
+ def endpoint_env(cfg: AuthConfig) -> str | None:
90
+ """Which env's BAKED endpoints this config carries (issuer or api_base match), or None.
91
+
92
+ None for an empty config and for custom endpoints (a deliberate ``--api-base`` / ``--issuer``
93
+ pointing somewhere no env bakes), so only a config that is recognisably ANOTHER env's is flagged.
94
+ """
95
+ for env, (issuer, api_base, _agent) in _ENV_DEFAULTS.items():
96
+ if (cfg.issuer and cfg.issuer == issuer) or (cfg.api_base and cfg.api_base.rstrip("/") == api_base):
97
+ return env
98
+ return None
99
+
100
+
101
+ def endpoints_belong_to_other_env(cfg: AuthConfig) -> str | None:
102
+ """The OTHER env whose endpoints this config carries (vs. this package's ``BUILD_ENV``), else None.
103
+
104
+ FDP-3072: the ``env`` stamp alone is not proof. A config stamped ``env=prod`` can still hold the
105
+ dev issuer/api_base (seen 2026-09-30 after installing the prod package over a dev login): the
106
+ stamp matched, so ``login`` kept the cache and every command silently went to dev.
107
+ """
108
+ env = endpoint_env(cfg)
109
+ return env if env is not None and env != BUILD_ENV else None
110
+
111
+
112
+ _warned_mismatch = False
113
+
114
+
115
+ def _warn_if_other_env(cfg: AuthConfig) -> None:
116
+ """One stderr line per process — a warning, never a block (custom endpoints are never flagged)."""
117
+ global _warned_mismatch
118
+ other = endpoints_belong_to_other_env(cfg)
119
+ if other is None or _warned_mismatch:
120
+ return
121
+ _warned_mismatch = True
122
+ print(
123
+ f"⚠ {config_path()} points at the {other} endpoints, but this is the {BUILD_ENV} CLI — "
124
+ f"commands would go to {other}. Run `keystone login` to reset it to {BUILD_ENV}.",
125
+ file=sys.stderr,
126
+ )
127
+
128
+
88
129
  def config_path() -> Path:
89
130
  """Config file path. ``KEYSTONE_CONFIG`` overrides (tests); default ``~/.keystone/config.json``."""
90
131
  override = os.environ.get("KEYSTONE_CONFIG")
@@ -100,7 +141,9 @@ def load() -> AuthConfig:
100
141
  # turned a valid session into "not logged in".
101
142
  data = json.loads(path.read_text(encoding="utf-8"))
102
143
  known = {f.name for f in fields(AuthConfig)}
103
- return AuthConfig(**{k: v for k, v in data.items() if k in known})
144
+ cfg = AuthConfig(**{k: v for k, v in data.items() if k in known})
145
+ _warn_if_other_env(cfg)
146
+ return cfg
104
147
 
105
148
 
106
149
  def _ensure_private_dir(directory: Path) -> None:
@@ -61,7 +61,15 @@ def login(
61
61
  # cached (no silent wrong-realm). Same env keeps the cache; flags still override.
62
62
  cfg = ts.load()
63
63
  same_env = (cfg.env == ts.BUILD_ENV) if cfg.env else (not cfg.issuer or cfg.issuer == ts.DEFAULT_ISSUER)
64
- if not same_env:
64
+ # FDP-3072: the stamp can match while the endpoints are another env's (env=prod holding dev URLs).
65
+ # Treat that as cross-env too, or the stale endpoints survive every login.
66
+ stale_env = ts.endpoints_belong_to_other_env(cfg)
67
+ if stale_env is not None:
68
+ typer.secho(
69
+ f" cached config pointed at the {stale_env} endpoints — resetting to the {ts.BUILD_ENV} defaults",
70
+ fg=typer.colors.YELLOW,
71
+ )
72
+ if not same_env or stale_env is not None:
65
73
  cfg = ts.AuthConfig()
66
74
  cfg.env = ts.BUILD_ENV
67
75
  cfg.issuer = issuer or cfg.issuer or ts.DEFAULT_ISSUER
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keystone-cli
3
- Version: 0.1.26
3
+ Version: 0.1.27
4
4
  Summary: Keystone pro-code agent CLI — author, validate, and run agents locally, then deploy and invoke them on the Keystone platform.
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: keystone-agent-sdk<0.11,>=0.10
@@ -106,6 +106,7 @@ tests/test_assistant_provenance_and_flags.py
106
106
  tests/test_auth.py
107
107
  tests/test_build_progress.py
108
108
  tests/test_ci_token.py
109
+ tests/test_config_env_mismatch.py
109
110
  tests/test_deploy.py
110
111
  tests/test_dev.py
111
112
  tests/test_doctor.py
@@ -0,0 +1,102 @@
1
+ """FDP-3072 — a config whose endpoints belong to ANOTHER env than the installed package.
2
+
3
+ Seen 2026-09-30: the prod package over a dev login left ``env=prod`` with the dev issuer and
4
+ api_base; ``keystone login`` kept them (the stamp matched) and every command went to dev, silently.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+
11
+ import pytest
12
+ from typer.testing import CliRunner
13
+
14
+ from keystone_cli.__main__ import app
15
+ from keystone_cli.auth import token_store as ts
16
+
17
+ runner = CliRunner()
18
+
19
+ DEV_ISSUER, DEV_API, DEV_AGENT = ts._ENV_DEFAULTS["dev"]
20
+ PROD_ISSUER, PROD_API, PROD_AGENT = ts._ENV_DEFAULTS["prod"]
21
+
22
+
23
+ @pytest.fixture(autouse=True)
24
+ def _prod_package(tmp_path, monkeypatch):
25
+ """Pretend the installed package is the prod one, with a private config file."""
26
+ monkeypatch.setenv("KEYSTONE_CONFIG", str(tmp_path / "config.json"))
27
+ monkeypatch.setattr(ts, "BUILD_ENV", "prod")
28
+ monkeypatch.setattr(ts, "DEFAULT_ISSUER", PROD_ISSUER)
29
+ monkeypatch.setattr(ts, "DEFAULT_API_BASE", PROD_API)
30
+ monkeypatch.setattr(ts, "DEFAULT_AGENT_API_BASE", PROD_AGENT)
31
+ monkeypatch.setattr(ts, "_warned_mismatch", False)
32
+ monkeypatch.setattr("keystone_cli.commands.login.pc.get_me", lambda *_a, **_k: {})
33
+
34
+
35
+ def _write(tmp_path, **cfg) -> None:
36
+ (tmp_path / "config.json").write_text(json.dumps(cfg), encoding="utf-8")
37
+
38
+
39
+ def test_endpoint_env_recognises_baked_hosts_only() -> None:
40
+ assert ts.endpoint_env(ts.AuthConfig(issuer=DEV_ISSUER, api_base=DEV_API)) == "dev"
41
+ assert ts.endpoint_env(ts.AuthConfig(api_base=PROD_API + "/")) == "prod"
42
+ assert ts.endpoint_env(ts.AuthConfig(api_base="http://localhost:8080")) is None # custom → never flagged
43
+ assert ts.endpoint_env(ts.AuthConfig()) is None
44
+
45
+
46
+ def test_load_warns_once_when_endpoints_are_another_envs(tmp_path, capsys) -> None:
47
+ _write(tmp_path, env="prod", issuer=DEV_ISSUER, api_base=DEV_API, agent_api_base=DEV_AGENT)
48
+
49
+ ts.load()
50
+ ts.load()
51
+
52
+ err = capsys.readouterr().err
53
+ assert err.count("points at the dev endpoints") == 1
54
+ assert "keystone login" in err
55
+
56
+
57
+ @pytest.mark.parametrize(
58
+ "cfg",
59
+ [
60
+ {"env": "prod", "issuer": PROD_ISSUER, "api_base": PROD_API},
61
+ {"env": "prod", "issuer": "https://kc.local/realms/x", "api_base": "http://localhost:8080"},
62
+ ],
63
+ )
64
+ def test_load_is_silent_for_matching_or_custom_endpoints(tmp_path, capsys, cfg) -> None:
65
+ _write(tmp_path, **cfg)
66
+ ts.load()
67
+ assert capsys.readouterr().err == ""
68
+
69
+
70
+ def test_login_resets_a_prod_stamped_config_holding_dev_endpoints(tmp_path, monkeypatch) -> None:
71
+ _write(
72
+ tmp_path,
73
+ env="prod",
74
+ issuer=DEV_ISSUER,
75
+ api_base=DEV_API,
76
+ agent_api_base=DEV_AGENT,
77
+ workspace_id="dev-ws",
78
+ workspace_name="default-workspace",
79
+ )
80
+ monkeypatch.setattr(
81
+ "keystone_cli.commands.login.df.start_device_flow",
82
+ lambda *_a, **_k: {
83
+ "device_code": "dc",
84
+ "user_code": "WXYZ",
85
+ "verification_uri": "https://kc/device",
86
+ "interval": 1,
87
+ "expires_in": 600,
88
+ },
89
+ )
90
+ monkeypatch.setattr(
91
+ "keystone_cli.commands.login.df.poll_for_token",
92
+ lambda *_a, **_k: {"access_token": "at", "refresh_token": "rt", "expires_in": 300},
93
+ )
94
+
95
+ result = runner.invoke(app, ["login", "--no-browser", "--no-select-workspace"])
96
+
97
+ assert result.exit_code == 0, result.output
98
+ assert "pointed at the dev endpoints" in result.output
99
+ saved = json.loads((tmp_path / "config.json").read_text(encoding="utf-8"))
100
+ assert saved["env"] == "prod"
101
+ assert (saved["issuer"], saved["api_base"], saved["agent_api_base"]) == (PROD_ISSUER, PROD_API, PROD_AGENT)
102
+ assert saved["workspace_id"] == "" # the dev workspace must not survive into prod
File without changes