gitgrip 1.5.0__py3-none-any.whl
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.
- gitgrip-1.5.0.dist-info/METADATA +13 -0
- gitgrip-1.5.0.dist-info/RECORD +80 -0
- gitgrip-1.5.0.dist-info/WHEEL +5 -0
- gitgrip-1.5.0.dist-info/entry_points.txt +2 -0
- gitgrip-1.5.0.dist-info/top_level.txt +2 -0
- gr2/__init__.py +0 -0
- gr2/overlay/__init__.py +6 -0
- gr2/overlay/activate.py +196 -0
- gr2/overlay/agent_manifest.py +138 -0
- gr2/overlay/cli.py +181 -0
- gr2/overlay/cross_repo.py +124 -0
- gr2/overlay/drivers.py +113 -0
- gr2/overlay/introspection.py +155 -0
- gr2/overlay/language_drivers.py +115 -0
- gr2/overlay/objects.py +412 -0
- gr2/overlay/perf.py +251 -0
- gr2/overlay/refs.py +36 -0
- gr2/overlay/trust.py +150 -0
- gr2/overlay/types.py +69 -0
- gr2/overlay/units.py +313 -0
- gr2/overlay/workspace_spec.py +59 -0
- gr2/prototypes/__init__.py +0 -0
- gr2/prototypes/cache_materialization_probe.py +190 -0
- gr2/prototypes/concurrent_event_stress.py +199 -0
- gr2/prototypes/concurrent_lease_stress.py +240 -0
- gr2/prototypes/concurrent_workspace_cap_stress.py +231 -0
- gr2/prototypes/contribution_protocol.py +665 -0
- gr2/prototypes/cross_mode_lane_stress.py +986 -0
- gr2/prototypes/jsonl_store.py +158 -0
- gr2/prototypes/lane_workspace_prototype.py +2088 -0
- gr2/prototypes/layout_model_probe.py +139 -0
- gr2/prototypes/propagation_daemon.py +546 -0
- gr2/prototypes/propagation_state_machine.py +1478 -0
- gr2/prototypes/python_exec_playground.py +194 -0
- gr2/prototypes/python_hook_runtime_playground.py +240 -0
- gr2/prototypes/python_migration_playground.py +144 -0
- gr2/prototypes/python_review_checkout_playground.py +242 -0
- gr2/prototypes/python_spec_apply_playground.py +282 -0
- gr2/prototypes/real_git_lane_materialization.py +248 -0
- gr2/prototypes/real_git_playground.py +334 -0
- gr2/prototypes/recall_lane_history.py +274 -0
- gr2/prototypes/repo_maintenance_prototype.py +659 -0
- gr2/prototypes/repo_transport_probe.py +147 -0
- gr2/python_cli/__init__.py +2 -0
- gr2/python_cli/__main__.py +6 -0
- gr2/python_cli/add.py +51 -0
- gr2/python_cli/app.py +2516 -0
- gr2/python_cli/branch.py +67 -0
- gr2/python_cli/channel_bridge.py +131 -0
- gr2/python_cli/clone_exec.py +1019 -0
- gr2/python_cli/commit.py +199 -0
- gr2/python_cli/config.py +291 -0
- gr2/python_cli/env_exec.py +419 -0
- gr2/python_cli/events.py +529 -0
- gr2/python_cli/execops.py +372 -0
- gr2/python_cli/failures.py +98 -0
- gr2/python_cli/file_exec.py +256 -0
- gr2/python_cli/gitops.py +226 -0
- gr2/python_cli/grip.py +1337 -0
- gr2/python_cli/grip_cli.py +493 -0
- gr2/python_cli/hooks.py +450 -0
- gr2/python_cli/launch_exec.py +786 -0
- gr2/python_cli/merge_verification.py +274 -0
- gr2/python_cli/migration.py +985 -0
- gr2/python_cli/open_gr_review.py +699 -0
- gr2/python_cli/platform.py +441 -0
- gr2/python_cli/pr.py +487 -0
- gr2/python_cli/project_review.py +314 -0
- gr2/python_cli/prune.py +365 -0
- gr2/python_cli/push.py +172 -0
- gr2/python_cli/review.py +462 -0
- gr2/python_cli/review_ephemeral.py +143 -0
- gr2/python_cli/review_run.py +621 -0
- gr2/python_cli/spec_apply.py +1285 -0
- gr2/python_cli/staging_cleanup.py +205 -0
- gr2/python_cli/syncops.py +920 -0
- gr2/python_cli/target.py +100 -0
- gr2/python_cli/workspace_snapshot.py +105 -0
- gr2/schemas/gr2-materialization-plan-v1.schema.json +191 -0
- gr2_overlay/__init__.py +37 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Adversarial cross-mode stress harness for the gr2 lane model.
|
|
3
|
+
|
|
4
|
+
This script pressures the lane prototype across the four primary user modes:
|
|
5
|
+
|
|
6
|
+
1. solo human
|
|
7
|
+
2. single agent
|
|
8
|
+
3. multi-agent
|
|
9
|
+
4. mixed human + agent
|
|
10
|
+
|
|
11
|
+
It does not pretend the model is complete. It reports where the current
|
|
12
|
+
prototype holds, where it only partially holds, and where it still falls over.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
from dataclasses import asdict, dataclass
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ScenarioResult:
|
|
28
|
+
scenario_id: str
|
|
29
|
+
user_mode: str
|
|
30
|
+
title: str
|
|
31
|
+
verdict: str
|
|
32
|
+
holds: list[str]
|
|
33
|
+
gaps: list[str]
|
|
34
|
+
evidence: list[str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class HarnessCommandError(RuntimeError):
|
|
38
|
+
"""A child command failed with its diagnostic output preserved."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_args() -> argparse.Namespace:
|
|
42
|
+
parser = argparse.ArgumentParser(
|
|
43
|
+
description="Run adversarial cross-mode lane stress checks"
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--workspace-root",
|
|
47
|
+
type=Path,
|
|
48
|
+
help="optional workspace root; defaults to a temporary workspace",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--json",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="emit structured JSON instead of human-readable text",
|
|
54
|
+
)
|
|
55
|
+
return parser.parse_args()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def repo_root() -> Path:
|
|
59
|
+
return Path(__file__).resolve().parents[2]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def lane_proto(root: Path) -> Path:
|
|
63
|
+
return root / "gr2" / "prototypes" / "lane_workspace_prototype.py"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run(
|
|
67
|
+
argv: list[str], *, capture: bool = False, cwd: Path | None = None
|
|
68
|
+
) -> subprocess.CompletedProcess[str]:
|
|
69
|
+
proc = subprocess.run(
|
|
70
|
+
argv,
|
|
71
|
+
cwd=cwd,
|
|
72
|
+
check=False,
|
|
73
|
+
text=True,
|
|
74
|
+
capture_output=capture,
|
|
75
|
+
)
|
|
76
|
+
if proc.returncode != 0:
|
|
77
|
+
stdout = proc.stdout or "<not captured>"
|
|
78
|
+
stderr = proc.stderr or "<not captured>"
|
|
79
|
+
raise HarnessCommandError(
|
|
80
|
+
f"child command failed with exit {proc.returncode}: {' '.join(argv)}\n"
|
|
81
|
+
f"stdout:\n{stdout}\nstderr:\n{stderr}"
|
|
82
|
+
)
|
|
83
|
+
return proc
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def init_workspace(workspace_root: Path) -> None:
|
|
87
|
+
(workspace_root / ".grip").mkdir(parents=True, exist_ok=True)
|
|
88
|
+
(workspace_root / "agents").mkdir(exist_ok=True)
|
|
89
|
+
spec = """schema_version = 1
|
|
90
|
+
workspace_name = "lane-cross-mode-stress"
|
|
91
|
+
|
|
92
|
+
[cache]
|
|
93
|
+
root = ".grip/cache"
|
|
94
|
+
|
|
95
|
+
[[repos]]
|
|
96
|
+
name = "app"
|
|
97
|
+
path = "repos/app"
|
|
98
|
+
url = "https://example.invalid/app.git"
|
|
99
|
+
|
|
100
|
+
[[repos]]
|
|
101
|
+
name = "api"
|
|
102
|
+
path = "repos/api"
|
|
103
|
+
url = "https://example.invalid/api.git"
|
|
104
|
+
|
|
105
|
+
[[repos]]
|
|
106
|
+
name = "web"
|
|
107
|
+
path = "repos/web"
|
|
108
|
+
url = "https://example.invalid/web.git"
|
|
109
|
+
|
|
110
|
+
[[repos]]
|
|
111
|
+
name = "billing"
|
|
112
|
+
path = "repos/billing"
|
|
113
|
+
url = "https://example.invalid/billing.git"
|
|
114
|
+
|
|
115
|
+
[workspace_constraints]
|
|
116
|
+
max_concurrent_edit_leases_global = 2
|
|
117
|
+
|
|
118
|
+
[workspace_constraints.required_reviewers]
|
|
119
|
+
billing = 2
|
|
120
|
+
app = 1
|
|
121
|
+
|
|
122
|
+
[[units]]
|
|
123
|
+
name = "atlas"
|
|
124
|
+
path = "agents/atlas"
|
|
125
|
+
agent_id = "atlas-agent"
|
|
126
|
+
repos = ["app", "api", "web", "billing"]
|
|
127
|
+
|
|
128
|
+
[[units]]
|
|
129
|
+
name = "apollo"
|
|
130
|
+
path = "agents/apollo"
|
|
131
|
+
agent_id = "apollo-agent"
|
|
132
|
+
repos = ["app", "api", "web", "billing"]
|
|
133
|
+
|
|
134
|
+
[[units]]
|
|
135
|
+
name = "layne"
|
|
136
|
+
path = "agents/layne"
|
|
137
|
+
agent_id = "layne-human"
|
|
138
|
+
repos = ["app", "api", "web", "billing"]
|
|
139
|
+
|
|
140
|
+
[[units]]
|
|
141
|
+
name = "synapt-core"
|
|
142
|
+
path = "agents/synapt-core"
|
|
143
|
+
agent_id = "agent_opus_abc123"
|
|
144
|
+
repos = ["app", "api", "web", "billing"]
|
|
145
|
+
|
|
146
|
+
[[units]]
|
|
147
|
+
name = "release-control"
|
|
148
|
+
path = "agents/release-control"
|
|
149
|
+
agent_id = "agent_opus_abc123"
|
|
150
|
+
repos = ["app", "api", "web", "billing"]
|
|
151
|
+
"""
|
|
152
|
+
(workspace_root / ".grip" / "workspace_spec.toml").write_text(spec)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def create_lane(root: Path, workspace_root: Path, owner_unit: str, lane_name: str, repos: str, branch: str, lane_type: str = "feature") -> None:
|
|
156
|
+
run(
|
|
157
|
+
[
|
|
158
|
+
"python3",
|
|
159
|
+
str(lane_proto(root)),
|
|
160
|
+
"create-lane",
|
|
161
|
+
str(workspace_root),
|
|
162
|
+
owner_unit,
|
|
163
|
+
lane_name,
|
|
164
|
+
"--type",
|
|
165
|
+
lane_type,
|
|
166
|
+
"--repos",
|
|
167
|
+
repos,
|
|
168
|
+
"--branch",
|
|
169
|
+
branch,
|
|
170
|
+
]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def create_review_lane(root: Path, workspace_root: Path, owner_unit: str, repo: str, pr_number: int) -> None:
|
|
175
|
+
run(
|
|
176
|
+
[
|
|
177
|
+
"python3",
|
|
178
|
+
str(lane_proto(root)),
|
|
179
|
+
"create-review-lane",
|
|
180
|
+
str(workspace_root),
|
|
181
|
+
owner_unit,
|
|
182
|
+
repo,
|
|
183
|
+
str(pr_number),
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def plan_exec_json(root: Path, workspace_root: Path, owner_unit: str, lane_name: str, command_text: str) -> list[dict]:
|
|
189
|
+
proc = run(
|
|
190
|
+
[
|
|
191
|
+
"python3",
|
|
192
|
+
str(lane_proto(root)),
|
|
193
|
+
"plan-exec",
|
|
194
|
+
str(workspace_root),
|
|
195
|
+
owner_unit,
|
|
196
|
+
lane_name,
|
|
197
|
+
command_text,
|
|
198
|
+
"--json",
|
|
199
|
+
],
|
|
200
|
+
capture=True,
|
|
201
|
+
)
|
|
202
|
+
return json.loads(proc.stdout)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def acquire_lease(root: Path, workspace_root: Path, owner_unit: str, lane_name: str, actor: str, mode: str, ttl_seconds: int = 900, force: bool = False, expect_ok: bool = True) -> subprocess.CompletedProcess[str]:
|
|
206
|
+
argv = [
|
|
207
|
+
"python3",
|
|
208
|
+
str(lane_proto(root)),
|
|
209
|
+
"acquire-lane-lease",
|
|
210
|
+
str(workspace_root),
|
|
211
|
+
owner_unit,
|
|
212
|
+
lane_name,
|
|
213
|
+
"--actor",
|
|
214
|
+
actor,
|
|
215
|
+
"--mode",
|
|
216
|
+
mode,
|
|
217
|
+
"--ttl-seconds",
|
|
218
|
+
str(ttl_seconds),
|
|
219
|
+
]
|
|
220
|
+
if force:
|
|
221
|
+
argv.append("--force")
|
|
222
|
+
proc = subprocess.run(argv, check=False, text=True, capture_output=True)
|
|
223
|
+
if expect_ok and proc.returncode != 0:
|
|
224
|
+
raise SystemExit(f"lease acquisition failed unexpectedly: {' '.join(argv)}\n{proc.stdout}\n{proc.stderr}")
|
|
225
|
+
return proc
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def show_leases_json(root: Path, workspace_root: Path, owner_unit: str, lane_name: str) -> list[dict]:
|
|
229
|
+
proc = run(
|
|
230
|
+
[
|
|
231
|
+
"python3",
|
|
232
|
+
str(lane_proto(root)),
|
|
233
|
+
"show-lane-leases",
|
|
234
|
+
str(workspace_root),
|
|
235
|
+
owner_unit,
|
|
236
|
+
lane_name,
|
|
237
|
+
"--json",
|
|
238
|
+
],
|
|
239
|
+
capture=True,
|
|
240
|
+
)
|
|
241
|
+
return json.loads(proc.stdout)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def check_review_requirements_json(root: Path, workspace_root: Path, repo: str, pr_number: int) -> dict:
|
|
245
|
+
proc = run(
|
|
246
|
+
[
|
|
247
|
+
"python3",
|
|
248
|
+
str(lane_proto(root)),
|
|
249
|
+
"check-review-requirements",
|
|
250
|
+
str(workspace_root),
|
|
251
|
+
repo,
|
|
252
|
+
str(pr_number),
|
|
253
|
+
"--json",
|
|
254
|
+
],
|
|
255
|
+
capture=True,
|
|
256
|
+
)
|
|
257
|
+
return json.loads(proc.stdout)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def list_lanes_text(root: Path, workspace_root: Path, owner_unit: str | None = None) -> str:
|
|
261
|
+
argv = [
|
|
262
|
+
"python3",
|
|
263
|
+
str(lane_proto(root)),
|
|
264
|
+
"list-lanes",
|
|
265
|
+
str(workspace_root),
|
|
266
|
+
]
|
|
267
|
+
if owner_unit:
|
|
268
|
+
argv.extend(["--owner-unit", owner_unit])
|
|
269
|
+
proc = run(argv, capture=True)
|
|
270
|
+
return proc.stdout
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def plan_handoff_json(
|
|
274
|
+
root: Path,
|
|
275
|
+
workspace_root: Path,
|
|
276
|
+
source_owner_unit: str,
|
|
277
|
+
source_lane_name: str,
|
|
278
|
+
target_unit: str,
|
|
279
|
+
mode: str,
|
|
280
|
+
target_lane_name: str | None = None,
|
|
281
|
+
) -> dict:
|
|
282
|
+
argv = [
|
|
283
|
+
"python3",
|
|
284
|
+
str(lane_proto(root)),
|
|
285
|
+
"plan-handoff",
|
|
286
|
+
str(workspace_root),
|
|
287
|
+
source_owner_unit,
|
|
288
|
+
source_lane_name,
|
|
289
|
+
target_unit,
|
|
290
|
+
"--mode",
|
|
291
|
+
mode,
|
|
292
|
+
"--json",
|
|
293
|
+
]
|
|
294
|
+
if target_lane_name:
|
|
295
|
+
argv.extend(["--target-lane-name", target_lane_name])
|
|
296
|
+
proc = run(argv, capture=True)
|
|
297
|
+
return json.loads(proc.stdout)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def scenario_multi_agent_same_repo(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
301
|
+
create_lane(root, workspace_root, "atlas", "feat-router", "app", "feat/router")
|
|
302
|
+
create_lane(root, workspace_root, "apollo", "feat-materialize", "app", "feat/materialize")
|
|
303
|
+
|
|
304
|
+
atlas_lane = workspace_root / "agents" / "atlas" / "lanes" / "feat-router" / "lane.toml"
|
|
305
|
+
apollo_lane = workspace_root / "agents" / "apollo" / "lanes" / "feat-materialize" / "lane.toml"
|
|
306
|
+
|
|
307
|
+
holds = []
|
|
308
|
+
gaps = []
|
|
309
|
+
evidence = []
|
|
310
|
+
|
|
311
|
+
if atlas_lane.exists() and apollo_lane.exists():
|
|
312
|
+
holds.append("two units can create separate lanes touching the same repo without metadata collision")
|
|
313
|
+
evidence.append(f"lane files: {atlas_lane.relative_to(workspace_root)}, {apollo_lane.relative_to(workspace_root)}")
|
|
314
|
+
else:
|
|
315
|
+
gaps.append("unit-scoped lane metadata was not isolated cleanly")
|
|
316
|
+
|
|
317
|
+
atlas_exec = plan_exec_json(root, workspace_root, "atlas", "feat-router", "cargo test")
|
|
318
|
+
apollo_exec = plan_exec_json(root, workspace_root, "apollo", "feat-materialize", "cargo test")
|
|
319
|
+
if atlas_exec and apollo_exec and atlas_exec[0]["cwd"] != apollo_exec[0]["cwd"]:
|
|
320
|
+
holds.append("execution planning stays unit-scoped even when both lanes include the same repo")
|
|
321
|
+
evidence.append(f"exec cwd atlas={atlas_exec[0]['cwd']} apollo={apollo_exec[0]['cwd']}")
|
|
322
|
+
verdict = "holds"
|
|
323
|
+
else:
|
|
324
|
+
gaps.append("execution planning did not stay unit-scoped for same-repo parallel work")
|
|
325
|
+
verdict = "fails"
|
|
326
|
+
|
|
327
|
+
return ScenarioResult(
|
|
328
|
+
scenario_id="multi-agent-same-repo",
|
|
329
|
+
user_mode="multi-agent",
|
|
330
|
+
title="two agents create lanes that touch the same repo",
|
|
331
|
+
verdict=verdict,
|
|
332
|
+
holds=holds,
|
|
333
|
+
gaps=gaps,
|
|
334
|
+
evidence=evidence,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def scenario_agent_handoff_relay(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
339
|
+
create_lane(root, workspace_root, "atlas", "feat-router", "app,api", "feat/router")
|
|
340
|
+
run(
|
|
341
|
+
[
|
|
342
|
+
"python3",
|
|
343
|
+
str(lane_proto(root)),
|
|
344
|
+
"share-lane",
|
|
345
|
+
str(workspace_root),
|
|
346
|
+
"atlas",
|
|
347
|
+
"feat-router",
|
|
348
|
+
"apollo",
|
|
349
|
+
]
|
|
350
|
+
)
|
|
351
|
+
shared_plan = plan_handoff_json(
|
|
352
|
+
root,
|
|
353
|
+
workspace_root,
|
|
354
|
+
"atlas",
|
|
355
|
+
"feat-router",
|
|
356
|
+
"apollo",
|
|
357
|
+
"shared",
|
|
358
|
+
)
|
|
359
|
+
run(
|
|
360
|
+
[
|
|
361
|
+
"python3",
|
|
362
|
+
str(lane_proto(root)),
|
|
363
|
+
"create-continuation-lane",
|
|
364
|
+
str(workspace_root),
|
|
365
|
+
"atlas",
|
|
366
|
+
"feat-router",
|
|
367
|
+
"apollo",
|
|
368
|
+
"feat-router-relay",
|
|
369
|
+
]
|
|
370
|
+
)
|
|
371
|
+
continuation_plan = plan_handoff_json(
|
|
372
|
+
root,
|
|
373
|
+
workspace_root,
|
|
374
|
+
"atlas",
|
|
375
|
+
"feat-router",
|
|
376
|
+
"apollo",
|
|
377
|
+
"continuation",
|
|
378
|
+
"feat-router-relay",
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
holds = []
|
|
382
|
+
gaps = []
|
|
383
|
+
evidence = [
|
|
384
|
+
json.dumps(shared_plan, indent=2),
|
|
385
|
+
json.dumps(continuation_plan, indent=2),
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
if not shared_plan["invariant_assessment"]["unit_scoped"]:
|
|
389
|
+
holds.append("cross-unit shared-lane relay exposes the unit-scoping violation directly")
|
|
390
|
+
else:
|
|
391
|
+
gaps.append("shared-lane relay incorrectly appears unit-scoped")
|
|
392
|
+
|
|
393
|
+
shared_cwds = {row["cwd"] for row in shared_plan["exec_rows"]}
|
|
394
|
+
if all("/agents/atlas/lanes/feat-router/" in cwd for cwd in shared_cwds):
|
|
395
|
+
holds.append("shared-lane relay forces the target unit to execute inside the source unit lane root")
|
|
396
|
+
else:
|
|
397
|
+
gaps.append("shared-lane relay did not clearly surface source-unit cwd ownership")
|
|
398
|
+
|
|
399
|
+
if continuation_plan["invariant_assessment"]["unit_scoped"]:
|
|
400
|
+
holds.append("continuation lane preserves unit-scoped cwd and lease ownership")
|
|
401
|
+
else:
|
|
402
|
+
gaps.append("continuation lane did not preserve unit scoping")
|
|
403
|
+
|
|
404
|
+
continuation_cwds = {row["cwd"] for row in continuation_plan["exec_rows"]}
|
|
405
|
+
if all("/agents/apollo/lanes/feat-router-relay/" in cwd for cwd in continuation_cwds):
|
|
406
|
+
holds.append("continuation lane gives the target unit an independent lane root")
|
|
407
|
+
verdict = "holds"
|
|
408
|
+
else:
|
|
409
|
+
gaps.append("continuation lane did not create target-unit-local execution roots")
|
|
410
|
+
verdict = "fails"
|
|
411
|
+
|
|
412
|
+
return ScenarioResult(
|
|
413
|
+
scenario_id="agent-handoff-relay",
|
|
414
|
+
user_mode="multi-agent",
|
|
415
|
+
title="agent-to-agent lane handoff prefers continuation over cross-unit shared lanes",
|
|
416
|
+
verdict=verdict,
|
|
417
|
+
holds=holds,
|
|
418
|
+
gaps=gaps,
|
|
419
|
+
evidence=evidence,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def scenario_mixed_same_lane_exec(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
424
|
+
create_lane(root, workspace_root, "layne", "feat-blog", "app", "feat/blog")
|
|
425
|
+
acquire_lease(root, workspace_root, "layne", "feat-blog", "human:layne", "edit")
|
|
426
|
+
|
|
427
|
+
exec_rows = plan_exec_json(root, workspace_root, "layne", "feat-blog", "cargo test")
|
|
428
|
+
|
|
429
|
+
holds = []
|
|
430
|
+
gaps = []
|
|
431
|
+
evidence = [
|
|
432
|
+
"human edit lease acquired for layne/feat-blog",
|
|
433
|
+
json.dumps(exec_rows if isinstance(exec_rows, list) else exec_rows, indent=2),
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
if isinstance(exec_rows, dict) and exec_rows.get("status") == "blocked":
|
|
437
|
+
holds.append("same-lane human-edit vs agent-exec is blocked by a lease")
|
|
438
|
+
holds.append("prototype now models occupancy instead of silently planning through it")
|
|
439
|
+
verdict = "holds"
|
|
440
|
+
else:
|
|
441
|
+
gaps.append("same-lane concurrent human-edit vs agent-exec is not modeled or blocked")
|
|
442
|
+
verdict = "fails"
|
|
443
|
+
|
|
444
|
+
return ScenarioResult(
|
|
445
|
+
scenario_id="mixed-same-lane-exec",
|
|
446
|
+
user_mode="mixed-human-agent",
|
|
447
|
+
title="human edits in a lane while an agent plans exec in the same lane",
|
|
448
|
+
verdict=verdict,
|
|
449
|
+
holds=holds,
|
|
450
|
+
gaps=gaps,
|
|
451
|
+
evidence=evidence,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def scenario_single_agent_interrupt_recovery(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
456
|
+
create_lane(root, workspace_root, "atlas", "feat-auth", "app,api", "feat/auth")
|
|
457
|
+
create_review_lane(root, workspace_root, "atlas", "app", 123)
|
|
458
|
+
run(
|
|
459
|
+
[
|
|
460
|
+
"python3",
|
|
461
|
+
str(lane_proto(root)),
|
|
462
|
+
"enter-lane",
|
|
463
|
+
str(workspace_root),
|
|
464
|
+
"atlas",
|
|
465
|
+
"feat-auth",
|
|
466
|
+
"--actor",
|
|
467
|
+
"agent:atlas",
|
|
468
|
+
]
|
|
469
|
+
)
|
|
470
|
+
run(
|
|
471
|
+
[
|
|
472
|
+
"python3",
|
|
473
|
+
str(lane_proto(root)),
|
|
474
|
+
"enter-lane",
|
|
475
|
+
str(workspace_root),
|
|
476
|
+
"atlas",
|
|
477
|
+
"review-123",
|
|
478
|
+
"--actor",
|
|
479
|
+
"agent:atlas",
|
|
480
|
+
]
|
|
481
|
+
)
|
|
482
|
+
lane_listing = list_lanes_text(root, workspace_root, "atlas")
|
|
483
|
+
current_lane_proc = run(
|
|
484
|
+
[
|
|
485
|
+
"python3",
|
|
486
|
+
str(lane_proto(root)),
|
|
487
|
+
"current-lane",
|
|
488
|
+
str(workspace_root),
|
|
489
|
+
"atlas",
|
|
490
|
+
"--json",
|
|
491
|
+
],
|
|
492
|
+
capture=True,
|
|
493
|
+
)
|
|
494
|
+
current_lane_doc = json.loads(current_lane_proc.stdout)
|
|
495
|
+
holds = [
|
|
496
|
+
"agent can enumerate all of its lanes without guessing filesystem paths",
|
|
497
|
+
"lane metadata includes repos, type, and PR references",
|
|
498
|
+
]
|
|
499
|
+
gaps = []
|
|
500
|
+
evidence = [lane_listing.strip(), json.dumps(current_lane_doc, indent=2)]
|
|
501
|
+
|
|
502
|
+
current = current_lane_doc.get("current", {})
|
|
503
|
+
recent = current_lane_doc.get("recent", [])
|
|
504
|
+
if current.get("lane_name") == "review-123":
|
|
505
|
+
holds.append("agent can recover current lane after an interruption")
|
|
506
|
+
else:
|
|
507
|
+
gaps.append("current-lane surface did not record the lane entered most recently")
|
|
508
|
+
|
|
509
|
+
if recent and recent[0].get("lane_name") == "feat-auth":
|
|
510
|
+
holds.append("agent can recover previous lane from recent history")
|
|
511
|
+
verdict = "holds"
|
|
512
|
+
else:
|
|
513
|
+
gaps.append("prototype still cannot recover previous lane deterministically")
|
|
514
|
+
verdict = "partial"
|
|
515
|
+
|
|
516
|
+
return ScenarioResult(
|
|
517
|
+
scenario_id="single-agent-interrupt-recovery",
|
|
518
|
+
user_mode="single-agent",
|
|
519
|
+
title="agent is interrupted mid-task and needs to recover lane context",
|
|
520
|
+
verdict=verdict,
|
|
521
|
+
holds=holds,
|
|
522
|
+
gaps=gaps,
|
|
523
|
+
evidence=evidence,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def scenario_lease_conflict_matrix(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
528
|
+
create_lane(root, workspace_root, "atlas", "feat-matrix", "app", "feat/matrix")
|
|
529
|
+
|
|
530
|
+
exec_one = acquire_lease(root, workspace_root, "atlas", "feat-matrix", "agent:atlas", "exec")
|
|
531
|
+
exec_two = acquire_lease(root, workspace_root, "atlas", "feat-matrix", "agent:apollo", "exec")
|
|
532
|
+
edit_conflict = acquire_lease(
|
|
533
|
+
root,
|
|
534
|
+
workspace_root,
|
|
535
|
+
"atlas",
|
|
536
|
+
"feat-matrix",
|
|
537
|
+
"human:layne",
|
|
538
|
+
"edit",
|
|
539
|
+
expect_ok=False,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
create_lane(root, workspace_root, "atlas", "feat-review-lock", "app", "feat/review-lock")
|
|
543
|
+
acquire_lease(root, workspace_root, "atlas", "feat-review-lock", "agent:atlas", "review")
|
|
544
|
+
review_conflict = acquire_lease(
|
|
545
|
+
root,
|
|
546
|
+
workspace_root,
|
|
547
|
+
"atlas",
|
|
548
|
+
"feat-review-lock",
|
|
549
|
+
"agent:apollo",
|
|
550
|
+
"exec",
|
|
551
|
+
expect_ok=False,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
holds = []
|
|
555
|
+
gaps = []
|
|
556
|
+
evidence = []
|
|
557
|
+
|
|
558
|
+
if exec_one.returncode == 0 and exec_two.returncode == 0:
|
|
559
|
+
holds.append("exec-vs-exec is allowed for the same lane")
|
|
560
|
+
evidence.append("two exec leases acquired successfully on atlas/feat-matrix")
|
|
561
|
+
else:
|
|
562
|
+
gaps.append("exec-vs-exec was blocked unexpectedly")
|
|
563
|
+
|
|
564
|
+
if edit_conflict.returncode != 0:
|
|
565
|
+
holds.append("edit-vs-exec conflicts as expected")
|
|
566
|
+
evidence.append(edit_conflict.stdout.strip())
|
|
567
|
+
else:
|
|
568
|
+
gaps.append("edit-vs-exec did not conflict")
|
|
569
|
+
|
|
570
|
+
if review_conflict.returncode != 0:
|
|
571
|
+
holds.append("review-vs-anything is exclusive")
|
|
572
|
+
evidence.append(review_conflict.stdout.strip())
|
|
573
|
+
else:
|
|
574
|
+
gaps.append("review-vs-exec did not conflict")
|
|
575
|
+
|
|
576
|
+
leases = show_leases_json(root, workspace_root, "atlas", "feat-matrix")
|
|
577
|
+
evidence.append(json.dumps(leases, indent=2))
|
|
578
|
+
verdict = "holds" if not gaps else "fails"
|
|
579
|
+
return ScenarioResult(
|
|
580
|
+
scenario_id="lease-conflict-matrix",
|
|
581
|
+
user_mode="cross-mode",
|
|
582
|
+
title="lease conflict matrix enforces edit/exec/review semantics",
|
|
583
|
+
verdict=verdict,
|
|
584
|
+
holds=holds,
|
|
585
|
+
gaps=gaps,
|
|
586
|
+
evidence=evidence,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def scenario_synapt_lane_events(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
591
|
+
create_lane(root, workspace_root, "atlas", "feat-events", "app,api", "feat/events")
|
|
592
|
+
run(
|
|
593
|
+
[
|
|
594
|
+
"python3",
|
|
595
|
+
str(lane_proto(root)),
|
|
596
|
+
"enter-lane",
|
|
597
|
+
str(workspace_root),
|
|
598
|
+
"atlas",
|
|
599
|
+
"feat-events",
|
|
600
|
+
"--actor",
|
|
601
|
+
"agent:atlas",
|
|
602
|
+
"--notify-channel",
|
|
603
|
+
"--recall",
|
|
604
|
+
]
|
|
605
|
+
)
|
|
606
|
+
acquire_lease(root, workspace_root, "atlas", "feat-events", "agent:atlas", "exec")
|
|
607
|
+
run(
|
|
608
|
+
[
|
|
609
|
+
"python3",
|
|
610
|
+
str(lane_proto(root)),
|
|
611
|
+
"release-lane-lease",
|
|
612
|
+
str(workspace_root),
|
|
613
|
+
"atlas",
|
|
614
|
+
"feat-events",
|
|
615
|
+
"--actor",
|
|
616
|
+
"agent:atlas",
|
|
617
|
+
]
|
|
618
|
+
)
|
|
619
|
+
run(
|
|
620
|
+
[
|
|
621
|
+
"python3",
|
|
622
|
+
str(lane_proto(root)),
|
|
623
|
+
"exit-lane",
|
|
624
|
+
str(workspace_root),
|
|
625
|
+
"atlas",
|
|
626
|
+
"--actor",
|
|
627
|
+
"agent:atlas",
|
|
628
|
+
"--notify-channel",
|
|
629
|
+
"--recall",
|
|
630
|
+
]
|
|
631
|
+
)
|
|
632
|
+
history_proc = run(
|
|
633
|
+
[
|
|
634
|
+
"python3",
|
|
635
|
+
str(lane_proto(root)),
|
|
636
|
+
"lane-history",
|
|
637
|
+
str(workspace_root),
|
|
638
|
+
"atlas",
|
|
639
|
+
"--json",
|
|
640
|
+
],
|
|
641
|
+
capture=True,
|
|
642
|
+
)
|
|
643
|
+
history_rows = json.loads(history_proc.stdout)
|
|
644
|
+
events_path = workspace_root / ".grip" / "events" / "lane_events.jsonl"
|
|
645
|
+
recall_path = workspace_root / ".grip" / "events" / "recall_lane_history.jsonl"
|
|
646
|
+
|
|
647
|
+
holds = []
|
|
648
|
+
gaps = []
|
|
649
|
+
evidence = [json.dumps(history_rows, indent=2)]
|
|
650
|
+
|
|
651
|
+
event_types = [row["type"] for row in history_rows]
|
|
652
|
+
expected = ["lane_enter", "lease_acquire", "lease_release", "lane_exit"]
|
|
653
|
+
if event_types == expected:
|
|
654
|
+
holds.append("lane event timeline is reconstructible from append-only event log")
|
|
655
|
+
else:
|
|
656
|
+
gaps.append(f"unexpected lane event order: {event_types}")
|
|
657
|
+
|
|
658
|
+
if all(row.get("agent_id") == "atlas-agent" for row in history_rows):
|
|
659
|
+
holds.append("agent_id flows from workspace spec into lane events")
|
|
660
|
+
else:
|
|
661
|
+
gaps.append("agent_id did not flow consistently into lane events")
|
|
662
|
+
|
|
663
|
+
if events_path.exists() and recall_path.exists():
|
|
664
|
+
holds.append("channel-compatible and recall-compatible event logs are both written")
|
|
665
|
+
else:
|
|
666
|
+
gaps.append("expected event logs were not both written")
|
|
667
|
+
|
|
668
|
+
verdict = "holds" if not gaps else "fails"
|
|
669
|
+
return ScenarioResult(
|
|
670
|
+
scenario_id="synapt-lane-events",
|
|
671
|
+
user_mode="single-agent",
|
|
672
|
+
title="lane enter/lease/exit emits reconstructible synapt-compatible events",
|
|
673
|
+
verdict=verdict,
|
|
674
|
+
holds=holds,
|
|
675
|
+
gaps=gaps,
|
|
676
|
+
evidence=evidence,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def scenario_stale_lease_force_break(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
681
|
+
create_lane(root, workspace_root, "atlas", "feat-stale", "app", "feat/stale")
|
|
682
|
+
stale = acquire_lease(
|
|
683
|
+
root,
|
|
684
|
+
workspace_root,
|
|
685
|
+
"atlas",
|
|
686
|
+
"feat-stale",
|
|
687
|
+
"human:layne",
|
|
688
|
+
"edit",
|
|
689
|
+
ttl_seconds=0,
|
|
690
|
+
)
|
|
691
|
+
blocked_exec = plan_exec_json(root, workspace_root, "atlas", "feat-stale", "cargo test")
|
|
692
|
+
forced = acquire_lease(
|
|
693
|
+
root,
|
|
694
|
+
workspace_root,
|
|
695
|
+
"atlas",
|
|
696
|
+
"feat-stale",
|
|
697
|
+
"agent:atlas",
|
|
698
|
+
"exec",
|
|
699
|
+
ttl_seconds=900,
|
|
700
|
+
force=True,
|
|
701
|
+
)
|
|
702
|
+
leases_after = show_leases_json(root, workspace_root, "atlas", "feat-stale")
|
|
703
|
+
|
|
704
|
+
holds = []
|
|
705
|
+
gaps = []
|
|
706
|
+
evidence = [stale.stdout.strip(), json.dumps(blocked_exec, indent=2), forced.stdout.strip(), json.dumps(leases_after, indent=2)]
|
|
707
|
+
|
|
708
|
+
if isinstance(blocked_exec, dict) and blocked_exec.get("reason") == "stale-conflicting-lease":
|
|
709
|
+
holds.append("plan-exec detects stale conflicting leases")
|
|
710
|
+
else:
|
|
711
|
+
gaps.append("plan-exec did not flag stale conflicting leases")
|
|
712
|
+
|
|
713
|
+
actors_after = {lease["actor"] for lease in leases_after}
|
|
714
|
+
if "human:layne" not in actors_after and "agent:atlas" in actors_after:
|
|
715
|
+
holds.append("force acquisition breaks stale conflicting lease and installs new lease")
|
|
716
|
+
else:
|
|
717
|
+
gaps.append("force acquisition did not replace stale conflicting lease cleanly")
|
|
718
|
+
|
|
719
|
+
verdict = "holds" if not gaps else "fails"
|
|
720
|
+
return ScenarioResult(
|
|
721
|
+
scenario_id="stale-lease-force-break",
|
|
722
|
+
user_mode="cross-mode",
|
|
723
|
+
title="stale leases are detectable and force-breakable",
|
|
724
|
+
verdict=verdict,
|
|
725
|
+
holds=holds,
|
|
726
|
+
gaps=gaps,
|
|
727
|
+
evidence=evidence,
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def scenario_solo_human_forgets_lane(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
732
|
+
create_lane(root, workspace_root, "layne", "feat-auth", "app,api", "feat/auth")
|
|
733
|
+
create_lane(root, workspace_root, "layne", "feat-web", "web", "feat/web")
|
|
734
|
+
create_lane(root, workspace_root, "layne", "feat-release", "app,web", "feat/release")
|
|
735
|
+
run(
|
|
736
|
+
[
|
|
737
|
+
"python3",
|
|
738
|
+
str(lane_proto(root)),
|
|
739
|
+
"enter-lane",
|
|
740
|
+
str(workspace_root),
|
|
741
|
+
"layne",
|
|
742
|
+
"feat-release",
|
|
743
|
+
"--actor",
|
|
744
|
+
"human:layne",
|
|
745
|
+
]
|
|
746
|
+
)
|
|
747
|
+
create_review_lane(root, workspace_root, "layne", "app", 456)
|
|
748
|
+
run(
|
|
749
|
+
[
|
|
750
|
+
"python3",
|
|
751
|
+
str(lane_proto(root)),
|
|
752
|
+
"enter-lane",
|
|
753
|
+
str(workspace_root),
|
|
754
|
+
"layne",
|
|
755
|
+
"review-456",
|
|
756
|
+
"--actor",
|
|
757
|
+
"human:layne",
|
|
758
|
+
]
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
lane_listing = list_lanes_text(root, workspace_root, "layne")
|
|
762
|
+
current_lane_proc = run(
|
|
763
|
+
[
|
|
764
|
+
"python3",
|
|
765
|
+
str(lane_proto(root)),
|
|
766
|
+
"current-lane",
|
|
767
|
+
str(workspace_root),
|
|
768
|
+
"layne",
|
|
769
|
+
"--json",
|
|
770
|
+
],
|
|
771
|
+
capture=True,
|
|
772
|
+
)
|
|
773
|
+
current_lane_doc = json.loads(current_lane_proc.stdout)
|
|
774
|
+
holds = [
|
|
775
|
+
"user can see all lanes in one listing",
|
|
776
|
+
"review lane is isolated as its own lane type rather than overwriting feature state",
|
|
777
|
+
]
|
|
778
|
+
gaps = []
|
|
779
|
+
evidence = [lane_listing.strip(), json.dumps(current_lane_doc, indent=2)]
|
|
780
|
+
|
|
781
|
+
if current_lane_doc.get("current", {}).get("lane_name") == "review-456":
|
|
782
|
+
holds.append("current review lane is visible after switching")
|
|
783
|
+
else:
|
|
784
|
+
gaps.append("current lane is not visible after switching to review")
|
|
785
|
+
|
|
786
|
+
recent = current_lane_doc.get("recent", [])
|
|
787
|
+
if recent and recent[0].get("lane_name") == "feat-release":
|
|
788
|
+
holds.append("previous feature lane is recoverable after entering review")
|
|
789
|
+
verdict = "holds"
|
|
790
|
+
else:
|
|
791
|
+
gaps.append("prototype lacks an obvious return-to-previous-lane recovery path")
|
|
792
|
+
verdict = "partial"
|
|
793
|
+
|
|
794
|
+
return ScenarioResult(
|
|
795
|
+
scenario_id="solo-human-lane-recovery",
|
|
796
|
+
user_mode="solo-human",
|
|
797
|
+
title="solo human has three feature lanes, switches to review, then forgets the prior lane",
|
|
798
|
+
verdict=verdict,
|
|
799
|
+
holds=holds,
|
|
800
|
+
gaps=gaps,
|
|
801
|
+
evidence=evidence,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def scenario_global_edit_lease_cap(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
806
|
+
"""Verify the workspace cap sequentially; concurrency has a separate stress harness."""
|
|
807
|
+
create_lane(root, workspace_root, "atlas", "feat-cap-a", "app", "feat/cap-a")
|
|
808
|
+
create_lane(root, workspace_root, "apollo", "feat-cap-b", "api", "feat/cap-b")
|
|
809
|
+
create_lane(root, workspace_root, "layne", "feat-cap-c", "web", "feat/cap-c")
|
|
810
|
+
|
|
811
|
+
create_lane(root, workspace_root, "release-control", "feat-cap-stale", "billing", "feat/cap-stale")
|
|
812
|
+
acquire_lease(root, workspace_root, "release-control", "feat-cap-stale", "agent:opus", "edit", ttl_seconds=0)
|
|
813
|
+
|
|
814
|
+
lease_a = acquire_lease(root, workspace_root, "atlas", "feat-cap-a", "agent:atlas", "edit")
|
|
815
|
+
lease_b = acquire_lease(root, workspace_root, "apollo", "feat-cap-b", "agent:apollo", "edit")
|
|
816
|
+
lease_c = acquire_lease(
|
|
817
|
+
root, workspace_root, "layne", "feat-cap-c", "human:layne", "edit", expect_ok=False
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
stale_force = acquire_lease(
|
|
821
|
+
root,
|
|
822
|
+
workspace_root,
|
|
823
|
+
"release-control",
|
|
824
|
+
"feat-cap-stale",
|
|
825
|
+
"agent:opus",
|
|
826
|
+
"edit",
|
|
827
|
+
ttl_seconds=900,
|
|
828
|
+
force=True,
|
|
829
|
+
expect_ok=False,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
holds = []
|
|
833
|
+
gaps = []
|
|
834
|
+
evidence = [lease_c.stdout.strip(), stale_force.stdout.strip()]
|
|
835
|
+
|
|
836
|
+
if lease_a.returncode == 0 and lease_b.returncode == 0 and lease_c.returncode != 0:
|
|
837
|
+
holds.append("third sequential edit lease is blocked when global cap of 2 is reached")
|
|
838
|
+
else:
|
|
839
|
+
gaps.append("global edit lease cap did not block the third concurrent edit lease")
|
|
840
|
+
|
|
841
|
+
if stale_force.returncode != 0 and "workspace-edit-lease-cap" in stale_force.stdout:
|
|
842
|
+
holds.append("force-breaking a stale local lease does not bypass the workspace edit cap")
|
|
843
|
+
else:
|
|
844
|
+
gaps.append("stale force-break bypassed the workspace edit lease cap")
|
|
845
|
+
|
|
846
|
+
# Release leases so later scenarios aren't blocked by the global cap
|
|
847
|
+
for unit, lane, actor in [
|
|
848
|
+
("atlas", "feat-cap-a", "agent:atlas"),
|
|
849
|
+
("apollo", "feat-cap-b", "agent:apollo"),
|
|
850
|
+
]:
|
|
851
|
+
run(
|
|
852
|
+
[
|
|
853
|
+
"python3",
|
|
854
|
+
str(lane_proto(root)),
|
|
855
|
+
"release-lane-lease",
|
|
856
|
+
str(workspace_root),
|
|
857
|
+
unit,
|
|
858
|
+
lane,
|
|
859
|
+
"--actor",
|
|
860
|
+
actor,
|
|
861
|
+
],
|
|
862
|
+
capture=True,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
verdict = "holds" if not gaps else "fails"
|
|
866
|
+
return ScenarioResult(
|
|
867
|
+
scenario_id="global-edit-lease-cap",
|
|
868
|
+
user_mode="cross-mode",
|
|
869
|
+
title="workspace-wide edit lease cap is enforced sequentially across all units",
|
|
870
|
+
verdict=verdict,
|
|
871
|
+
holds=holds,
|
|
872
|
+
gaps=gaps,
|
|
873
|
+
evidence=evidence,
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def scenario_required_reviewers(root: Path, workspace_root: Path) -> ScenarioResult:
|
|
878
|
+
zero = check_review_requirements_json(root, workspace_root, "billing", 777)
|
|
879
|
+
create_review_lane(root, workspace_root, "atlas", "billing", 777)
|
|
880
|
+
one = check_review_requirements_json(root, workspace_root, "billing", 777)
|
|
881
|
+
create_review_lane(root, workspace_root, "apollo", "billing", 777)
|
|
882
|
+
two = check_review_requirements_json(root, workspace_root, "billing", 777)
|
|
883
|
+
|
|
884
|
+
holds = []
|
|
885
|
+
gaps = []
|
|
886
|
+
evidence = [
|
|
887
|
+
json.dumps(zero, indent=2),
|
|
888
|
+
json.dumps(one, indent=2),
|
|
889
|
+
json.dumps(two, indent=2),
|
|
890
|
+
]
|
|
891
|
+
|
|
892
|
+
if zero["required_reviewers"] == 2 and zero["actual_reviewers"] == 0 and not zero["satisfied"]:
|
|
893
|
+
holds.append("review requirements report unsatisfied with zero review lanes")
|
|
894
|
+
else:
|
|
895
|
+
gaps.append("zero-reviewer requirement state is incorrect")
|
|
896
|
+
|
|
897
|
+
if one["actual_reviewers"] == 1 and not one["satisfied"]:
|
|
898
|
+
holds.append("one review lane is still unsatisfied when the repo requires two reviewers")
|
|
899
|
+
else:
|
|
900
|
+
gaps.append("single reviewer state is incorrect")
|
|
901
|
+
|
|
902
|
+
if two["actual_reviewers"] == 2 and two["satisfied"]:
|
|
903
|
+
holds.append("two review lanes satisfy the repo review requirement")
|
|
904
|
+
else:
|
|
905
|
+
gaps.append("two reviewers did not satisfy the repo requirement")
|
|
906
|
+
|
|
907
|
+
verdict = "holds" if not gaps else "fails"
|
|
908
|
+
return ScenarioResult(
|
|
909
|
+
scenario_id="required-reviewers",
|
|
910
|
+
user_mode="cross-mode",
|
|
911
|
+
title="required reviewer counts are enforced from workspace constraints",
|
|
912
|
+
verdict=verdict,
|
|
913
|
+
holds=holds,
|
|
914
|
+
gaps=gaps,
|
|
915
|
+
evidence=evidence,
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def run_scenarios(workspace_root: Path) -> list[ScenarioResult]:
|
|
920
|
+
root = repo_root()
|
|
921
|
+
init_workspace(workspace_root)
|
|
922
|
+
return [
|
|
923
|
+
scenario_synapt_lane_events(root, workspace_root),
|
|
924
|
+
scenario_lease_conflict_matrix(root, workspace_root),
|
|
925
|
+
scenario_stale_lease_force_break(root, workspace_root),
|
|
926
|
+
scenario_multi_agent_same_repo(root, workspace_root),
|
|
927
|
+
scenario_agent_handoff_relay(root, workspace_root),
|
|
928
|
+
scenario_global_edit_lease_cap(root, workspace_root),
|
|
929
|
+
scenario_required_reviewers(root, workspace_root),
|
|
930
|
+
scenario_mixed_same_lane_exec(root, workspace_root),
|
|
931
|
+
scenario_single_agent_interrupt_recovery(root, workspace_root),
|
|
932
|
+
scenario_solo_human_forgets_lane(root, workspace_root),
|
|
933
|
+
]
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def print_human(results: list[ScenarioResult], workspace_root: Path) -> None:
|
|
937
|
+
print("gr2 cross-mode lane stress results")
|
|
938
|
+
print(f"workspace: {workspace_root}")
|
|
939
|
+
print()
|
|
940
|
+
for result in results:
|
|
941
|
+
print(f"[{result.verdict}] {result.user_mode}: {result.title}")
|
|
942
|
+
if result.holds:
|
|
943
|
+
print(" holds:")
|
|
944
|
+
for item in result.holds:
|
|
945
|
+
print(f" - {item}")
|
|
946
|
+
if result.gaps:
|
|
947
|
+
print(" gaps:")
|
|
948
|
+
for item in result.gaps:
|
|
949
|
+
print(f" - {item}")
|
|
950
|
+
if result.evidence:
|
|
951
|
+
print(" evidence:")
|
|
952
|
+
for item in result.evidence:
|
|
953
|
+
for line in item.splitlines():
|
|
954
|
+
print(f" {line}")
|
|
955
|
+
print()
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
def main() -> int:
|
|
959
|
+
args = parse_args()
|
|
960
|
+
try:
|
|
961
|
+
if args.workspace_root:
|
|
962
|
+
workspace_root = args.workspace_root.resolve()
|
|
963
|
+
workspace_root.mkdir(parents=True, exist_ok=True)
|
|
964
|
+
results = run_scenarios(workspace_root)
|
|
965
|
+
else:
|
|
966
|
+
with tempfile.TemporaryDirectory(prefix="gr2-cross-mode-") as tmp:
|
|
967
|
+
workspace_root = Path(tmp)
|
|
968
|
+
results = run_scenarios(workspace_root)
|
|
969
|
+
if args.json:
|
|
970
|
+
print(json.dumps([asdict(result) for result in results], indent=2))
|
|
971
|
+
return 0
|
|
972
|
+
print_human(results, workspace_root)
|
|
973
|
+
return 0
|
|
974
|
+
except Exception as exc:
|
|
975
|
+
print(f"gr2 cross-mode lane stress FAILED: {exc}", file=sys.stderr)
|
|
976
|
+
return 1
|
|
977
|
+
|
|
978
|
+
if args.json:
|
|
979
|
+
print(json.dumps([asdict(result) for result in results], indent=2))
|
|
980
|
+
else:
|
|
981
|
+
print_human(results, workspace_root)
|
|
982
|
+
return 0
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
if __name__ == "__main__":
|
|
986
|
+
raise SystemExit(main())
|