@plainconceptsplatform/workflows 1.2.3 → 1.3.1
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.
- package/dist/route-processing.js +1 -0
- package/loops/actions/add-issue-labels/action.yml +50 -50
- package/loops/actions/agent-output.cjs +17 -17
- package/loops/actions/apply-agent-bundle/action.yml +24 -24
- package/loops/actions/apply-agent-comments/action.yml +42 -42
- package/loops/actions/apply-agent-labels/action.yml +55 -55
- package/loops/actions/apply-agent-output/action.yml +108 -108
- package/loops/actions/assess-blast-radius/action.yml +47 -8
- package/loops/actions/assess-blast-radius/assess-blast-radius.sh +27 -5
- package/loops/actions/assess-blast-radius/migration-is-additive.sh +38 -0
- package/loops/actions/classify-route/action.yml +2 -1
- package/loops/actions/classify-route/classify-route.sh +7 -1
- package/loops/actions/cleanup-artifacts/action.yml +91 -91
- package/loops/actions/close-agent-issues/action.yml +43 -43
- package/loops/actions/create-agent-issues/action.yml +52 -52
- package/loops/actions/create-issue-comment/action.yml +29 -29
- package/loops/actions/download-agent-output/action.yml +53 -53
- package/loops/actions/link-pr-to-issue/action.yml +40 -40
- package/loops/actions/list-open-issues/action.yml +33 -33
- package/loops/actions/load-issue-context/action.yml +45 -45
- package/loops/actions/merge-agent-pr/action.yml +49 -49
- package/loops/actions/push-agent-branch/action.yml +45 -45
- package/loops/actions/remove-issue-labels/action.yml +37 -37
- package/loops/actions/update-agent-issues/action.yml +58 -58
- package/loops/actions/validate-merge-gate-output/action.yml +6 -1
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +9 -2
- package/loops/actions/validate-review-output/action.yml +35 -35
- package/loops/actions/validate-triage-output/action.yml +36 -36
- package/loops/actions/verify-composite-actions/action.yml +9 -9
- package/loops/actions/verify-refine-output/action.yml +9 -9
- package/loops/actions/verify-route-matrix/action.yml +9 -9
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +149 -29
- package/loops/docs/diagrams.md +26 -11
- package/loops/templates/agentics/agentics-maintenance.yml +121 -121
- package/loops/templates/issues/bug_report.yml +109 -109
- package/loops/templates/issues/feature_request.yml +75 -75
- package/loops/templates/release/github-release.yml +30 -30
- package/loops/workflows/agent-apply-review.md +5 -0
- package/loops/workflows/agent-implement.md +6 -0
- package/loops/workflows/agent-merge-gate.md +100 -3
- package/loops/workflows/authorize-bot-work.yml +105 -105
- package/loops/workflows/evidence.yml +79 -4
- package/loops/workflows/shared/platform-defaults.md +23 -23
- package/loops/workflows/work-router.yml +97 -21
- package/package.json +1 -1
|
@@ -78,7 +78,7 @@ route_field() {
|
|
|
78
78
|
unset EVENT ACTION LABEL ISSUE_LABELS EVENT_ISSUE_NUMBER EVENT_PR_NUMBER \
|
|
79
79
|
COMMENT_ON_PR COMMENT_SENDER_TYPE RUN_PR_NUMBER RUN_CONCLUSION RUN_ID \
|
|
80
80
|
SCHEDULE OPERATION INPUT_ISSUE_NUMBER INPUT_PR_NUMBER INPUT_MODE \
|
|
81
|
-
INPUT_CI_CONCLUSION INPUT_CI_RUN_ID INPUT_TRIGGER_KIND PR_MERGED EVIDENCE_ENABLED
|
|
81
|
+
INPUT_CI_CONCLUSION INPUT_CI_RUN_ID INPUT_TRIGGER_KIND PR_MERGED EVIDENCE_ENABLED REVIEW_STATE
|
|
82
82
|
|
|
83
83
|
for assignment in "${assignments[@]}"; do
|
|
84
84
|
key="${assignment%%=*}"
|
|
@@ -185,6 +185,12 @@ assert_route "a review comment routes to apply-review" apply-review \
|
|
|
185
185
|
EVENT=pull_request_review_comment EVENT_PR_NUMBER=7
|
|
186
186
|
assert_route "a submitted review routes to apply-review" apply-review \
|
|
187
187
|
EVENT=pull_request_review EVENT_PR_NUMBER=7
|
|
188
|
+
assert_route "a review requesting changes routes to apply-review" apply-review \
|
|
189
|
+
EVENT=pull_request_review REVIEW_STATE=changes_requested EVENT_PR_NUMBER=7
|
|
190
|
+
assert_route "an approval routes to merge-approved" merge-approved \
|
|
191
|
+
EVENT=pull_request_review REVIEW_STATE=approved EVENT_PR_NUMBER=7
|
|
192
|
+
assert "merge-approved carries the pull request" 7 \
|
|
193
|
+
"$(route_field pr-number EVENT=pull_request_review REVIEW_STATE=approved EVENT_PR_NUMBER=7)"
|
|
188
194
|
assert_route "a pull_request_target routes to bot-approve" bot-approve \
|
|
189
195
|
EVENT=pull_request_target ACTION=opened
|
|
190
196
|
assert_route "a merged pull request routes to evidence" evidence \
|
|
@@ -704,7 +710,7 @@ if [ "$(count -c 'attempts_so_far' "$ROUTER_YML")" -lt 2 ]; then
|
|
|
704
710
|
BELT_OK=0; echo "FAIL: dispatch sites must forward attempts_so_far" >&2
|
|
705
711
|
fi
|
|
706
712
|
# A second gate for a pull request whose gate is already queued or running reads the same CI
|
|
707
|
-
# verdict and
|
|
713
|
+
# verdict, and the belt's queue would run it twice.
|
|
708
714
|
if [ "$(count -c 'a merge-gate run is already live' "$ROUTER_YML")" -lt 2 ]; then
|
|
709
715
|
BELT_OK=0; echo "FAIL: both dispatch paths must skip a pull request whose gate is already live" >&2
|
|
710
716
|
fi
|
|
@@ -1079,6 +1085,17 @@ for route in "${EXCLUDED_ROUTES[@]}"; do
|
|
|
1079
1085
|
fi
|
|
1080
1086
|
done
|
|
1081
1087
|
|
|
1088
|
+
# merge-approved serves the gate and leaves with it.
|
|
1089
|
+
if worker_installed merge-gate; then
|
|
1090
|
+
if grep -q "route == 'merge-approved'" "$ROUTER_YML"; then PASS=$((PASS + 1)); else
|
|
1091
|
+
FAIL=$((FAIL + 1)); echo "FAIL: work-router.yml has no job for route 'merge-approved'" >&2
|
|
1092
|
+
fi
|
|
1093
|
+
elif grep -q "route == 'merge-approved'" "$ROUTER_YML"; then
|
|
1094
|
+
FAIL=$((FAIL + 1)); echo "FAIL: merge-approved is installed without the merge gate" >&2
|
|
1095
|
+
else
|
|
1096
|
+
PASS=$((PASS + 1))
|
|
1097
|
+
fi
|
|
1098
|
+
|
|
1082
1099
|
while read -r operation; do
|
|
1083
1100
|
if grep -q "route == '${operation}'" "$ROUTER_YML"; then
|
|
1084
1101
|
PASS=$((PASS + 1))
|
|
@@ -1415,7 +1432,93 @@ level=low")
|
|
|
1415
1432
|
printf '%s\n' "$blast_injection" >&2
|
|
1416
1433
|
fi
|
|
1417
1434
|
|
|
1435
|
+
# Ignored paths leave the size, never the protection: a protected path that only reaches the
|
|
1436
|
+
# script through ALL_PATHS still holds the merge.
|
|
1437
|
+
blast_ignored=$(PROTECTED_PATHS='^\.' OWNER_PATHS='' SENSITIVE_PATHS='' \
|
|
1438
|
+
HIGH_FILES=20 HIGH_LINES=800 MEDIUM_FILES=5 MEDIUM_LINES=200 \
|
|
1439
|
+
ALL_PATHS=$'src/ui/list.tsx\n.github/workflows/ci.yml' IGNORED_FILES=1 IGNORED_LINES=4 \
|
|
1440
|
+
bash "$BLAST_SCRIPT" 1 10 <<<"src/ui/list.tsx")
|
|
1441
|
+
printf '%s\n' "$blast_ignored" | grep -qx 'requires_review=true' ||
|
|
1442
|
+
{ BLAST_OK=0; echo "FAIL: a protected path outside the counted files no longer holds the merge" >&2; }
|
|
1443
|
+
|
|
1444
|
+
# An additive migration under an owner path asks for a second look, not its owner. Any other
|
|
1445
|
+
# owner path in the same diff still forces high.
|
|
1446
|
+
blast_additive() {
|
|
1447
|
+
PROTECTED_PATHS='^\.' OWNER_PATHS='(^|/)([Mm]igrations|auth)/' SENSITIVE_PATHS='' \
|
|
1448
|
+
HIGH_FILES=20 HIGH_LINES=800 MEDIUM_FILES=5 MEDIUM_LINES=200 \
|
|
1449
|
+
ADDITIVE_MIGRATIONS="$1" bash "$BLAST_SCRIPT" 2 120 <<<"$2" | sed -n 's/^level=//p'
|
|
1450
|
+
}
|
|
1451
|
+
[ "$(blast_additive 'src/Migrations/1_AddX.cs' $'src/Migrations/1_AddX.cs\nsrc/ui/x.tsx')" = medium ] ||
|
|
1452
|
+
{ BLAST_OK=0; echo "FAIL: an additive migration still forces its owner" >&2; }
|
|
1453
|
+
[ "$(blast_additive '' $'src/Migrations/1_AddX.cs\nsrc/ui/x.tsx')" = high ] ||
|
|
1454
|
+
{ BLAST_OK=0; echo "FAIL: a migration nobody proved additive no longer needs its owner" >&2; }
|
|
1455
|
+
[ "$(blast_additive 'src/Migrations/1_AddX.cs' $'src/Migrations/1_AddX.cs\nsrc/auth/login.ts')" = high ] ||
|
|
1456
|
+
{ BLAST_OK=0; echo "FAIL: an additive migration let another owner path through" >&2; }
|
|
1457
|
+
|
|
1458
|
+
# The shipped ignore list, against paths from real consumer pull requests.
|
|
1459
|
+
ignore_re=$(sed -n "s/^ BLAST_IGNORE_PATHS: '\(.*\)'$/\1/p" "$MERGE_GATE_WORKER_MD")
|
|
1460
|
+
for ignored in tests/Pliny.UnitTests/Agents/SecretRedactorTests.cs apps/api/tests/Numa.UnitTests/QuoteTests.cs \
|
|
1461
|
+
apps/web/src/views/chat/ui/internals-pane.test.tsx apps/web/src/shared/config/i18n/es.json \
|
|
1462
|
+
openspec/changes/archive/x/verification-plan.md docs/ARCH.md \
|
|
1463
|
+
src/Pliny.Infrastructure/Migrations/20261014225437_X.Designer.cs src/Migrations/AppDbContextModelSnapshot.cs; do
|
|
1464
|
+
grep -qE "$ignore_re" <<<"$ignored" ||
|
|
1465
|
+
{ BLAST_OK=0; echo "FAIL: BLAST_IGNORE_PATHS counts ${ignored} toward the diff's size" >&2; }
|
|
1466
|
+
done
|
|
1467
|
+
for counted in src/Pliny.Infrastructure/Agents/SecretRedactor.cs apps/web/src/views/chat/ui/internals-pane.tsx \
|
|
1468
|
+
src/Pliny.Infrastructure/Migrations/20261014225437_X.cs apps/api/src/Numa.Api/appsettings.json; do
|
|
1469
|
+
if grep -qE "$ignore_re" <<<"$counted"; then
|
|
1470
|
+
BLAST_OK=0; echo "FAIL: BLAST_IGNORE_PATHS ignores ${counted}, which ships" >&2
|
|
1471
|
+
fi
|
|
1472
|
+
done
|
|
1473
|
+
|
|
1418
1474
|
if [ "$BLAST_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
1475
|
+
|
|
1476
|
+
# A migration is additive only when its Up() creates and adds. Down() is all drops by design,
|
|
1477
|
+
# and a data rewrite or a required column with no default needs the owner.
|
|
1478
|
+
MIGRATION_OK=1
|
|
1479
|
+
MIGRATION_SCRIPT="${HERE}/../assess-blast-radius/migration-is-additive.sh"
|
|
1480
|
+
migration_case() {
|
|
1481
|
+
local name="$1" want="$2" patch="$3" got
|
|
1482
|
+
if bash "$MIGRATION_SCRIPT" <<<"$patch" >/dev/null; then got=additive; else got=owner; fi
|
|
1483
|
+
[ "$got" = "$want" ] || { MIGRATION_OK=0; echo "FAIL: migration '${name}' read as ${got}, expected ${want}" >&2; }
|
|
1484
|
+
}
|
|
1485
|
+
migration_case "a new table, dropped in Down()" additive '+ protected override void Up(MigrationBuilder migrationBuilder)
|
|
1486
|
+
+ {
|
|
1487
|
+
+ migrationBuilder.CreateTable(name: "TokenRevocations", columns: table => new { Id = table.Column<Guid>(nullable: false) });
|
|
1488
|
+
+ migrationBuilder.AddColumn<string>(
|
|
1489
|
+
+ name: "Note",
|
|
1490
|
+
+ table: "Quotes",
|
|
1491
|
+
+ nullable: true);
|
|
1492
|
+
+ }
|
|
1493
|
+
+ protected override void Down(MigrationBuilder migrationBuilder)
|
|
1494
|
+
+ {
|
|
1495
|
+
+ migrationBuilder.DropTable(name: "TokenRevocations");
|
|
1496
|
+
+ }'
|
|
1497
|
+
migration_case "a data rewrite" owner '+ protected override void Up(MigrationBuilder migrationBuilder)
|
|
1498
|
+
+ {
|
|
1499
|
+
+ migrationBuilder.Sql("UPDATE Bots SET Instructions = NULL");
|
|
1500
|
+
+ }'
|
|
1501
|
+
migration_case "a renamed column" owner '+ protected override void Up(MigrationBuilder migrationBuilder)
|
|
1502
|
+
+ {
|
|
1503
|
+
+ migrationBuilder.RenameColumn(name: "A", table: "T", newName: "B");
|
|
1504
|
+
+ }'
|
|
1505
|
+
migration_case "a required column with no default" owner '+ protected override void Up(MigrationBuilder migrationBuilder)
|
|
1506
|
+
+ {
|
|
1507
|
+
+ migrationBuilder.AddColumn<int>(
|
|
1508
|
+
+ name: "Level",
|
|
1509
|
+
+ table: "Quotes",
|
|
1510
|
+
+ nullable: false);
|
|
1511
|
+
+ }'
|
|
1512
|
+
migration_case "a required column with a default" additive '+ protected override void Up(MigrationBuilder migrationBuilder)
|
|
1513
|
+
+ {
|
|
1514
|
+
+ migrationBuilder.AddColumn<int>(
|
|
1515
|
+
+ name: "Level",
|
|
1516
|
+
+ table: "Quotes",
|
|
1517
|
+
+ nullable: false,
|
|
1518
|
+
+ defaultValue: 0);
|
|
1519
|
+
+ }'
|
|
1520
|
+
migration_case "no Up() in the patch" owner '+// generated'
|
|
1521
|
+
if [ "$MIGRATION_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
1419
1522
|
fi
|
|
1420
1523
|
|
|
1421
1524
|
# apply-review's validator compares the unresolved review threads on a pull request against the
|
|
@@ -1471,10 +1574,10 @@ if [ -f "$GATE_VALIDATOR" ] && worker_installed merge-gate; then
|
|
|
1471
1574
|
|
|
1472
1575
|
gate_case() {
|
|
1473
1576
|
local name="$1" want="$2" json="$3" conclusion="$4"
|
|
1474
|
-
local blast="${5:-low}" protected="${6:-false}" owner="${7:-false}"
|
|
1577
|
+
local blast="${5:-low}" protected="${6:-false}" owner="${7:-false}" code="${8-pass}"
|
|
1475
1578
|
printf '%s' "$json" > "$gate_fixture"
|
|
1476
1579
|
local got
|
|
1477
|
-
got=$(bash "$GATE_VALIDATOR" "$gate_fixture" 7 "$conclusion" "$blast" "$protected" "$owner" 0.8 2>&1)
|
|
1580
|
+
got=$(bash "$GATE_VALIDATOR" "$gate_fixture" 7 "$conclusion" "$blast" "$protected" "$owner" 0.8 "$code" 2>&1)
|
|
1478
1581
|
if [ "$got" != "$want" ]; then
|
|
1479
1582
|
VALIDATOR_OK=0
|
|
1480
1583
|
echo "FAIL: the merge-gate validator called '${name}' ${got}, expected ${want}" >&2
|
|
@@ -1500,6 +1603,14 @@ if [ -f "$GATE_VALIDATOR" ] && worker_installed merge-gate; then
|
|
|
1500
1603
|
gate_case "an owner path needs the owner" owner-review "$(gate_items "$(gate_comment assessed)")" success low false true
|
|
1501
1604
|
gate_case "a non-success CI conclusion blocks" blocked "$(gate_items "$(gate_comment assessed)")" failure low
|
|
1502
1605
|
|
|
1606
|
+
# check-diff on the pull request head is a measured fact like CI: failing it never merges,
|
|
1607
|
+
# a repository without the script is not held to it, and a job that did not report is a person's call.
|
|
1608
|
+
gate_case "failing check-diff blocks" blocked "$(gate_items "$(gate_comment assessed)")" success low false false fail
|
|
1609
|
+
gate_case "no check-diff installed auto-merges" auto-merge "$(gate_items "$(gate_comment assessed)")" success low false false absent
|
|
1610
|
+
gate_case "check-diff that did not report needs a person" \
|
|
1611
|
+
human-review "$(gate_items "$(gate_comment assessed)")" success low false false ''
|
|
1612
|
+
gate_case "remediating check-diff is accepted" remediated "$(gate_items "$(gate_comment remediated),${gate_push}")" success low false false fail
|
|
1613
|
+
|
|
1503
1614
|
# The agent's report decides the rest. This is the rule the whole redesign rests on: an
|
|
1504
1615
|
# unverified finding is a warning whatever severity it claims, so a model cannot fail the gate
|
|
1505
1616
|
# by asserting something it did not demonstrate, and cannot pass it by understating one it did.
|
|
@@ -1724,6 +1835,28 @@ else
|
|
|
1724
1835
|
fi
|
|
1725
1836
|
ev_absent 'secrets\.' "$EVIDENCE_YML" 'evidence.yml must need no secret; GITHUB_TOKEN is enough'
|
|
1726
1837
|
ev_absent 'gh (issue|pr) comment.*--attach' "$EVIDENCE_YML" 'gh --attach refuses installation tokens; commit the screenshots beside their plans'
|
|
1838
|
+
# An unmerged pull request's preview goes to its own branch, never to the pull request's branch
|
|
1839
|
+
# (a commit there invalidates the gate's verdict) and never to the default branch.
|
|
1840
|
+
preview_step="$(sed -n '/name: Commit a preview of the screenshots/,/name: Write the evidence comment/p' "$EVIDENCE_YML")"
|
|
1841
|
+
grep -q "if: steps.pr.outputs.merged != 'true'" <<<"$preview_step" ||
|
|
1842
|
+
{ EV_OK=0; echo "FAIL: the preview step must run only for an unmerged pull request" >&2; }
|
|
1843
|
+
grep -qF 'git push -q origin "HEAD:refs/heads/$PREVIEW"' <<<"$preview_step" ||
|
|
1844
|
+
{ EV_OK=0; echo "FAIL: the preview must be pushed to EVIDENCE_PREVIEW_BRANCH and nowhere else" >&2; }
|
|
1845
|
+
ev '^ EVIDENCE_PREVIEW_BRANCH: ' "$EVIDENCE_YML" 'evidence.yml does not declare EVIDENCE_PREVIEW_BRANCH'
|
|
1846
|
+
fi
|
|
1847
|
+
|
|
1848
|
+
# The gate replays the plan for a person it hands the pull request to, never on auto-merge: a
|
|
1849
|
+
# dispatch next to the merge is the race that sank agent-visual-verify.
|
|
1850
|
+
if worker_installed merge-gate; then
|
|
1851
|
+
ev '^ EVIDENCE_BEFORE_REVIEW: ' "$MERGE_GATE_WORKER_MD" 'merge-gate does not declare EVIDENCE_BEFORE_REVIEW'
|
|
1852
|
+
review_dispatch="$(sed -n '/name: Replay the verification plan for the reviewer/,/inputs\[pr-number\]/p' "$MERGE_GATE_WORKER_MD")"
|
|
1853
|
+
if ! grep -q '"human-review","owner-review"' <<<"$review_dispatch" || grep -q 'auto-merge' <<<"$review_dispatch" ||
|
|
1854
|
+
! grep -q 'inputs\[operation\]=evidence' <<<"$review_dispatch"; then
|
|
1855
|
+
EV_OK=0
|
|
1856
|
+
echo "FAIL: the gate must dispatch evidence for human-review and owner-review only" >&2
|
|
1857
|
+
fi
|
|
1858
|
+
grep -q 'name: Replay the verification plan for the owner' "$MERGE_GATE_WORKER_MD" ||
|
|
1859
|
+
{ EV_OK=0; echo "FAIL: a protected-path hold never reaches conclude, so it must dispatch evidence itself" >&2; }
|
|
1727
1860
|
fi
|
|
1728
1861
|
|
|
1729
1862
|
# The retired worker stays retired: nothing dispatches it and nothing waits for it.
|
|
@@ -2366,31 +2499,18 @@ for worker_md in "${WORKFLOWS_DIR}"/agent-*.md; do
|
|
|
2366
2499
|
done
|
|
2367
2500
|
if [ "$INPUT_WIRING_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
2368
2501
|
|
|
2369
|
-
#
|
|
2370
|
-
#
|
|
2371
|
-
#
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
if [ -z "$belt_dispatch_block" ]; then
|
|
2382
|
-
BELT_ONE_PER_TICK_OK=0
|
|
2383
|
-
echo "FAIL: the merge belt's gate dispatch could not be located, so its one-per-tick guard cannot be checked" >&2
|
|
2384
|
-
elif ! printf '%s\n' "$belt_dispatch_block" | grep -qE '^ *break$'; then
|
|
2385
|
-
BELT_ONE_PER_TICK_OK=0
|
|
2386
|
-
echo "FAIL: the merge belt dispatches a gate per eligible pull request and never breaks; only the last stays pending and the rest are cancelled in silence" >&2
|
|
2387
|
-
fi
|
|
2388
|
-
# And the group it relies on must still be the serialising one.
|
|
2389
|
-
# Anchored to the whole line: a substring search matched `merge-belt-renamed` and stayed green
|
|
2390
|
-
# through a mutation that removed the serialisation the break depends on.
|
|
2391
|
-
grep -qE '^ *group: merge-belt *$' "$ROUTER_YML" ||
|
|
2392
|
-
{ BELT_ONE_PER_TICK_OK=0; echo "FAIL: the merge gate is no longer serialised on the merge-belt concurrency group" >&2; }
|
|
2393
|
-
if [ "$BELT_ONE_PER_TICK_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
2502
|
+
# Several paths dispatch gates at once (the reconcile tick, CI's dispatch-merge-gate, the
|
|
2503
|
+
# conflict rescan). Without `queue: max` GitHub keeps one pending run in the group and cancels the
|
|
2504
|
+
# rest with no comment and no recorded attempt; every dispatch still returns 204.
|
|
2505
|
+
BELT_QUEUE_OK=1
|
|
2506
|
+
belt_caller=$(awk '/^ call-merge-gate:/ { found = 1; print; next } found && /^ [a-z]/ { exit } found { print }' "$ROUTER_YML")
|
|
2507
|
+
printf '%s
|
|
2508
|
+
' "$belt_caller" | grep -qE '^ *group: merge-belt *$' ||
|
|
2509
|
+
{ BELT_QUEUE_OK=0; echo "FAIL: the merge gate is no longer serialised on the merge-belt concurrency group" >&2; }
|
|
2510
|
+
printf '%s
|
|
2511
|
+
' "$belt_caller" | grep -qE '^ *queue: max *$' ||
|
|
2512
|
+
{ BELT_QUEUE_OK=0; echo "FAIL: call-merge-gate must queue its waiting gates (queue: max); a single-slot group cancels every gate but the last" >&2; }
|
|
2513
|
+
if [ "$BELT_QUEUE_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
2394
2514
|
|
|
2395
2515
|
# The specific wiring that broke, asserted end to end: the router resolves the run ID, the gate
|
|
2396
2516
|
# forwards it, and the action seeds from it rather than only from its own lookup.
|
package/loops/docs/diagrams.md
CHANGED
|
@@ -282,7 +282,7 @@ flowchart TD
|
|
|
282
282
|
gateFix("Repair<br/>read the failure, fix, verify.<br/>Empty evidence on a conflicting<br/>PR means the conflict is the fault") --> gatePush
|
|
283
283
|
gatePush(("Pushed<br/>CI runs again, gate re-enters"))
|
|
284
284
|
gateFix -.->|"cannot fix"| gateHuman
|
|
285
|
-
gateProtected{"protected_changes (rung 4b)<br/>blast radius from paths
|
|
285
|
+
gateProtected{"protected_changes (rung 4b)<br/>blast radius from paths, CODEOWNERS<br/>and the size of the code that ships"}
|
|
286
286
|
gateProtected -.->|"protected path"| gateOwner
|
|
287
287
|
gateProtected -->|"measured low, medium or high"| gateAssess
|
|
288
288
|
gateAssess("Agent (rung 5)<br/>find defects, verify them,<br/>rate recoverability") --> gateDisposition
|
|
@@ -294,6 +294,10 @@ flowchart TD
|
|
|
294
294
|
gateMerge(("AUTO-MERGE<br/>squash, issue closed,<br/>pr-pending removed"))
|
|
295
295
|
gateHuman(("HUMAN REVIEW<br/>review label,<br/>implement label kept"))
|
|
296
296
|
gateOwner(("OWNER REVIEW<br/>review + owner-review,<br/>CODEOWNERS asked when named"))
|
|
297
|
+
gateHuman --> gatePreview
|
|
298
|
+
gateOwner --> gatePreview
|
|
299
|
+
gatePreview("operation=evidence<br/>screenshots of the head<br/>before the person decides") --> gateApproved
|
|
300
|
+
gateApproved(("Approved by a writer<br/>merge-approved merges<br/>the approved commit"))
|
|
297
301
|
gateBlocked(("BLOCKED<br/>review + blocked,<br/>the belt does not retry"))
|
|
298
302
|
gateIdle(("Idle<br/>not ours, or not open"))
|
|
299
303
|
gateFix -.->|"attempt 6 of 6"| gateBlocked
|
|
@@ -305,11 +309,11 @@ flowchart TD
|
|
|
305
309
|
classDef failure fill:#fff0f0,stroke:#ef2929,stroke-width:2px,color:#8b1a1a
|
|
306
310
|
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
307
311
|
class gateStart start
|
|
308
|
-
class gateBranch,gateFacts,gateFix,gateAssess action
|
|
312
|
+
class gateBranch,gateFacts,gateFix,gateAssess,gatePreview action
|
|
309
313
|
class gateSubject,gateCi,gateProtected,gateDisposition decision
|
|
310
314
|
class gateIdle idle
|
|
311
315
|
class gateHuman,gateOwner,gateBlocked failure
|
|
312
|
-
class gateMerge,gatePush success
|
|
316
|
+
class gateMerge,gatePush,gateApproved success
|
|
313
317
|
```
|
|
314
318
|
|
|
315
319
|
A protected-path match holds the merge but does not stop the repair path: the agent may still fix
|
|
@@ -321,6 +325,12 @@ agent ran. A category the diff touches is no longer, on its own, a reason to par
|
|
|
321
325
|
only a verified high or critical finding, a sensitive path, or a change that would be hard to undo
|
|
322
326
|
sends it to a person.
|
|
323
327
|
|
|
328
|
+
Size counts only code that ships: tests, docs, specs, translations and generated migration code
|
|
329
|
+
(`BLAST_IGNORE_PATHS`) are left out, and a new EF Core migration whose `Up()` only creates and adds
|
|
330
|
+
is a medium change rather than an owner one. When the gate parks a pull request for a person it
|
|
331
|
+
dispatches `operation=evidence`, so the screenshots arrive before the decision, and an approval
|
|
332
|
+
from anyone with write access merges the approved commit (`merge-approved` in the router).
|
|
333
|
+
|
|
324
334
|
---
|
|
325
335
|
|
|
326
336
|
## agent-apply-review.md
|
|
@@ -628,14 +638,16 @@ Archives what a merged pull request looks like. Not an agent: the plan was writt
|
|
|
628
638
|
implemented the change (`pc-repo-verify`), and this replays it. The router calls it when a pull
|
|
629
639
|
request merges, and `operation=evidence` runs it by hand. Nothing waits on it. The screenshots are
|
|
630
640
|
committed beside each plan, in an `evidence/` folder on the default branch, so the record stays with
|
|
631
|
-
the change it proves; the comment embeds a manifest that Project Health reads.
|
|
632
|
-
|
|
641
|
+
the change it proves; the comment embeds a manifest that Project Health reads. The merge gate also
|
|
642
|
+
runs it when it hands a pull request to a person: that pull request is not merged, so its
|
|
643
|
+
screenshots go to a preview folder (`pr-<number>/`) on the `evidence-previews` branch, and the
|
|
644
|
+
merge replaces them with the archived ones.
|
|
633
645
|
The capture job runs the pull request's code with a read-only token; only the publish job writes,
|
|
634
646
|
and it runs no repository code.
|
|
635
647
|
|
|
636
648
|
```mermaid
|
|
637
649
|
flowchart TD
|
|
638
|
-
evStart("router: pull request merged
|
|
650
|
+
evStart("router: pull request merged,<br/>the gate parked it for a person,<br/>or operation=evidence") --> evPlans
|
|
639
651
|
evPlans{"verification-plan.md<br/>in the pull request?"}
|
|
640
652
|
evPlans -.->|"no"| evIdle
|
|
641
653
|
evPlans -->|"yes"| evInspect
|
|
@@ -648,8 +660,11 @@ flowchart TD
|
|
|
648
660
|
evUp -.->|"no, after the timeout"| evUnreached
|
|
649
661
|
evReplay["replay each waypoint<br/>in headless Chromium,<br/>one JPEG per waypoint"] --> evPublish
|
|
650
662
|
evUnreached["record why nothing<br/>was captured"] --> evPublish
|
|
651
|
-
evPublish
|
|
652
|
-
|
|
663
|
+
evPublish{"publish (GITHUB_TOKEN)<br/>merged?"}
|
|
664
|
+
evPublish -->|"yes"| evDone
|
|
665
|
+
evPublish -->|"not yet"| evPreview
|
|
666
|
+
evDone(("Archived beside the plan<br/>on the default branch,<br/>one comment upserted"))
|
|
667
|
+
evPreview(("Preview on evidence-previews<br/>pr-number folder,<br/>same comment"))
|
|
653
668
|
evIdle(("Nothing to capture"))
|
|
654
669
|
|
|
655
670
|
classDef start fill:#ffffff,stroke:#172033,stroke-width:2px,color:#172033
|
|
@@ -658,8 +673,8 @@ flowchart TD
|
|
|
658
673
|
classDef idle fill:#f4f4f6,stroke:#5b5b66,stroke-width:2px,color:#2b2b33
|
|
659
674
|
classDef success fill:#e8f8ec,stroke:#18883c,stroke-width:2px,color:#145a32
|
|
660
675
|
class evStart start
|
|
661
|
-
class evApp,evReplay,evUnreached
|
|
662
|
-
class evPlans,evInspect,evUp decision
|
|
676
|
+
class evApp,evReplay,evUnreached action
|
|
677
|
+
class evPlans,evInspect,evUp,evPublish decision
|
|
663
678
|
class evIdle idle
|
|
664
|
-
class evDone success
|
|
679
|
+
class evDone,evPreview success
|
|
665
680
|
```
|
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/agentics/agentics-maintenance.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
-
# Derived from the gh-aw v0.87.5 maintenance workflow.
|
|
3
|
-
# Copy provenance is documented in docs/ownership.md.
|
|
4
|
-
name: Agentic Maintenance
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
schedule:
|
|
8
|
-
- cron: "37 0 * * *"
|
|
9
|
-
workflow_dispatch:
|
|
10
|
-
inputs:
|
|
11
|
-
operation:
|
|
12
|
-
description: Optional maintenance operation to run
|
|
13
|
-
required: false
|
|
14
|
-
type: choice
|
|
15
|
-
default: ""
|
|
16
|
-
options:
|
|
17
|
-
- ""
|
|
18
|
-
- disable
|
|
19
|
-
- enable
|
|
20
|
-
- update
|
|
21
|
-
- upgrade
|
|
22
|
-
- safe_outputs
|
|
23
|
-
- create_labels
|
|
24
|
-
- activity_report
|
|
25
|
-
- close_agentic_workflows_issues
|
|
26
|
-
- clean_cache_memories
|
|
27
|
-
- update_pull_request_branches
|
|
28
|
-
- validate
|
|
29
|
-
- forecast
|
|
30
|
-
run_url:
|
|
31
|
-
description: Run URL or run ID required for safe_outputs
|
|
32
|
-
required: false
|
|
33
|
-
type: string
|
|
34
|
-
default: ""
|
|
35
|
-
workflow_call:
|
|
36
|
-
inputs:
|
|
37
|
-
operation:
|
|
38
|
-
required: false
|
|
39
|
-
type: string
|
|
40
|
-
default: ""
|
|
41
|
-
run_url:
|
|
42
|
-
required: false
|
|
43
|
-
type: string
|
|
44
|
-
default: ""
|
|
45
|
-
|
|
46
|
-
permissions: {}
|
|
47
|
-
|
|
48
|
-
jobs:
|
|
49
|
-
close-expired-discussions:
|
|
50
|
-
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
51
|
-
runs-on: agents-arc
|
|
52
|
-
permissions:
|
|
53
|
-
discussions: write
|
|
54
|
-
steps:
|
|
55
|
-
- name: Setup Scripts
|
|
56
|
-
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
57
|
-
with:
|
|
58
|
-
destination: ${{ runner.temp }}/gh-aw/actions
|
|
59
|
-
- name: Close expired discussions
|
|
60
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
61
|
-
with:
|
|
62
|
-
script: |
|
|
63
|
-
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
64
|
-
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
65
|
-
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs');
|
|
66
|
-
await main();
|
|
67
|
-
close-expired-issues:
|
|
68
|
-
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
69
|
-
runs-on: agents-arc
|
|
70
|
-
permissions:
|
|
71
|
-
issues: write
|
|
72
|
-
steps:
|
|
73
|
-
- name: Setup Scripts
|
|
74
|
-
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
75
|
-
with:
|
|
76
|
-
destination: ${{ runner.temp }}/gh-aw/actions
|
|
77
|
-
- name: Close expired issues
|
|
78
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
79
|
-
with:
|
|
80
|
-
script: |
|
|
81
|
-
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
82
|
-
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
83
|
-
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs');
|
|
84
|
-
await main();
|
|
85
|
-
close-expired-pull-requests:
|
|
86
|
-
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
87
|
-
runs-on: agents-arc
|
|
88
|
-
permissions:
|
|
89
|
-
pull-requests: write
|
|
90
|
-
steps:
|
|
91
|
-
- name: Setup Scripts
|
|
92
|
-
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
93
|
-
with:
|
|
94
|
-
destination: ${{ runner.temp }}/gh-aw/actions
|
|
95
|
-
- name: Close expired pull requests
|
|
96
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
97
|
-
with:
|
|
98
|
-
script: |
|
|
99
|
-
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
100
|
-
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
101
|
-
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs');
|
|
102
|
-
await main();
|
|
103
|
-
cleanup-cache-memory:
|
|
104
|
-
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
|
|
105
|
-
runs-on: agents-arc
|
|
106
|
-
permissions:
|
|
107
|
-
actions: write
|
|
108
|
-
steps:
|
|
109
|
-
- name: Setup Scripts
|
|
110
|
-
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
111
|
-
with:
|
|
112
|
-
destination: ${{ runner.temp }}/gh-aw/actions
|
|
113
|
-
- name: Cleanup outdated cache-memory entries
|
|
114
|
-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
115
|
-
with:
|
|
116
|
-
script: |
|
|
117
|
-
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
118
|
-
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
119
|
-
const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs');
|
|
120
|
-
await main();
|
|
121
|
-
#
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/templates/agentics/agentics-maintenance.yml. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
2
|
+
# Derived from the gh-aw v0.87.5 maintenance workflow.
|
|
3
|
+
# Copy provenance is documented in docs/ownership.md.
|
|
4
|
+
name: Agentic Maintenance
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "37 0 * * *"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
operation:
|
|
12
|
+
description: Optional maintenance operation to run
|
|
13
|
+
required: false
|
|
14
|
+
type: choice
|
|
15
|
+
default: ""
|
|
16
|
+
options:
|
|
17
|
+
- ""
|
|
18
|
+
- disable
|
|
19
|
+
- enable
|
|
20
|
+
- update
|
|
21
|
+
- upgrade
|
|
22
|
+
- safe_outputs
|
|
23
|
+
- create_labels
|
|
24
|
+
- activity_report
|
|
25
|
+
- close_agentic_workflows_issues
|
|
26
|
+
- clean_cache_memories
|
|
27
|
+
- update_pull_request_branches
|
|
28
|
+
- validate
|
|
29
|
+
- forecast
|
|
30
|
+
run_url:
|
|
31
|
+
description: Run URL or run ID required for safe_outputs
|
|
32
|
+
required: false
|
|
33
|
+
type: string
|
|
34
|
+
default: ""
|
|
35
|
+
workflow_call:
|
|
36
|
+
inputs:
|
|
37
|
+
operation:
|
|
38
|
+
required: false
|
|
39
|
+
type: string
|
|
40
|
+
default: ""
|
|
41
|
+
run_url:
|
|
42
|
+
required: false
|
|
43
|
+
type: string
|
|
44
|
+
default: ""
|
|
45
|
+
|
|
46
|
+
permissions: {}
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
close-expired-discussions:
|
|
50
|
+
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
51
|
+
runs-on: agents-arc
|
|
52
|
+
permissions:
|
|
53
|
+
discussions: write
|
|
54
|
+
steps:
|
|
55
|
+
- name: Setup Scripts
|
|
56
|
+
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
57
|
+
with:
|
|
58
|
+
destination: ${{ runner.temp }}/gh-aw/actions
|
|
59
|
+
- name: Close expired discussions
|
|
60
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
61
|
+
with:
|
|
62
|
+
script: |
|
|
63
|
+
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
64
|
+
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
65
|
+
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs');
|
|
66
|
+
await main();
|
|
67
|
+
close-expired-issues:
|
|
68
|
+
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
69
|
+
runs-on: agents-arc
|
|
70
|
+
permissions:
|
|
71
|
+
issues: write
|
|
72
|
+
steps:
|
|
73
|
+
- name: Setup Scripts
|
|
74
|
+
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
75
|
+
with:
|
|
76
|
+
destination: ${{ runner.temp }}/gh-aw/actions
|
|
77
|
+
- name: Close expired issues
|
|
78
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
79
|
+
with:
|
|
80
|
+
script: |
|
|
81
|
+
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
82
|
+
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
83
|
+
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs');
|
|
84
|
+
await main();
|
|
85
|
+
close-expired-pull-requests:
|
|
86
|
+
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }}
|
|
87
|
+
runs-on: agents-arc
|
|
88
|
+
permissions:
|
|
89
|
+
pull-requests: write
|
|
90
|
+
steps:
|
|
91
|
+
- name: Setup Scripts
|
|
92
|
+
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
93
|
+
with:
|
|
94
|
+
destination: ${{ runner.temp }}/gh-aw/actions
|
|
95
|
+
- name: Close expired pull requests
|
|
96
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
97
|
+
with:
|
|
98
|
+
script: |
|
|
99
|
+
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
100
|
+
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
101
|
+
const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs');
|
|
102
|
+
await main();
|
|
103
|
+
cleanup-cache-memory:
|
|
104
|
+
if: ${{ !(github.event.repository.fork) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }}
|
|
105
|
+
runs-on: agents-arc
|
|
106
|
+
permissions:
|
|
107
|
+
actions: write
|
|
108
|
+
steps:
|
|
109
|
+
- name: Setup Scripts
|
|
110
|
+
uses: github/gh-aw-actions/setup@2a78d04403fdc6907d0f05327cffac9dbad5312d # v0.87.5
|
|
111
|
+
with:
|
|
112
|
+
destination: ${{ runner.temp }}/gh-aw/actions
|
|
113
|
+
- name: Cleanup outdated cache-memory entries
|
|
114
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
115
|
+
with:
|
|
116
|
+
script: |
|
|
117
|
+
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
|
118
|
+
setupGlobals(core, github, context, exec, io, getOctokit);
|
|
119
|
+
const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs');
|
|
120
|
+
await main();
|
|
121
|
+
#
|