agentic-runner 3.9.0__tar.gz → 3.11.0__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.
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/PKG-INFO +1 -1
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/__init__.py +1 -1
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/activities.py +314 -12
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/callback.py +29 -1
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/cli.py +16 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/contracts.py +15 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/fake_workspace.py +13 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/workspace.py +31 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/fastapi_client.py +18 -2
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/.gitignore +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/LICENSE +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/README.md +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/pyproject.toml +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/attempts.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/auth_mode.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/build.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/child_watcher.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/cli_floor.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/config.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/credentials.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/device_login_activities.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/egress.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/harness_self_test.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/heartbeat_link.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/hooks.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/host_store.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/evidence.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/auth.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/fake_client.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/gh_client.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/oauth/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/oauth/fake_provider.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/lifecycle.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/llm_proxy.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/mcp.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/message_store.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/oauth_connectors.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/private_state.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/py.typed +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/recipient_key_secret.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/registration.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/runtime/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/runtime/verifier_command.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/sealed_box.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/service.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/__main__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/control_plane.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/plugin.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/test_conformance.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/workflow.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/tiny_http.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/triage_activities.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/usage_windows.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/user_sources.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/__init__.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/_runtime_support.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/acp_runtime.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/agent_runtime.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/claude_runtime.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/claude_sign_in.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/codex_runtime.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/command_policy.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/contract_device_login.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/contract_isolation.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/harness_outcome.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/harness_usage.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/mcp_config.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/settings.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/skills.py +0 -0
- {agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workstation.py +0 -0
|
@@ -64,6 +64,8 @@ from agentic_runner.callback import (
|
|
|
64
64
|
MessageListResponse,
|
|
65
65
|
MessageSendRequest,
|
|
66
66
|
MessageSendResponse,
|
|
67
|
+
RepoRequest,
|
|
68
|
+
RepoResponse,
|
|
67
69
|
VerbRequest,
|
|
68
70
|
VerbResponse,
|
|
69
71
|
)
|
|
@@ -90,6 +92,7 @@ from agentic_runner.hooks import (
|
|
|
90
92
|
)
|
|
91
93
|
from agentic_runner.integrations.git.contracts import (
|
|
92
94
|
AdoptWorkspaceRequest,
|
|
95
|
+
CheckoutDefaultBranchRequest,
|
|
93
96
|
CheckoutWorkBranchRequest,
|
|
94
97
|
CloneWorkspaceRequest,
|
|
95
98
|
CommitAllRequest,
|
|
@@ -182,6 +185,7 @@ from agentic_runner_contracts.activity_io import (
|
|
|
182
185
|
MemberDirectiveInput,
|
|
183
186
|
MemberDirectiveOutput,
|
|
184
187
|
OwnerConfirmationPending,
|
|
188
|
+
ProductBinding,
|
|
185
189
|
ProposedLesson,
|
|
186
190
|
PullRequestCloseInput,
|
|
187
191
|
PullRequestCloseOutput,
|
|
@@ -244,6 +248,7 @@ from agentic_runner_contracts.github_port import (
|
|
|
244
248
|
ReviewRequest,
|
|
245
249
|
)
|
|
246
250
|
from agentic_runner_contracts.grants import (
|
|
251
|
+
BRANCH_VERB,
|
|
247
252
|
CHANNEL_READ_VERB,
|
|
248
253
|
CHANNEL_RESOURCE_TYPE,
|
|
249
254
|
CHANNEL_WRITE_VERB,
|
|
@@ -252,6 +257,7 @@ from agentic_runner_contracts.grants import (
|
|
|
252
257
|
PR_OPEN_VERB,
|
|
253
258
|
PR_REVIEW_VERB,
|
|
254
259
|
PUSH_VERB,
|
|
260
|
+
READ_VERB,
|
|
255
261
|
REPO_RESOURCE_TYPE,
|
|
256
262
|
UNENFORCED_SNAPSHOT,
|
|
257
263
|
Decision,
|
|
@@ -264,6 +270,7 @@ from agentic_runner_contracts.routing import (
|
|
|
264
270
|
RoutingRefusedError,
|
|
265
271
|
RunnerRoutingIdentity,
|
|
266
272
|
assert_routed,
|
|
273
|
+
selector_satisfied,
|
|
267
274
|
)
|
|
268
275
|
from agentic_runner_contracts.runtime_context import (
|
|
269
276
|
EGRESS_ALLOW_LIST_KEY,
|
|
@@ -287,6 +294,12 @@ _MERGE_ERROR_SUMMARY_LIMIT = 512
|
|
|
287
294
|
|
|
288
295
|
_GRANT_EVALUATION_SOURCE = "ralph.grant_evaluation"
|
|
289
296
|
|
|
297
|
+
# ADR-0018 §5: the verbs that write to a repository, and so may touch only the Product a
|
|
298
|
+
# Work Record is bound to. A read ranges over every repository in reach.
|
|
299
|
+
_REPOSITORY_WRITE_VERBS = frozenset(
|
|
300
|
+
{BRANCH_VERB, PUSH_VERB, PR_OPEN_VERB, PR_REVIEW_VERB, PR_MERGE_VERB, PR_COMMENT_VERB}
|
|
301
|
+
)
|
|
302
|
+
|
|
290
303
|
_DEFAULT_CLI_KIND = "codex_cli"
|
|
291
304
|
|
|
292
305
|
_CONTRACT_WIPE_SOURCE = "ralph.contract_wipe"
|
|
@@ -504,6 +517,11 @@ class RunnerRalphFastApiClient(Protocol):
|
|
|
504
517
|
async def get_question(self, question_id: str) -> dict[str, Any]:
|
|
505
518
|
"""A Question and its answer, for the Directive its hold wakes (PRD issue 60)."""
|
|
506
519
|
|
|
520
|
+
async def bind_product(
|
|
521
|
+
self, work_record_id: str, *, repository: str, base_branch: str
|
|
522
|
+
) -> dict[str, Any]:
|
|
523
|
+
"""The first `repo.branch` binds the Work Record's Product (ADR-0018 §5)."""
|
|
524
|
+
|
|
507
525
|
async def get_directive_usage(
|
|
508
526
|
self,
|
|
509
527
|
work_record_id: str,
|
|
@@ -1206,6 +1224,49 @@ def _with_question[Output: (BranchPullRequestOutput, FixDirectiveOutput)](
|
|
|
1206
1224
|
return replace(output, question=asked[0]) if asked else output
|
|
1207
1225
|
|
|
1208
1226
|
|
|
1227
|
+
def _with_binding(output: FixDirectiveOutput, bound: list[ProductBinding]) -> FixDirectiveOutput:
|
|
1228
|
+
"""The Product the attempt's `repo branch` bound rides out on the output (ADR-0018 §5),
|
|
1229
|
+
and the bound repository's checkout is the Workspace from here on (§7)."""
|
|
1230
|
+
|
|
1231
|
+
if not bound or not output.workspace_path:
|
|
1232
|
+
return output
|
|
1233
|
+
binding = bound[0]
|
|
1234
|
+
return replace(
|
|
1235
|
+
output,
|
|
1236
|
+
product_binding=binding,
|
|
1237
|
+
workspace_path=str(_repository_checkout(Path(output.workspace_path), binding.repository)),
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
def _repository_checkout(workspace_path: Path, repository: str) -> Path:
|
|
1242
|
+
"""Where an Organisation-scoped Work Record checks ``repository`` out (ADR-0018 §7).
|
|
1243
|
+
|
|
1244
|
+
Beneath its own Workspace, keyed on the repository's name. The Contract owns that
|
|
1245
|
+
directory and its Directive is running while the Runner clones, so a component it
|
|
1246
|
+
turned into a symlink is refused rather than followed out of the Workspace.
|
|
1247
|
+
"""
|
|
1248
|
+
|
|
1249
|
+
owner, name = _split_repo(repository)
|
|
1250
|
+
root = workspace_path.resolve()
|
|
1251
|
+
checkout = root / owner / name
|
|
1252
|
+
for component in (root / owner, checkout):
|
|
1253
|
+
if component.is_symlink():
|
|
1254
|
+
raise GitWorkspacePolicyError(f"'{component}' is a symlink; refusing to check out")
|
|
1255
|
+
return checkout
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def _bound_checkout(workspace_path: Path, carried: str) -> Path:
|
|
1259
|
+
"""The Workspace a Directive after binding runs in: the bound checkout the previous
|
|
1260
|
+
Directive handed back, when it lies beneath this Work Record's own Workspace."""
|
|
1261
|
+
|
|
1262
|
+
if not carried:
|
|
1263
|
+
return workspace_path
|
|
1264
|
+
checkout = Path(carried).resolve()
|
|
1265
|
+
if workspace_path.resolve() in checkout.parents and checkout.is_dir():
|
|
1266
|
+
return checkout
|
|
1267
|
+
return workspace_path
|
|
1268
|
+
|
|
1269
|
+
|
|
1209
1270
|
def _question_prompt(question: Mapping[str, Any]) -> str:
|
|
1210
1271
|
"""What the woken Agent knows of its Question: the text, and the answer or its
|
|
1211
1272
|
absence -- and, on silence, the two moves it has (PRD issue 60, 19: nothing ends on
|
|
@@ -1948,10 +2009,13 @@ class RunnerRalphActivities:
|
|
|
1948
2009
|
)
|
|
1949
2010
|
contract_id = runtime_state.contract_id
|
|
1950
2011
|
await self._record_uid_allocation(request.work_record_id, contract_id)
|
|
1951
|
-
workspace_path =
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2012
|
+
workspace_path = _bound_checkout(
|
|
2013
|
+
self._prepare_workspace(
|
|
2014
|
+
workspace_root=workspace_root,
|
|
2015
|
+
contract_id=contract_id,
|
|
2016
|
+
work_record_id=request.work_record_id,
|
|
2017
|
+
),
|
|
2018
|
+
request.workspace_path,
|
|
1955
2019
|
)
|
|
1956
2020
|
runtime_state = replace(runtime_state, workspace_path=workspace_path)
|
|
1957
2021
|
attempt = await attempt_stack.enter_async_context(
|
|
@@ -2524,6 +2588,7 @@ class RunnerRalphActivities:
|
|
|
2524
2588
|
reserve_max_tokens: int | None = None,
|
|
2525
2589
|
mcp_plan: McpPlan | None = None,
|
|
2526
2590
|
asked: list[QuestionAsked] | None = None,
|
|
2591
|
+
bound: list[ProductBinding] | None = None,
|
|
2527
2592
|
) -> AsyncIterator[_DirectiveAttempt]:
|
|
2528
2593
|
"""Open one attempt's hook lifecycle and its callback socket (PRD issue 45).
|
|
2529
2594
|
|
|
@@ -2588,6 +2653,7 @@ class RunnerRalphActivities:
|
|
|
2588
2653
|
workspace_path,
|
|
2589
2654
|
directive_number=directive_number,
|
|
2590
2655
|
asked=asked,
|
|
2656
|
+
bound=bound,
|
|
2591
2657
|
),
|
|
2592
2658
|
uid=uid,
|
|
2593
2659
|
)
|
|
@@ -3091,6 +3157,7 @@ class RunnerRalphActivities:
|
|
|
3091
3157
|
*,
|
|
3092
3158
|
directive_number: int = 0,
|
|
3093
3159
|
asked: list[QuestionAsked] | None = None,
|
|
3160
|
+
bound: list[ProductBinding] | None = None,
|
|
3094
3161
|
) -> CallbackHandlers:
|
|
3095
3162
|
"""What ``agentic-runner annotate | artifact upload | verb`` actually do.
|
|
3096
3163
|
|
|
@@ -3154,7 +3221,11 @@ class RunnerRalphActivities:
|
|
|
3154
3221
|
async def verb(request: VerbRequest) -> VerbResponse:
|
|
3155
3222
|
try:
|
|
3156
3223
|
decision = await self._evaluate_verb(
|
|
3157
|
-
work_record_id,
|
|
3224
|
+
work_record_id,
|
|
3225
|
+
state,
|
|
3226
|
+
verb=request.verb,
|
|
3227
|
+
identifier=request.resource,
|
|
3228
|
+
bound_product=bound[0].product_id if bound else None,
|
|
3158
3229
|
)
|
|
3159
3230
|
except SeamUnavailableError as error:
|
|
3160
3231
|
# The Directive asked over its own socket, so it is answered rather than
|
|
@@ -3321,6 +3392,18 @@ class RunnerRalphActivities:
|
|
|
3321
3392
|
addressed_to=str(answer.get("addressed_to") or ""),
|
|
3322
3393
|
)
|
|
3323
3394
|
|
|
3395
|
+
bindings: list[ProductBinding] = [] if bound is None else bound
|
|
3396
|
+
|
|
3397
|
+
async def repo_read(request: RepoRequest) -> RepoResponse:
|
|
3398
|
+
return await self._repo_callback(
|
|
3399
|
+
work_record_id, state, workspace_path, request, verb=READ_VERB, bound=bindings
|
|
3400
|
+
)
|
|
3401
|
+
|
|
3402
|
+
async def repo_branch(request: RepoRequest) -> RepoResponse:
|
|
3403
|
+
return await self._repo_callback(
|
|
3404
|
+
work_record_id, state, workspace_path, request, verb=BRANCH_VERB, bound=bindings
|
|
3405
|
+
)
|
|
3406
|
+
|
|
3324
3407
|
return CallbackHandlers(
|
|
3325
3408
|
annotate=annotate,
|
|
3326
3409
|
artifact=artifact,
|
|
@@ -3328,6 +3411,8 @@ class RunnerRalphActivities:
|
|
|
3328
3411
|
message_send=message_send,
|
|
3329
3412
|
message_list=message_list,
|
|
3330
3413
|
ask=ask,
|
|
3414
|
+
repo_read=repo_read,
|
|
3415
|
+
repo_branch=repo_branch,
|
|
3331
3416
|
)
|
|
3332
3417
|
|
|
3333
3418
|
def _open_store(self, state: _RuntimeContextState) -> tuple[MessageStore | None, str]:
|
|
@@ -3673,6 +3758,7 @@ class RunnerRalphActivities:
|
|
|
3673
3758
|
verb: str,
|
|
3674
3759
|
identifier: str,
|
|
3675
3760
|
resource_type: str = REPO_RESOURCE_TYPE,
|
|
3761
|
+
bound_product: str | None = None,
|
|
3676
3762
|
) -> VerbDecision:
|
|
3677
3763
|
"""The one local Grant evaluation, and the Evidence Event that records it.
|
|
3678
3764
|
|
|
@@ -3686,19 +3772,228 @@ class RunnerRalphActivities:
|
|
|
3686
3772
|
await self._require_live_link(
|
|
3687
3773
|
work_record_id, state, verb=verb, identifier=identifier, resource_type=resource_type
|
|
3688
3774
|
)
|
|
3775
|
+
snapshot = self._current_snapshot(state)
|
|
3689
3776
|
decision = decide_verb(
|
|
3690
|
-
|
|
3777
|
+
snapshot,
|
|
3691
3778
|
verb=verb,
|
|
3692
3779
|
identifier=identifier,
|
|
3693
3780
|
resource_type=resource_type,
|
|
3694
3781
|
)
|
|
3782
|
+
evidence = decision.evidence()
|
|
3783
|
+
refusal = (
|
|
3784
|
+
None
|
|
3785
|
+
if decision.decision is Decision.DENY or resource_type != REPO_RESOURCE_TYPE
|
|
3786
|
+
else self._product_scope_refusal(
|
|
3787
|
+
state,
|
|
3788
|
+
snapshot,
|
|
3789
|
+
verb=verb,
|
|
3790
|
+
identifier=identifier,
|
|
3791
|
+
bound_product=bound_product or state.product_id,
|
|
3792
|
+
)
|
|
3793
|
+
)
|
|
3794
|
+
if refusal is not None:
|
|
3795
|
+
reason, detail = refusal
|
|
3796
|
+
decision = replace(
|
|
3797
|
+
decision,
|
|
3798
|
+
decision=Decision.DENY,
|
|
3799
|
+
reason=reason,
|
|
3800
|
+
deciding_link=None,
|
|
3801
|
+
deciding_entry=None,
|
|
3802
|
+
)
|
|
3803
|
+
evidence = {**decision.evidence(), **detail}
|
|
3695
3804
|
await self._fastapi_client.append_evidence(
|
|
3696
3805
|
work_record_id,
|
|
3697
3806
|
source=_GRANT_EVALUATION_SOURCE,
|
|
3698
|
-
payload=
|
|
3807
|
+
payload=evidence,
|
|
3699
3808
|
)
|
|
3700
3809
|
return decision
|
|
3701
3810
|
|
|
3811
|
+
def _product_scope_refusal(
|
|
3812
|
+
self,
|
|
3813
|
+
state: _RuntimeContextState,
|
|
3814
|
+
snapshot: GrantSnapshot,
|
|
3815
|
+
*,
|
|
3816
|
+
verb: str,
|
|
3817
|
+
identifier: str,
|
|
3818
|
+
bound_product: str | None,
|
|
3819
|
+
) -> tuple[str, dict[str, object]] | None:
|
|
3820
|
+
"""Why a verb the Grant allows is still refused for the Product it touches.
|
|
3821
|
+
|
|
3822
|
+
Two rules on top of the Grant (ADR-0018), neither of which widens anything:
|
|
3823
|
+
|
|
3824
|
+
- §5: once a Work Record is bound, a write to a repository of a *second* Product
|
|
3825
|
+
is refused -- one branch and one pull request per Work Record (ADR-0012), and
|
|
3826
|
+
work across Products is an Epic, so the remedy is a child on that Product.
|
|
3827
|
+
- §6: before binding, the Runner was routed on the Contract's selector alone, so
|
|
3828
|
+
before any verb on a repository it checks its own tags against the selector of
|
|
3829
|
+
the Product that owns it, and fails closed. After binding, routing carries the
|
|
3830
|
+
Product's selector and ``assert_routed`` already holds it.
|
|
3831
|
+
|
|
3832
|
+
``None`` when neither applies, including a repository no registry row in the
|
|
3833
|
+
snapshot names a Product for: the Grant has already answered that one.
|
|
3834
|
+
"""
|
|
3835
|
+
|
|
3836
|
+
product_id = snapshot.product_of(identifier=identifier)
|
|
3837
|
+
if product_id is None:
|
|
3838
|
+
return None
|
|
3839
|
+
if bound_product and verb in _REPOSITORY_WRITE_VERBS and product_id != bound_product:
|
|
3840
|
+
return (
|
|
3841
|
+
f"this Work Record is bound to Product {bound_product}, and {identifier} "
|
|
3842
|
+
f"belongs to Product {product_id}: open a child Work Record on that Product "
|
|
3843
|
+
"with work.open",
|
|
3844
|
+
{
|
|
3845
|
+
"event": "work_record.second_product_refused",
|
|
3846
|
+
"bound_product_id": bound_product,
|
|
3847
|
+
"product_id": product_id,
|
|
3848
|
+
"remedy": "work.open",
|
|
3849
|
+
},
|
|
3850
|
+
)
|
|
3851
|
+
if state.repository or self._routing_identity is None:
|
|
3852
|
+
return None
|
|
3853
|
+
selector = snapshot.runner_selector_of(product_id)
|
|
3854
|
+
if selector is not None and selector_satisfied(selector, self._routing_identity.tags):
|
|
3855
|
+
return None
|
|
3856
|
+
return (
|
|
3857
|
+
f"this Runner's tags do not satisfy the Runner selector of Product {product_id}, "
|
|
3858
|
+
f"which owns {identifier}",
|
|
3859
|
+
{
|
|
3860
|
+
"event": "directive.repository_refused",
|
|
3861
|
+
"product_id": product_id,
|
|
3862
|
+
"runner_id": str(self._routing_identity.runner_id),
|
|
3863
|
+
"selector": dict(selector or {}),
|
|
3864
|
+
},
|
|
3865
|
+
)
|
|
3866
|
+
|
|
3867
|
+
async def _repo_callback(
|
|
3868
|
+
self,
|
|
3869
|
+
work_record_id: str,
|
|
3870
|
+
state: _RuntimeContextState,
|
|
3871
|
+
workspace_path: Path,
|
|
3872
|
+
request: RepoRequest,
|
|
3873
|
+
*,
|
|
3874
|
+
verb: str,
|
|
3875
|
+
bound: list[ProductBinding],
|
|
3876
|
+
) -> RepoResponse:
|
|
3877
|
+
"""``agentic-runner repo read | branch``: an Organisation-scoped Work Record's
|
|
3878
|
+
Workspace (ADR-0018 §5, §7).
|
|
3879
|
+
|
|
3880
|
+
Before binding, the Workspace is an empty directory and every repository read
|
|
3881
|
+
is checked out beneath it. `branch` cuts the Work Record's work branch in one of
|
|
3882
|
+
those checkouts and asks the control plane to bind the Product that owns the
|
|
3883
|
+
repository; that checkout is the Workspace from then on, and the next Directive
|
|
3884
|
+
is an ordinary Product-scoped one in it.
|
|
3885
|
+
|
|
3886
|
+
Both verbs go through `_evaluate_verb`, so they are evaluated, refused and
|
|
3887
|
+
recorded exactly as the activity seams are.
|
|
3888
|
+
"""
|
|
3889
|
+
|
|
3890
|
+
def refused(reason: str, decision: str = Decision.DENY.value) -> RepoResponse:
|
|
3891
|
+
return RepoResponse(
|
|
3892
|
+
repository=request.repository, allowed=False, decision=decision, reason=reason
|
|
3893
|
+
)
|
|
3894
|
+
|
|
3895
|
+
if self._git_workspace is None or self._workspace_root is None:
|
|
3896
|
+
return refused("this Runner has no git workspace")
|
|
3897
|
+
if state.repository:
|
|
3898
|
+
return refused(
|
|
3899
|
+
f"this Work Record is bound to {state.repository}; its Workspace is that checkout"
|
|
3900
|
+
)
|
|
3901
|
+
if bound and verb == BRANCH_VERB and bound[0].repository == request.repository:
|
|
3902
|
+
return RepoResponse(
|
|
3903
|
+
repository=request.repository,
|
|
3904
|
+
allowed=True,
|
|
3905
|
+
decision=Decision.ALLOW.value,
|
|
3906
|
+
reason="already bound to this repository",
|
|
3907
|
+
path=str(_repository_checkout(workspace_path, request.repository)),
|
|
3908
|
+
product_id=bound[0].product_id,
|
|
3909
|
+
)
|
|
3910
|
+
try:
|
|
3911
|
+
decision = await self._evaluate_verb(
|
|
3912
|
+
work_record_id,
|
|
3913
|
+
state,
|
|
3914
|
+
verb=verb,
|
|
3915
|
+
identifier=request.repository,
|
|
3916
|
+
bound_product=bound[0].product_id if bound else None,
|
|
3917
|
+
)
|
|
3918
|
+
except SeamUnavailableError as error:
|
|
3919
|
+
return refused(error.reason)
|
|
3920
|
+
if not decision.allowed:
|
|
3921
|
+
return refused(decision.reason, decision.decision.value)
|
|
3922
|
+
if bound and verb == BRANCH_VERB:
|
|
3923
|
+
return refused(
|
|
3924
|
+
f"this Work Record is bound to {bound[0].repository}: one branch and one pull "
|
|
3925
|
+
"request per Work Record; open a child Work Record with work.open"
|
|
3926
|
+
)
|
|
3927
|
+
checkout = _repository_checkout(workspace_path, request.repository)
|
|
3928
|
+
git_workspace = self._git_workspace
|
|
3929
|
+
|
|
3930
|
+
async def checkout_default_branch() -> str:
|
|
3931
|
+
checked_out = await asyncio.to_thread(
|
|
3932
|
+
git_workspace.checkout_default_branch,
|
|
3933
|
+
CheckoutDefaultBranchRequest(
|
|
3934
|
+
repo_full_name=request.repository, workspace_path=checkout
|
|
3935
|
+
),
|
|
3936
|
+
)
|
|
3937
|
+
return checked_out.base_branch
|
|
3938
|
+
|
|
3939
|
+
fresh = not checkout.is_dir()
|
|
3940
|
+
# On an existing checkout this re-registers it with the adapter, which a Runner
|
|
3941
|
+
# restart between two Directives would otherwise have forgotten.
|
|
3942
|
+
await asyncio.to_thread(
|
|
3943
|
+
git_workspace.clone_repository,
|
|
3944
|
+
CloneWorkspaceRequest(
|
|
3945
|
+
repo_full_name=request.repository,
|
|
3946
|
+
remote_url=_github_remote_url(request.repository),
|
|
3947
|
+
workspace_root=self._require_workspace_root(),
|
|
3948
|
+
workspace_path=checkout,
|
|
3949
|
+
),
|
|
3950
|
+
)
|
|
3951
|
+
default_branch = await checkout_default_branch() if fresh else ""
|
|
3952
|
+
product_id = ""
|
|
3953
|
+
if verb == BRANCH_VERB:
|
|
3954
|
+
base_ref = request.base_ref
|
|
3955
|
+
if base_ref:
|
|
3956
|
+
await asyncio.to_thread(
|
|
3957
|
+
git_workspace.fetch_base_branch,
|
|
3958
|
+
FetchBranchRequest(
|
|
3959
|
+
repo_full_name=request.repository,
|
|
3960
|
+
workspace_path=checkout,
|
|
3961
|
+
base_branch=base_ref,
|
|
3962
|
+
),
|
|
3963
|
+
)
|
|
3964
|
+
else:
|
|
3965
|
+
base_ref = default_branch or await checkout_default_branch()
|
|
3966
|
+
await asyncio.to_thread(
|
|
3967
|
+
git_workspace.checkout_work_branch,
|
|
3968
|
+
CheckoutWorkBranchRequest(
|
|
3969
|
+
repo_full_name=request.repository,
|
|
3970
|
+
workspace_path=checkout,
|
|
3971
|
+
base_branch=base_ref,
|
|
3972
|
+
work_branch=state.work_branch,
|
|
3973
|
+
),
|
|
3974
|
+
)
|
|
3975
|
+
answer = await self._fastapi_client.bind_product(
|
|
3976
|
+
work_record_id, repository=request.repository, base_branch=base_ref
|
|
3977
|
+
)
|
|
3978
|
+
product_id = str(answer.get("product_id") or "")
|
|
3979
|
+
if answer.get("decision") != Decision.ALLOW.value or not product_id:
|
|
3980
|
+
return refused(str(answer.get("reason") or "the control plane refused to bind"))
|
|
3981
|
+
bound.append(
|
|
3982
|
+
ProductBinding(
|
|
3983
|
+
repository=request.repository, base_ref=base_ref, product_id=product_id
|
|
3984
|
+
)
|
|
3985
|
+
)
|
|
3986
|
+
if self._contract_isolation is not None:
|
|
3987
|
+
self._contract_isolation.hand_workspace_to_contract(state.contract_id, checkout)
|
|
3988
|
+
return RepoResponse(
|
|
3989
|
+
repository=request.repository,
|
|
3990
|
+
allowed=True,
|
|
3991
|
+
decision=decision.decision.value,
|
|
3992
|
+
reason=decision.reason,
|
|
3993
|
+
path=str(checkout),
|
|
3994
|
+
product_id=product_id,
|
|
3995
|
+
)
|
|
3996
|
+
|
|
3702
3997
|
def _current_snapshot(self, state: _RuntimeContextState) -> GrantSnapshot:
|
|
3703
3998
|
"""The snapshot to decide *this* verb on, not the one the activity started with.
|
|
3704
3999
|
|
|
@@ -4890,13 +5185,16 @@ class RunnerRalphActivities:
|
|
|
4890
5185
|
kind: _InPlaceDirective,
|
|
4891
5186
|
) -> FixDirectiveOutput:
|
|
4892
5187
|
asked: list[QuestionAsked] = []
|
|
4893
|
-
|
|
5188
|
+
bound: list[ProductBinding] = []
|
|
5189
|
+
ran = await self._run_in_place_turn(request, kind, asked, bound)
|
|
5190
|
+
return _with_binding(_with_question(ran, asked), bound)
|
|
4894
5191
|
|
|
4895
5192
|
async def _run_in_place_turn(
|
|
4896
5193
|
self,
|
|
4897
5194
|
request: FixDirectiveInput | MemberDirectiveInput,
|
|
4898
5195
|
kind: _InPlaceDirective,
|
|
4899
5196
|
asked: list[QuestionAsked],
|
|
5197
|
+
bound: list[ProductBinding],
|
|
4900
5198
|
) -> FixDirectiveOutput:
|
|
4901
5199
|
"""One runtime turn in the Workspace the first Directive cloned, then push.
|
|
4902
5200
|
|
|
@@ -4977,6 +5275,7 @@ class RunnerRalphActivities:
|
|
|
4977
5275
|
sandbox=self._contract_sandbox(state),
|
|
4978
5276
|
mcp_plan=mcp_plan,
|
|
4979
5277
|
asked=asked,
|
|
5278
|
+
bound=bound,
|
|
4980
5279
|
)
|
|
4981
5280
|
)
|
|
4982
5281
|
# No checkout phase here: an in-place Directive runs in the Workspace the
|
|
@@ -5085,10 +5384,12 @@ class RunnerRalphActivities:
|
|
|
5085
5384
|
)
|
|
5086
5385
|
raise RuntimeError("Codex CLI fix directive failed")
|
|
5087
5386
|
# A report's directory is no checkout (console-v2 issue 23): what the Agent
|
|
5088
|
-
# wrote there stays there, and its report left as a Message.
|
|
5387
|
+
# wrote there stays there, and its report left as a Message. Nor is an
|
|
5388
|
+
# Organisation-scoped Work Record's before it binds (ADR-0018 §7): what it
|
|
5389
|
+
# changed in the checkout it bound is committed by the next Directive.
|
|
5089
5390
|
git_evidence = (
|
|
5090
5391
|
None
|
|
5091
|
-
if state.kind == KIND_REPORT
|
|
5392
|
+
if state.kind == KIND_REPORT or not state.repository
|
|
5092
5393
|
else self._git_workspace.collect_git_evidence(
|
|
5093
5394
|
state.workspace_path,
|
|
5094
5395
|
output_limit_bytes=_GIT_EVIDENCE_LIMIT_BYTES,
|
|
@@ -5426,7 +5727,8 @@ class RunnerRalphActivities:
|
|
|
5426
5727
|
re-assembled tree is the work as it was last pushed, not a fresh branch off base.
|
|
5427
5728
|
|
|
5428
5729
|
A report has no branch (console-v2 issue 23): its Workspace is the empty
|
|
5429
|
-
directory alone, on its first Directive and after any re-route alike.
|
|
5730
|
+
directory alone, on its first Directive and after any re-route alike. So is an
|
|
5731
|
+
Organisation-scoped Work Record's before it binds a Product (ADR-0018 §7).
|
|
5430
5732
|
"""
|
|
5431
5733
|
|
|
5432
5734
|
await self._assert_routed(request)
|
|
@@ -5441,7 +5743,7 @@ class RunnerRalphActivities:
|
|
|
5441
5743
|
contract_id=contract_id,
|
|
5442
5744
|
work_record_id=request.work_record_id,
|
|
5443
5745
|
)
|
|
5444
|
-
if state.kind == KIND_REPORT:
|
|
5746
|
+
if state.kind == KIND_REPORT or not state.repository:
|
|
5445
5747
|
await self._fastapi_client.append_evidence(
|
|
5446
5748
|
request.work_record_id,
|
|
5447
5749
|
source=_ROUTING_EVIDENCE_SOURCE,
|
|
@@ -4,7 +4,7 @@ Buildkite's Job API, narrowed to what ADR-0011 §9 can defend. For each Directiv
|
|
|
4
4
|
the Runner mints 32 random bytes as a bearer and opens a Unix socket **owned by the
|
|
5
5
|
Contract's uid** (map ticket 17 A11); the Agent Runtime subprocess receives the socket
|
|
6
6
|
path and the bearer in its environment and nothing else. ``agentic-runner annotate |
|
|
7
|
-
artifact upload | verb`` speak to it.
|
|
7
|
+
artifact upload | verb | repo`` speak to it.
|
|
8
8
|
|
|
9
9
|
What makes that not a credential: the token's only audience is this Runner's own socket,
|
|
10
10
|
it is useless off the box, it dies with the attempt, and it is a *correlator* rather than
|
|
@@ -65,6 +65,8 @@ __all__ = [
|
|
|
65
65
|
"MessageListResponse",
|
|
66
66
|
"MessageSendRequest",
|
|
67
67
|
"MessageSendResponse",
|
|
68
|
+
"RepoRequest",
|
|
69
|
+
"RepoResponse",
|
|
68
70
|
"Route",
|
|
69
71
|
"VerbRequest",
|
|
70
72
|
"VerbResponse",
|
|
@@ -127,6 +129,28 @@ class VerbResponse(BaseModel):
|
|
|
127
129
|
reason: str
|
|
128
130
|
|
|
129
131
|
|
|
132
|
+
class RepoRequest(BaseModel):
|
|
133
|
+
"""``agentic-runner repo read | branch`` (ADR-0018 §5, §7): one repository, by name.
|
|
134
|
+
|
|
135
|
+
``base_ref`` is for `branch` only; empty cuts the work branch from the repository's
|
|
136
|
+
default branch.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
repository: str = Field(max_length=200, pattern=r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
|
|
140
|
+
base_ref: str = Field(default="", max_length=255)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class RepoResponse(BaseModel):
|
|
144
|
+
"""The seam's verdict and, when allowed, where the checkout is in the Workspace."""
|
|
145
|
+
|
|
146
|
+
repository: str
|
|
147
|
+
allowed: bool
|
|
148
|
+
decision: str
|
|
149
|
+
reason: str
|
|
150
|
+
path: str = ""
|
|
151
|
+
product_id: str = ""
|
|
152
|
+
|
|
153
|
+
|
|
130
154
|
class MessageSendRequest(BaseModel):
|
|
131
155
|
"""``agentic-runner message send`` (PRD issue 52): the envelope minus what the Runner
|
|
132
156
|
fills in itself -- sender, Work Record, id and time are the attempt's, never the
|
|
@@ -200,6 +224,8 @@ ROUTES: Final[dict[tuple[str, str], Route]] = {
|
|
|
200
224
|
("POST", "/v0/message/send"): Route(MessageSendRequest, MessageSendResponse, "message_send"),
|
|
201
225
|
("POST", "/v0/message/list"): Route(MessageListRequest, MessageListResponse, "message_list"),
|
|
202
226
|
("POST", "/v0/ask"): Route(AskRequest, AskResponse, "ask"),
|
|
227
|
+
("POST", "/v0/repo/read"): Route(RepoRequest, RepoResponse, "repo_read"),
|
|
228
|
+
("POST", "/v0/repo/branch"): Route(RepoRequest, RepoResponse, "repo_branch"),
|
|
203
229
|
}
|
|
204
230
|
|
|
205
231
|
|
|
@@ -213,6 +239,8 @@ class CallbackHandlers:
|
|
|
213
239
|
message_send: Callable[[MessageSendRequest], Awaitable[MessageSendResponse]]
|
|
214
240
|
message_list: Callable[[MessageListRequest], Awaitable[MessageListResponse]]
|
|
215
241
|
ask: Callable[[AskRequest], Awaitable[AskResponse]]
|
|
242
|
+
repo_read: Callable[[RepoRequest], Awaitable[RepoResponse]]
|
|
243
|
+
repo_branch: Callable[[RepoRequest], Awaitable[RepoResponse]]
|
|
216
244
|
|
|
217
245
|
|
|
218
246
|
class CallbackError(RuntimeError):
|
|
@@ -233,6 +233,17 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
233
233
|
)
|
|
234
234
|
ask.add_argument("text", nargs="?", help="the question; omitted reads stdin")
|
|
235
235
|
|
|
236
|
+
repo = subcommands.add_parser(
|
|
237
|
+
"repo",
|
|
238
|
+
help="check out a repository in reach; `branch` binds an Organisation-wide Work Record "
|
|
239
|
+
"to its Product (ADR-0018)",
|
|
240
|
+
)
|
|
241
|
+
repo.add_argument("action", choices=("read", "branch"))
|
|
242
|
+
repo.add_argument("repository", help="owner/name")
|
|
243
|
+
repo.add_argument(
|
|
244
|
+
"--base", default="", help="`branch` only: the base branch; default the repository's"
|
|
245
|
+
)
|
|
246
|
+
|
|
236
247
|
subcommands.add_parser("config", help="print the resolved configuration (flag > env > file)")
|
|
237
248
|
|
|
238
249
|
arguments = parser.parse_args(argv)
|
|
@@ -391,6 +402,11 @@ def _request(arguments: argparse.Namespace) -> tuple[str, dict[str, object]]:
|
|
|
391
402
|
return "/v0/ask", {
|
|
392
403
|
"text": arguments.text if arguments.text is not None else sys.stdin.read()
|
|
393
404
|
}
|
|
405
|
+
if arguments.command == "repo":
|
|
406
|
+
return f"/v0/repo/{arguments.action}", {
|
|
407
|
+
"repository": arguments.repository,
|
|
408
|
+
"base_ref": arguments.base,
|
|
409
|
+
}
|
|
394
410
|
if arguments.command == "message":
|
|
395
411
|
if arguments.action == "list":
|
|
396
412
|
return "/v0/message/list", {"channel_id": arguments.channel}
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/contracts.py
RENAMED
|
@@ -60,6 +60,14 @@ class FetchBranchResult:
|
|
|
60
60
|
base_branch: str
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class CheckoutDefaultBranchRequest:
|
|
65
|
+
"""Request to check out ``origin``'s default branch, detached, in a fresh clone."""
|
|
66
|
+
|
|
67
|
+
repo_full_name: str
|
|
68
|
+
workspace_path: Path
|
|
69
|
+
|
|
70
|
+
|
|
63
71
|
@dataclass(frozen=True)
|
|
64
72
|
class CheckoutWorkBranchRequest:
|
|
65
73
|
"""Request to create or reset a generated work branch from the base branch."""
|
|
@@ -172,6 +180,13 @@ class GitWorkspace(Protocol):
|
|
|
172
180
|
def fetch_base_branch(self, request: FetchBranchRequest) -> FetchBranchResult:
|
|
173
181
|
"""Fetch the requested base branch into a workspace."""
|
|
174
182
|
|
|
183
|
+
def checkout_default_branch(self, request: CheckoutDefaultBranchRequest) -> FetchBranchResult:
|
|
184
|
+
"""Check out the remote's default branch detached; its name is the result's base.
|
|
185
|
+
|
|
186
|
+
A repository an Organisation-scoped Work Record reads has no base branch of its
|
|
187
|
+
own yet (ADR-0018 §7): the clone's ``origin/HEAD`` is the one it reads.
|
|
188
|
+
"""
|
|
189
|
+
|
|
175
190
|
def checkout_work_branch(
|
|
176
191
|
self,
|
|
177
192
|
request: CheckoutWorkBranchRequest,
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/fake_workspace.py
RENAMED
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
from agentic_runner.integrations.git.contracts import (
|
|
7
7
|
AdoptWorkspaceRequest,
|
|
8
|
+
CheckoutDefaultBranchRequest,
|
|
8
9
|
CheckoutWorkBranchRequest,
|
|
9
10
|
CheckoutWorkBranchResult,
|
|
10
11
|
CloneWorkspaceRequest,
|
|
@@ -63,6 +64,7 @@ class FakeGitWorkspace:
|
|
|
63
64
|
self._next_commit_number = 1
|
|
64
65
|
self._status_evidence = status_evidence
|
|
65
66
|
self._diff_evidence = diff_evidence
|
|
67
|
+
self.default_branch = "main"
|
|
66
68
|
|
|
67
69
|
@property
|
|
68
70
|
def calls(self) -> tuple[FakeGitWorkspaceCall, ...]:
|
|
@@ -146,6 +148,17 @@ class FakeGitWorkspace:
|
|
|
146
148
|
base_branch=request.base_branch,
|
|
147
149
|
)
|
|
148
150
|
|
|
151
|
+
def checkout_default_branch(self, request: CheckoutDefaultBranchRequest) -> FetchBranchResult:
|
|
152
|
+
record = self._get_workspace(request.workspace_path)
|
|
153
|
+
self._require_same_repo(record, request.repo_full_name)
|
|
154
|
+
record.base_branch = self.default_branch
|
|
155
|
+
self._record_call("checkout_default_branch", request.repo_full_name, record.workspace_path)
|
|
156
|
+
return FetchBranchResult(
|
|
157
|
+
repo_full_name=request.repo_full_name,
|
|
158
|
+
workspace_path=record.workspace_path,
|
|
159
|
+
base_branch=self.default_branch,
|
|
160
|
+
)
|
|
161
|
+
|
|
149
162
|
def checkout_work_branch(
|
|
150
163
|
self,
|
|
151
164
|
request: CheckoutWorkBranchRequest,
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/workspace.py
RENAMED
|
@@ -13,6 +13,7 @@ from uuid import UUID
|
|
|
13
13
|
|
|
14
14
|
from agentic_runner.integrations.git.contracts import (
|
|
15
15
|
AdoptWorkspaceRequest,
|
|
16
|
+
CheckoutDefaultBranchRequest,
|
|
16
17
|
CheckoutWorkBranchRequest,
|
|
17
18
|
CheckoutWorkBranchResult,
|
|
18
19
|
CloneWorkspaceRequest,
|
|
@@ -390,6 +391,36 @@ class LocalGitWorkspace:
|
|
|
390
391
|
base_branch=base_branch,
|
|
391
392
|
)
|
|
392
393
|
|
|
394
|
+
def checkout_default_branch(self, request: CheckoutDefaultBranchRequest) -> FetchBranchResult:
|
|
395
|
+
record = self._get_workspace(request.workspace_path)
|
|
396
|
+
self._require_same_repo(record, request.repo_full_name)
|
|
397
|
+
# Asked of the remote, not read off ``refs/remotes/origin/HEAD``: a clone sets that
|
|
398
|
+
# ref only when the remote's HEAD resolved at clone time.
|
|
399
|
+
advertised = self._run_git(
|
|
400
|
+
["ls-remote", "--symref", "origin", "HEAD"],
|
|
401
|
+
cwd=record.workspace_path,
|
|
402
|
+
record=record,
|
|
403
|
+
).stdout
|
|
404
|
+
remote_head = next(
|
|
405
|
+
(
|
|
406
|
+
line.split()[1]
|
|
407
|
+
for line in advertised.splitlines()
|
|
408
|
+
if line.startswith("ref: ") and line.endswith("\tHEAD")
|
|
409
|
+
),
|
|
410
|
+
"",
|
|
411
|
+
)
|
|
412
|
+
base_branch = validate_base_branch(remote_head.removeprefix("refs/heads/"))
|
|
413
|
+
self._run_git(
|
|
414
|
+
["checkout", "--detach", f"refs/remotes/origin/{base_branch}"],
|
|
415
|
+
cwd=record.workspace_path,
|
|
416
|
+
record=record,
|
|
417
|
+
)
|
|
418
|
+
return FetchBranchResult(
|
|
419
|
+
repo_full_name=request.repo_full_name,
|
|
420
|
+
workspace_path=record.workspace_path,
|
|
421
|
+
base_branch=base_branch,
|
|
422
|
+
)
|
|
423
|
+
|
|
393
424
|
def checkout_work_branch(
|
|
394
425
|
self,
|
|
395
426
|
request: CheckoutWorkBranchRequest,
|
|
@@ -75,8 +75,9 @@ class RunnerFastApiClient:
|
|
|
75
75
|
for *bookkeeping* — its activities do the thing and return facts, and the workflow
|
|
76
76
|
records them through platform activities (``PlatformRalphActivities``). What is left
|
|
77
77
|
here is what an activity genuinely cannot do without: the Work Record's runtime
|
|
78
|
-
context, the Directive's metered usage, Questions and Owner Confirmations,
|
|
79
|
-
|
|
78
|
+
context, the Directive's metered usage, Questions and Owner Confirmations, the
|
|
79
|
+
Product binding the `repo.branch` seam asks for (ADR-0018 §5), and the Evidence its
|
|
80
|
+
verb seams write. Every path is on the public Runner surface; the
|
|
80
81
|
authoritative list of the routes a Runner calls is this class plus
|
|
81
82
|
``registration.py``'s constants (tests/integration/test_runner_api_surface.py).
|
|
82
83
|
|
|
@@ -196,6 +197,21 @@ class RunnerFastApiClient:
|
|
|
196
197
|
request_payload,
|
|
197
198
|
)
|
|
198
199
|
|
|
200
|
+
async def bind_product(
|
|
201
|
+
self, work_record_id: str, *, repository: str, base_branch: str
|
|
202
|
+
) -> dict[str, Any]:
|
|
203
|
+
"""Bind an Organisation-scoped Work Record to ``repository``'s Product (ADR-0018 §5).
|
|
204
|
+
|
|
205
|
+
The control plane resolves the Product from its own registry, sets it and records
|
|
206
|
+
`work_record.product_bound` in one transaction; ``decision`` is ``deny`` with a
|
|
207
|
+
``reason`` when the repository is out of reach or a second Product's.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
return await self._post(
|
|
211
|
+
f"{self._prefix}/work-records/{_path_segment(work_record_id)}/product-binding",
|
|
212
|
+
{"repository": repository, "base_branch": base_branch},
|
|
213
|
+
)
|
|
214
|
+
|
|
199
215
|
async def transition_work_record(
|
|
200
216
|
self,
|
|
201
217
|
work_record_id: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/device_login_activities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/git/evidence.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/__init__.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/auth.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/fake_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/github/gh_client.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/integrations/oauth/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/runtime/verifier_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/testing/test_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/_runtime_support.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/contract_device_login.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/contract_isolation.py
RENAMED
|
File without changes
|
{agentic_runner-3.9.0 → agentic_runner-3.11.0}/src/agentic_runner/workers/harness_outcome.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|