ACTIVITY-WP-0046: grant v2 carries a publish scope (remote, branch, path prefixes inside allowed_paths, fast-forward only) in its canonical grant id; v1 stays local-only with unchanged ids. ACTIVITY-WP-0047: disabled daily-security-flow definition dispatching the the-custodian runner as an ops_run (CUST-WP-0079-T03). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 296847@bnt-lap001 Assistant-Session: 4f997d46-1b9d-4131-9699-0c353a215155
133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
"""Repository-grant v1 validation and identity compatibility."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from activity_core.models import ActionDef, InstructionDef
|
|
from activity_core.orm import OpsRun
|
|
from activity_core.repository_grant import (
|
|
RepositoryGrant,
|
|
RepositoryGrantError,
|
|
validate_repository_grant,
|
|
)
|
|
|
|
|
|
def _grant(**changes: object) -> dict:
|
|
value = {
|
|
"version": "1",
|
|
"allowed_paths": ["docs/", "README.md"],
|
|
"commit_count": {"min": 1, "max": 1},
|
|
"publish": False,
|
|
}
|
|
value.update(changes)
|
|
return value
|
|
|
|
|
|
def test_repository_grant_preserves_payload_and_has_stable_identity() -> None:
|
|
raw = _grant()
|
|
grant = RepositoryGrant.model_validate(raw)
|
|
reordered = RepositoryGrant.model_validate(
|
|
_grant(allowed_paths=["README.md", "docs/"])
|
|
)
|
|
|
|
assert grant.payload() == raw
|
|
assert grant.payload() is not raw
|
|
assert grant.grant_id == reordered.grant_id
|
|
assert grant.acceptance_policy_id == reordered.acceptance_policy_id
|
|
assert grant.grant_id == "af2e7c8275c9ba8c8f78485067f9608e"
|
|
assert grant.acceptance_policy_id == "2ed31282d721f4466bc9ea722067d9d0"
|
|
|
|
|
|
def test_action_and_instruction_parse_repository_grant_as_typed_authority() -> None:
|
|
action = ActionDef(task_template="Do work", repository_grant=_grant())
|
|
instruction = InstructionDef(
|
|
id="do-work",
|
|
trusted_fields=[],
|
|
model="model",
|
|
prompt="Do work",
|
|
output_schema="schema.json",
|
|
repository_grant=_grant(),
|
|
)
|
|
|
|
assert isinstance(action.repository_grant, RepositoryGrant)
|
|
assert isinstance(instruction.repository_grant, RepositoryGrant)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"value",
|
|
[
|
|
_grant(extra="not-allowed"),
|
|
_grant(version=1),
|
|
_grant(allowed_paths=[]),
|
|
_grant(allowed_paths=["docs/", "docs/"]),
|
|
_grant(allowed_paths=["../escape"]),
|
|
_grant(allowed_paths=[".git/config"]),
|
|
_grant(commit_count={"min": True, "max": 1}),
|
|
_grant(commit_count={"min": 2, "max": 1}),
|
|
_grant(commit_count={"min": 1, "max": 33}),
|
|
_grant(publish=True),
|
|
_grant(publish=0),
|
|
],
|
|
)
|
|
def test_repository_grant_rejects_ambiguous_or_unsafe_values(value: dict) -> None:
|
|
with pytest.raises(RepositoryGrantError):
|
|
validate_repository_grant(value)
|
|
|
|
|
|
def test_ops_run_schema_has_separate_grant_and_close_identity_columns() -> None:
|
|
columns = OpsRun.__table__.columns
|
|
|
|
assert columns["repository_grant"].nullable is True
|
|
assert columns["close_intent_digest"].nullable is True
|
|
|
|
|
|
def _publish_grant(**scope_changes: object) -> dict:
|
|
scope = {
|
|
"remote": "origin",
|
|
"branch": "main",
|
|
"path_prefixes": ["checkpoints/"],
|
|
"fast_forward_only": True,
|
|
}
|
|
scope.update(scope_changes)
|
|
return _grant(
|
|
version="2",
|
|
allowed_paths=["checkpoints/"],
|
|
publish=True,
|
|
publish_scope=scope,
|
|
)
|
|
|
|
|
|
def test_v2_grant_carries_publish_scope_in_payload_and_identity() -> None:
|
|
raw = _publish_grant()
|
|
grant = RepositoryGrant.model_validate(raw)
|
|
narrower = RepositoryGrant.model_validate(
|
|
_publish_grant(path_prefixes=["checkpoints/2026/"])
|
|
)
|
|
|
|
assert grant.payload() == raw
|
|
assert grant.grant_id != narrower.grant_id
|
|
assert "publish_scope" not in RepositoryGrant.model_validate(_grant()).payload()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"value",
|
|
[
|
|
_publish_grant(fast_forward_only=False),
|
|
_publish_grant(remote="https://example.org/repo.git"),
|
|
_publish_grant(branch="refs/heads/main"),
|
|
_publish_grant(branch="feature/../main"),
|
|
_publish_grant(branch="-force"),
|
|
_publish_grant(path_prefixes=["docs/"]),
|
|
_publish_grant(path_prefixes=["checkpoints-other/"]),
|
|
_publish_grant(path_prefixes=[]),
|
|
_publish_grant(force=True),
|
|
{**_publish_grant(), "publish": False},
|
|
{**_publish_grant(), "version": "1"},
|
|
_grant(version="2", publish=True),
|
|
_grant(publish_scope=_publish_grant()["publish_scope"]),
|
|
],
|
|
)
|
|
def test_v2_grant_rejects_unsafe_or_inconsistent_publish_scope(value: dict) -> None:
|
|
with pytest.raises(RepositoryGrantError):
|
|
validate_repository_grant(value)
|