workstate-protocol 0.1.5__tar.gz → 0.1.7__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 (29) hide show
  1. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/PKG-INFO +1 -1
  2. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/pyproject.toml +1 -1
  3. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/__init__.py +19 -1
  4. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/bootstrap.py +44 -6
  5. workstate_protocol-0.1.7/src/workstate_protocol/env_aliases.py +54 -0
  6. workstate_protocol-0.1.7/src/workstate_protocol/paths.py +48 -0
  7. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/PKG-INFO +1 -1
  8. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/SOURCES.txt +1 -0
  9. workstate_protocol-0.1.7/tests/test_env_aliases.py +39 -0
  10. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_plugin_override_schema.py +1 -1
  11. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_skill_manifest_real_skills.py +2 -2
  12. workstate_protocol-0.1.5/src/workstate_protocol/env_aliases.py +0 -97
  13. workstate_protocol-0.1.5/tests/test_env_aliases.py +0 -98
  14. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/README.md +0 -0
  15. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/setup.cfg +0 -0
  16. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/branch_naming.py +0 -0
  17. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/compaction.py +0 -0
  18. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/handoff.py +0 -0
  19. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/hooks.py +0 -0
  20. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/py.typed +0 -0
  21. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol/skills.py +0 -0
  22. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/dependency_links.txt +0 -0
  23. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/requires.txt +0 -0
  24. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/top_level.txt +0 -0
  25. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_bootstrap_manifest.py +0 -0
  26. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_branch_grammar_registry.py +0 -0
  27. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_branch_naming.py +0 -0
  28. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_handoff_schema.py +0 -0
  29. {workstate_protocol-0.1.5 → workstate_protocol-0.1.7}/tests/test_package_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workstate-protocol
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/darce/workstate
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "workstate-protocol"
7
- version = "0.1.5"
7
+ version = "0.1.7"
8
8
  description = "Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -32,20 +32,36 @@ from .hooks import (
32
32
  StopEvent,
33
33
  UserPromptSubmitEvent,
34
34
  )
35
+ from .paths import (
36
+ CONTRACTS_DIR,
37
+ DOCS_MIRROR_DIR,
38
+ HARNESS_CONTRACT_RELPATH,
39
+ INSTRUCTIONS_RELPATH,
40
+ RULES_DIR,
41
+ RUNTIME_ROOT_DIRNAME,
42
+ docs_mirror_path,
43
+ runtime_root_path,
44
+ )
35
45
  from .skills import SkillManifest, SkillScope
36
46
 
37
- __version__ = "0.1.5"
47
+ __version__ = "0.1.6"
38
48
 
39
49
  __all__ = [
40
50
  "ActiveTask",
41
51
  "BootstrapManifest",
52
+ "CONTRACTS_DIR",
53
+ "DOCS_MIRROR_DIR",
42
54
  "DecisionRef",
55
+ "HARNESS_CONTRACT_RELPATH",
43
56
  "HandoffState",
44
57
  "HandoffStatus",
58
+ "INSTRUCTIONS_RELPATH",
45
59
  "OverlayConfigEntry",
46
60
  "OverlaySurface",
47
61
  "PostToolUseEvent",
48
62
  "PreToolUseEvent",
63
+ "RULES_DIR",
64
+ "RUNTIME_ROOT_DIRNAME",
49
65
  "SessionStartEvent",
50
66
  "SkillManifest",
51
67
  "SkillScope",
@@ -61,6 +77,8 @@ __all__ = [
61
77
  "__version__",
62
78
  "branch_naming",
63
79
  "derive_task_ref_candidates",
80
+ "docs_mirror_path",
64
81
  "format_suggested_branch_name",
65
82
  "resolve_env_alias",
83
+ "runtime_root_path",
66
84
  ]
@@ -27,7 +27,9 @@ class OverlaySurface(BaseModel):
27
27
 
28
28
  model_config = ConfigDict(extra="allow")
29
29
 
30
- path: str = Field(description="Repo-relative surface path (e.g. '.claude/skills/handoff-lifecycle').")
30
+ path: str = Field(
31
+ description="Repo-relative surface path (e.g. '.claude/skills/handoff-lifecycle')."
32
+ )
31
33
  source: Literal["shared", "local", "overlapping", "generated", "lifecycle"] = Field(
32
34
  description=(
33
35
  "Origin tier: 'shared' (symlinked from workstate-system), "
@@ -112,7 +114,9 @@ class PluginOverrideManifest(BaseModel):
112
114
 
113
115
  schema_version: Literal[1] = 1
114
116
  plugin: PluginComponentName
115
- components: PluginOverrideComponents = Field(default_factory=PluginOverrideComponents)
117
+ components: PluginOverrideComponents = Field(
118
+ default_factory=PluginOverrideComponents
119
+ )
116
120
 
117
121
 
118
122
  class ReplaceCommandOp(BaseModel):
@@ -240,10 +244,24 @@ class BootstrapManifest(BaseModel):
240
244
  model_config = ConfigDict(extra="allow")
241
245
 
242
246
  schema_version: int = Field(ge=1, description="Manifest schema version.")
243
- remote_url: str = Field(min_length=1)
244
- remote_ref: str = Field(min_length=1)
245
- remote_sha: Sha40 = Field(
246
- description="Resolved 40-char git SHA at install time.",
247
+ source_kind: Literal["git_overlay", "package"] = Field(
248
+ default="git_overlay",
249
+ description=(
250
+ "Overlay delivery source: 'git_overlay' (a clone checked out at "
251
+ "remote_ref) or 'package' (an installed workstate-system "
252
+ "distribution). Defaults to 'git_overlay' so manifests written "
253
+ "before WS-PKG-DELIVERY-01 validate unchanged."
254
+ ),
255
+ )
256
+ remote_url: str | None = Field(default=None, min_length=1)
257
+ remote_ref: str | None = Field(default=None, min_length=1)
258
+ remote_sha: Sha40 | None = Field(
259
+ default=None,
260
+ description="Resolved 40-char git SHA at install time (git_overlay source).",
261
+ )
262
+ package_version: str | None = Field(
263
+ default=None,
264
+ description="Installed workstate-system distribution version (package source).",
247
265
  )
248
266
  surfaces: list[OverlaySurface] = Field(default_factory=list)
249
267
  configs: list[OverlayConfigEntry] = Field(default_factory=list)
@@ -265,3 +283,23 @@ class BootstrapManifest(BaseModel):
265
283
  "override location."
266
284
  ),
267
285
  )
286
+
287
+ @model_validator(mode="after")
288
+ def _check_source_provenance(self) -> "BootstrapManifest":
289
+ """Each delivery source requires its own provenance fields.
290
+
291
+ ``git_overlay`` (the default, so pre-WS-PKG-DELIVERY-01 manifests keep
292
+ validating) requires the git ``remote_*`` triple; ``package`` requires
293
+ the installed distribution ``package_version``.
294
+ """
295
+ if self.source_kind == "git_overlay":
296
+ missing = [
297
+ name
298
+ for name in ("remote_url", "remote_ref", "remote_sha")
299
+ if not getattr(self, name)
300
+ ]
301
+ if missing:
302
+ raise ValueError("git_overlay manifest requires " + ", ".join(missing))
303
+ elif self.source_kind == "package" and not self.package_version:
304
+ raise ValueError("package manifest requires package_version")
305
+ return self
@@ -0,0 +1,54 @@
1
+ """Canonical Workstate env-var resolution helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping
7
+ from typing import overload
8
+
9
+ __all__ = ["resolve_env_alias"]
10
+
11
+
12
+ def _non_empty(value: str | None) -> str | None:
13
+ if value is None:
14
+ return None
15
+ stripped = value.strip()
16
+ return stripped or None
17
+
18
+
19
+ @overload
20
+ def resolve_env_alias(
21
+ canonical: str,
22
+ *,
23
+ env: Mapping[str, str] | None = ...,
24
+ default: str,
25
+ ) -> str: ...
26
+
27
+
28
+ @overload
29
+ def resolve_env_alias(
30
+ canonical: str,
31
+ *,
32
+ env: Mapping[str, str] | None = ...,
33
+ default: None = ...,
34
+ ) -> str | None: ...
35
+
36
+
37
+ def resolve_env_alias(
38
+ canonical: str,
39
+ *,
40
+ env: Mapping[str, str] | None = None,
41
+ default: str | None = None,
42
+ ) -> str | None:
43
+ """Resolve a canonical Workstate env var.
44
+
45
+ Blank/whitespace-only values are treated as unset, matching the existing
46
+ ``_first_non_empty_env`` behaviour in the handoff package.
47
+ """
48
+
49
+ source = os.environ if env is None else env
50
+
51
+ canonical_value = _non_empty(source.get(canonical))
52
+ if canonical_value is not None:
53
+ return canonical_value
54
+ return default
@@ -0,0 +1,48 @@
1
+ """Canonical Workstate runtime + doc-mirror path roots — single source of truth.
2
+
3
+ The runtime install directory is ``.workstate/`` and the mirrored docs/contracts
4
+ path is ``docs/workstate/``. Every package resolves these through this module so
5
+ the names live in exactly one place: a future path change is a one-line flip
6
+ here, not a repo-wide sweep.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ # Runtime install root — bootstrap materializes overlay surfaces and the remote
14
+ # clone under ``<target>/.workstate/``.
15
+ RUNTIME_ROOT_DIRNAME = ".workstate"
16
+
17
+ # Mirrored docs / contracts path — the SHARED_SURFACES consumed at install time
18
+ # (rules, contracts, templates) live under ``docs/workstate/``.
19
+ DOCS_MIRROR_DIR = "docs/workstate"
20
+
21
+ # Common derived locations under the canonical docs mirror.
22
+ CONTRACTS_DIR = f"{DOCS_MIRROR_DIR}/contracts"
23
+ RULES_DIR = f"{DOCS_MIRROR_DIR}/rules"
24
+ HARNESS_CONTRACT_RELPATH = Path(CONTRACTS_DIR) / "harness-protocol.yaml"
25
+ INSTRUCTIONS_RELPATH = Path(DOCS_MIRROR_DIR) / "instructions.md"
26
+
27
+ __all__ = [
28
+ "CONTRACTS_DIR",
29
+ "DOCS_MIRROR_DIR",
30
+ "HARNESS_CONTRACT_RELPATH",
31
+ "INSTRUCTIONS_RELPATH",
32
+ "RULES_DIR",
33
+ "RUNTIME_ROOT_DIRNAME",
34
+ "docs_mirror_path",
35
+ "runtime_root_path",
36
+ ]
37
+
38
+
39
+ def docs_mirror_path(*parts: str) -> Path:
40
+ """Return a path under the canonical docs mirror (``docs/workstate/...``)."""
41
+
42
+ return Path(DOCS_MIRROR_DIR, *parts)
43
+
44
+
45
+ def runtime_root_path(base: Path, *parts: str) -> Path:
46
+ """Return a path under ``<base>/.workstate/...``."""
47
+
48
+ return base.joinpath(RUNTIME_ROOT_DIRNAME, *parts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workstate-protocol
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/darce/workstate
@@ -7,6 +7,7 @@ src/workstate_protocol/compaction.py
7
7
  src/workstate_protocol/env_aliases.py
8
8
  src/workstate_protocol/handoff.py
9
9
  src/workstate_protocol/hooks.py
10
+ src/workstate_protocol/paths.py
10
11
  src/workstate_protocol/py.typed
11
12
  src/workstate_protocol/skills.py
12
13
  src/workstate_protocol.egg-info/PKG-INFO
@@ -0,0 +1,39 @@
1
+ """Tests for canonical Workstate env-var reads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from workstate_protocol import resolve_env_alias
8
+ from workstate_protocol import env_aliases
9
+
10
+
11
+ def test_canonical_name_wins():
12
+ env = {"WORKSTATE_STATE_DIR": "/new", "AGENT_HANDOFF_STATE_DIR": "/old"}
13
+ value = resolve_env_alias("WORKSTATE_STATE_DIR", env=env)
14
+ assert value == "/new"
15
+
16
+
17
+ def test_legacy_name_is_ignored():
18
+ env = {"AGENT_HANDOFF_STATE_DIR": "/old"}
19
+ assert resolve_env_alias("WORKSTATE_STATE_DIR", env=env) is None
20
+
21
+
22
+ def test_legacy_args_are_not_supported():
23
+ with pytest.raises(TypeError):
24
+ resolve_env_alias("WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env={})
25
+
26
+
27
+ def test_warn_once_reset_hook_is_removed():
28
+ assert not hasattr(env_aliases, "reset_alias_warnings")
29
+
30
+
31
+ def test_blank_canonical_returns_default_not_legacy():
32
+ env = {"WORKSTATE_STATE_DIR": " ", "AGENT_HANDOFF_STATE_DIR": "/old"}
33
+ value = resolve_env_alias("WORKSTATE_STATE_DIR", env=env, default="/fallback")
34
+ assert value == "/fallback"
35
+
36
+
37
+ def test_unset_returns_default():
38
+ assert resolve_env_alias("WORKSTATE_STATE_DIR", env={}) is None
39
+ assert resolve_env_alias("WORKSTATE_STATE_DIR", env={}, default="/fallback") == "/fallback"
@@ -111,7 +111,7 @@ def test_lock_models_roundtrip() -> None:
111
111
  "schema_version": 1,
112
112
  "plugin": "workstate-system",
113
113
  "base_remote_sha": "d" * 40,
114
- "effective_root": ".agentic/generated/plugins/workstate-system/effective/claude",
114
+ "effective_root": ".workstate/generated/plugins/workstate-system/effective/claude",
115
115
  "components": [
116
116
  {
117
117
  "component_kind": "skill",
@@ -28,12 +28,12 @@ def _resolve_skills_root() -> pathlib.Path | None:
28
28
  """Locate ``packages/workstate-system/skills`` robustly.
29
29
 
30
30
  Resolution order:
31
- 1. ``AGENTIC_SYSTEM_SKILLS_ROOT`` env var override.
31
+ 1. ``WORKSTATE_SYSTEM_SKILLS_ROOT`` env var override.
32
32
  2. Walk up from this file looking for a sibling
33
33
  ``packages/workstate-system`` directory.
34
34
  3. Sibling-package fallback two levels up.
35
35
  """
36
- env_override = os.environ.get("AGENTIC_SYSTEM_SKILLS_ROOT")
36
+ env_override = os.environ.get("WORKSTATE_SYSTEM_SKILLS_ROOT")
37
37
  if env_override:
38
38
  candidate = pathlib.Path(env_override).expanduser()
39
39
  return candidate if candidate.is_dir() else None
@@ -1,97 +0,0 @@
1
- """Tier-4 env-var alias resolution for the Workstate rebrand.
2
-
3
- implementation note Slice C / B4. During the one-release cutover from the legacy
4
- ``AGENT_HANDOFF_*`` / ``AGENT_ORCHESTRATOR_*`` / ``AGENTIC_*`` /
5
- ``MCP_AGENT_HANDOFF_*`` env-var names to the canonical ``WORKSTATE_*`` prefix,
6
- :func:`resolve_env_alias` reads the new name first, then falls back to any
7
- legacy alias, emitting exactly one :class:`DeprecationWarning` per legacy name
8
- that is actually read. The write side (exports, generated config) always sets
9
- the new ``WORKSTATE_*`` name only — this module is read-side compatibility so
10
- existing shells / CI that still export the old names keep working through the
11
- cutover release, with a nudge to migrate.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import os
17
- import warnings
18
- from collections.abc import Mapping
19
- from typing import overload
20
-
21
- __all__ = ["resolve_env_alias", "reset_alias_warnings"]
22
-
23
- # Legacy names already warned about, so the deprecation nudge fires once per
24
- # process regardless of how many call sites read the same var.
25
- _warned_legacy: set[str] = set()
26
-
27
-
28
- def reset_alias_warnings() -> None:
29
- """Clear the warn-once ledger. Intended for tests."""
30
-
31
- _warned_legacy.clear()
32
-
33
-
34
- def _non_empty(value: str | None) -> str | None:
35
- if value is None:
36
- return None
37
- stripped = value.strip()
38
- return stripped or None
39
-
40
-
41
- @overload
42
- def resolve_env_alias(
43
- canonical: str,
44
- *legacy: str,
45
- env: Mapping[str, str] | None = ...,
46
- default: str,
47
- ) -> str: ...
48
-
49
-
50
- @overload
51
- def resolve_env_alias(
52
- canonical: str,
53
- *legacy: str,
54
- env: Mapping[str, str] | None = ...,
55
- default: None = ...,
56
- ) -> str | None: ...
57
-
58
-
59
- def resolve_env_alias(
60
- canonical: str,
61
- *legacy: str,
62
- env: Mapping[str, str] | None = None,
63
- default: str | None = None,
64
- ) -> str | None:
65
- """Resolve an env var across its canonical and legacy alias names.
66
-
67
- Precedence: the canonical ``WORKSTATE_*`` name wins when set to a
68
- non-blank value. Otherwise the first non-blank legacy alias (in the order
69
- given) is returned, and a :class:`DeprecationWarning` is emitted once per
70
- process for that legacy name, naming the ``canonical`` replacement. When
71
- nothing is set, ``default`` is returned.
72
-
73
- Blank/whitespace-only values are treated as unset, matching the existing
74
- ``_first_non_empty_env`` behaviour in the handoff package.
75
- """
76
-
77
- source = os.environ if env is None else env
78
-
79
- canonical_value = _non_empty(source.get(canonical))
80
- if canonical_value is not None:
81
- return canonical_value
82
-
83
- for name in legacy:
84
- legacy_value = _non_empty(source.get(name))
85
- if legacy_value is not None:
86
- if name not in _warned_legacy:
87
- _warned_legacy.add(name)
88
- warnings.warn(
89
- f"Environment variable {name} is deprecated; "
90
- f"set {canonical} instead. The legacy name is read for "
91
- f"one release and will be removed.",
92
- DeprecationWarning,
93
- stacklevel=2,
94
- )
95
- return legacy_value
96
-
97
- return default
@@ -1,98 +0,0 @@
1
- """Tests for the Tier-4 env-var alias shim.
2
-
3
- implementation note Slice C / B4: ``workstate_protocol.env_aliases`` resolves the
4
- canonical ``WORKSTATE_*`` env-var name first, then falls back to any legacy
5
- alias (``AGENT_HANDOFF_*`` / ``AGENT_ORCHESTRATOR_*`` / ``AGENTIC_*`` /
6
- ``MCP_AGENT_HANDOFF_*``) for one release, emitting exactly one
7
- ``DeprecationWarning`` per legacy name that is actually read. Exports always
8
- set the new name only; this shim is read-side compatibility during cutover.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import warnings
14
-
15
- import pytest
16
-
17
- from workstate_protocol import resolve_env_alias
18
- from workstate_protocol.env_aliases import reset_alias_warnings
19
-
20
-
21
- @pytest.fixture(autouse=True)
22
- def _clean_warn_state():
23
- reset_alias_warnings()
24
- yield
25
- reset_alias_warnings()
26
-
27
-
28
- def test_canonical_name_wins_without_warning():
29
- env = {"WORKSTATE_STATE_DIR": "/new", "AGENT_HANDOFF_STATE_DIR": "/old"}
30
- with warnings.catch_warnings():
31
- warnings.simplefilter("error", DeprecationWarning)
32
- value = resolve_env_alias(
33
- "WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env=env
34
- )
35
- assert value == "/new"
36
-
37
-
38
- def test_legacy_fallback_emits_one_deprecation_warning():
39
- env = {"AGENT_HANDOFF_STATE_DIR": "/old"}
40
- with warnings.catch_warnings(record=True) as caught:
41
- warnings.simplefilter("always", DeprecationWarning)
42
- value = resolve_env_alias(
43
- "WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env=env
44
- )
45
- assert value == "/old"
46
- dep = [w for w in caught if issubclass(w.category, DeprecationWarning)]
47
- assert len(dep) == 1
48
- msg = str(dep[0].message)
49
- # The warning must name both the deprecated and the canonical replacement.
50
- assert "AGENT_HANDOFF_STATE_DIR" in msg
51
- assert "WORKSTATE_STATE_DIR" in msg
52
-
53
-
54
- def test_warn_once_per_legacy_name():
55
- env = {"AGENT_HANDOFF_STATE_DIR": "/old"}
56
- with warnings.catch_warnings(record=True) as caught:
57
- warnings.simplefilter("always", DeprecationWarning)
58
- first = resolve_env_alias(
59
- "WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env=env
60
- )
61
- second = resolve_env_alias(
62
- "WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env=env
63
- )
64
- assert first == second == "/old"
65
- dep = [w for w in caught if issubclass(w.category, DeprecationWarning)]
66
- assert len(dep) == 1
67
-
68
-
69
- def test_first_present_legacy_wins_among_multiple_aliases():
70
- env = {"AGENTIC_LANE_ID": "lane-2"}
71
- value = resolve_env_alias(
72
- "WORKSTATE_LANE_ID", "AGENT_HANDOFF_LANE_ID", "AGENTIC_LANE_ID", env=env
73
- )
74
- assert value == "lane-2"
75
-
76
-
77
- def test_blank_canonical_falls_back_to_legacy():
78
- env = {"WORKSTATE_STATE_DIR": " ", "AGENT_HANDOFF_STATE_DIR": "/old"}
79
- value = resolve_env_alias("WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env=env)
80
- assert value == "/old"
81
-
82
-
83
- def test_unset_returns_default_without_warning():
84
- with warnings.catch_warnings():
85
- warnings.simplefilter("error", DeprecationWarning)
86
- assert (
87
- resolve_env_alias("WORKSTATE_STATE_DIR", "AGENT_HANDOFF_STATE_DIR", env={})
88
- is None
89
- )
90
- assert (
91
- resolve_env_alias(
92
- "WORKSTATE_STATE_DIR",
93
- "AGENT_HANDOFF_STATE_DIR",
94
- env={},
95
- default="/fallback",
96
- )
97
- == "/fallback"
98
- )