Move tool profiles to a root file and version the release as 0.5.0
- Tool profiles come from a committed `.napseer-tools.json` at the
project root (schema napseer.tools.v1). The unreleased tool_profiles
field in .napseer/project.json is removed: released clients reject
unknown locator fields, and 0.4.0 treats any extra file inside .napseer/
as private state needing migration (a test loads the released 0.4.0
check from c427e24 to prove the root file is invisible to it).
Environment still overrides; unusable files warn and fall back to core.
- CLI release 0.5.0 with distribution contract 2026-09-29; the minimum
supported contract stays 2026-09-10. The newer-contract rejection test
now derives its date from the installer's own contract.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d86610da8e
commit
611a4571f3
@@ -12,6 +12,9 @@ sys.path.insert(0, str(SCRIPTS))
|
||||
from napseer_credentials import CredentialStore, atomic_json, read_json
|
||||
from test_user_credentials import provider, worker, outcome
|
||||
|
||||
# Released CLI 0.4.0 source revision (pinned by the Napseer deploy workflow).
|
||||
RELEASED_0_4_0 = 'c427e2432ac85038a4f00b2b888bbdb7c851ada1'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def worktrees(tmp_path):
|
||||
@@ -158,3 +161,52 @@ def test_private_state_cannot_be_redirected_into_checkout(worktrees, monkeypatch
|
||||
main,_=worktrees
|
||||
monkeypatch.setenv('NAPSEER_USER_DATA_DIR',str(main/'private-state'))
|
||||
with pytest.raises(CredentialError,match='outside'):WorkspacePaths(main)
|
||||
|
||||
|
||||
|
||||
def test_root_tools_file_resolves_like_locator_and_is_invisible_to_migration(worktrees, monkeypatch):
|
||||
from napseer_credentials import WorkspacePaths
|
||||
main, _ = worktrees
|
||||
monkeypatch.delenv('NAPSEER_TOOL_PROFILES', raising=False)
|
||||
tools = main/'.napseer-tools.json'
|
||||
committed = b'{"schema": "napseer.tools.v1", "tool_profiles": ["workflow"]}\n'
|
||||
tools.write_bytes(committed)
|
||||
sub = main/'nested'; sub.mkdir()
|
||||
# A worker started below the project root finds the file at the root that
|
||||
# owns .napseer/project.json, the same way it finds the locator.
|
||||
expression = ("{'path':str(m.project_tools_path()),'profiles':sorted(m.tool_profile_configuration()[0]),"
|
||||
"'source':m.tool_profile_configuration()[1],"
|
||||
"'pending':sorted(p.name for p in m.WORKSPACE.migration_sources())}")
|
||||
result = outcome(worker(sub, expression))['result']
|
||||
assert result == {'path': str(tools), 'profiles': ['workflow'], 'source': 'tools_file', 'pending': []}
|
||||
assert set(p.name for p in (main/'.napseer').iterdir()) <= {'project.json', '.gitignore'}
|
||||
paths = WorkspacePaths(main)
|
||||
assert paths.migration_sources() == []
|
||||
paths.require_migrated()
|
||||
paths.migrate()
|
||||
assert tools.read_bytes() == committed
|
||||
|
||||
|
||||
def test_root_tools_file_is_invisible_to_released_0_4_0_migration_check(worktrees, tmp_path):
|
||||
"""CLI 0.4.0 treats any extra .napseer/ entry as private state to migrate."""
|
||||
import importlib.util
|
||||
released = subprocess.run(
|
||||
['git', '-C', str(SCRIPTS.parent.parent), 'show', f'{RELEASED_0_4_0}:resources/scripts/napseer_credentials.py'],
|
||||
capture_output=True)
|
||||
if released.returncode != 0:
|
||||
pytest.skip(f'released 0.4.0 source {RELEASED_0_4_0} is not in this checkout')
|
||||
source = tmp_path/'napseer_credentials_0_4_0.py'
|
||||
source.write_bytes(released.stdout)
|
||||
spec = importlib.util.spec_from_file_location('napseer_credentials_0_4_0', source)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
main, _ = worktrees
|
||||
(main/'.napseer-tools.json').write_text('{"schema": "napseer.tools.v1", "tool_profiles": ["workflow"]}\n')
|
||||
paths = module.WorkspacePaths(main)
|
||||
assert paths.migration_sources() == []
|
||||
paths.require_migrated()
|
||||
# Control: the same file inside .napseer/ would block 0.4.0 clients.
|
||||
(main/'.napseer/tools.json').write_text('{}\n')
|
||||
assert [p.name for p in paths.migration_sources()] == ['tools.json']
|
||||
with pytest.raises(module.CredentialError):
|
||||
paths.require_migrated()
|
||||
|
||||
Reference in New Issue
Block a user