Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 42 additions & 48 deletions .github/workflows/sweep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1344,7 +1344,9 @@ jobs:
- uses: actions/checkout@v7
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
with:
ref: ${{ steps.publication-context.outputs.source_sha }}
# The artifact validator binds the producer SHA. The publisher itself
# must run the current repaired implementation, not historical code.
ref: main
filter: blob:none
fetch-depth: 0
persist-credentials: false
Expand Down Expand Up @@ -1393,6 +1395,33 @@ jobs:
EXACT_REVIEW_TARGET_REPO: ${{ steps.publication-context.outputs.target_repo }}
run: pnpm run --silent repair:exact-review-bundle validate

- name: Identify legacy tuple-less exact artifact
id: legacy-exact-artifact
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
env:
REPORT_PATH: .artifacts/exact-review-bundle/review/${{ steps.publication-context.outputs.item_number }}.md
run: |
set -euo pipefail
REPORT_PATH="$REPORT_PATH" node <<'NODE'
const fs = require("node:fs");
const reportPath = process.env.REPORT_PATH;
let legacyTupleless = false;
if (fs.existsSync(reportPath)) {
const markdown = fs.readFileSync(reportPath, "utf8");
const owner = /^review_lease_owner:\s*(.+)\s*$/m.exec(markdown)?.[1]?.trim() || "";
const commentId = Number(
/^review_lease_comment_id:\s*(\d+)\s*$/m.exec(markdown)?.[1] || "0",
);
legacyTupleless =
(!owner || owner === "unknown") &&
(!Number.isInteger(commentId) || commentId <= 0);
}
fs.appendFileSync(
process.env.GITHUB_OUTPUT,
`legacy_tupleless=${legacyTupleless ? "true" : "false"}\n`,
);
NODE

- name: Stage validated exact review artifact
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
run: |
Expand Down Expand Up @@ -1420,22 +1449,22 @@ jobs:
permission-statuses: read

- name: Create state token
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
if: ${{ steps.publication-context.outputs.claimed == 'true' && steps.legacy-exact-artifact.outputs.legacy_tupleless != 'true' }}
id: state-token
uses: ./.github/actions/create-state-token
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}

- uses: ./.github/actions/setup-state
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
if: ${{ steps.publication-context.outputs.claimed == 'true' && steps.legacy-exact-artifact.outputs.legacy_tupleless != 'true' }}
id: setup-state
with:
token: ${{ steps.state-token.outputs.token }}
fetch-depth: 1

- name: Publish event result and apply safe close
if: ${{ steps.publication-context.outputs.claimed == 'true' }}
if: ${{ steps.publication-context.outputs.claimed == 'true' && steps.legacy-exact-artifact.outputs.legacy_tupleless != 'true' }}
id: publish-event-result
env:
GH_TOKEN: ${{ steps.target-write-token.outputs.token }}
Expand Down Expand Up @@ -1571,7 +1600,7 @@ jobs:

- name: Queue fresh review after source drift
id: queue-source-drift-review
if: ${{ steps.publication-context.outputs.claimed == 'true' && steps.publish-event-result.outputs.requeue_latest == 'true' }}
if: ${{ steps.publication-context.outputs.claimed == 'true' && (steps.publish-event-result.outputs.requeue_latest == 'true' || steps.legacy-exact-artifact.outputs.legacy_tupleless == 'true') }}
env:
CLAIM_DECISION: ${{ steps.publication-context.outputs.decision }}
PRODUCER_RUN_ATTEMPT: ${{ steps.publication-context.outputs.generation_attempt }}
Expand Down Expand Up @@ -1663,13 +1692,17 @@ jobs:
ROUTING_DEFERRED: ${{ steps.publish-event-result.outputs.routing_deferred }}
POLICY_NOOP: ${{ steps.publish-event-result.outputs.policy_noop }}
REQUEUE_LATEST: ${{ steps.publish-event-result.outputs.requeue_latest }}
LEGACY_TUPLELESS: ${{ steps.legacy-exact-artifact.outputs.legacy_tupleless }}
REVIEW_ONLY: ${{ fromJSON(steps.publication-context.outputs.decision).sourceAction == 'failed_review_shard_recovery' && 'true' || 'false' }}
run: |
state="Failed"
detail="The review artifact was captured, but publication did not finish cleanly."
if [ "$POLICY_NOOP" = "true" ]; then
state="Complete"
detail="The exact review reached a deterministic policy veto; no action was taken."
elif [ "$LEGACY_TUPLELESS" = "true" ] && [ "${{ steps.queue-source-drift-review.outcome }}" = "success" ]; then
state="Complete"
detail="The legacy artifact lacked its durable review lease tuple; one fresh exact review was queued."
elif [ "$REQUEUE_LATEST" = "true" ] && [ "${{ steps.queue-source-drift-review.outcome }}" = "success" ]; then
state="Complete"
detail="The item changed after review; one fresh exact review was queued."
Expand Down Expand Up @@ -1722,48 +1755,6 @@ jobs:
gh api "repos/$TARGET_REPO/issues/$ITEM_NUMBER/reactions/$reaction_id" --method DELETE || true
done <<< "$reaction_ids"

- name: Publish exact review action ledger
if: ${{ always() && steps.publication-context.outputs.claimed == 'true' && steps.setup-state.outcome == 'success' }}
continue-on-error: true
env:
GENERATION_ATTEMPT: ${{ steps.publication-context.outputs.generation_attempt }}
run: |
set -euo pipefail
test -n "${CLAWSWEEPER_STATE_DIR:-}"
mkdir -p .artifacts
event_paths_file=".artifacts/exact-review-action-ledger-paths.txt"
: > "$event_paths_file"
source_root=".artifacts/exact-review-bundle/action-ledger"
if [ -d "$source_root/ledger" ]; then
pnpm run --silent publish-action-events -- \
--source-root "$source_root" \
--state-root "$CLAWSWEEPER_STATE_DIR" \
--expected-producer-job event-review-apply \
--expected-producer-run-id "${{ steps.publication-context.outputs.producer_run_id }}" \
--expected-producer-sha "${{ steps.publication-context.outputs.source_sha }}" \
--expected-producer-run-attempt "$GENERATION_ATTEMPT" | jq -r '.paths[]?' >> "$event_paths_file"
fi
publisher_ledger_root="${CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT:-}"
if [ -n "$publisher_ledger_root" ] && [ -d "$publisher_ledger_root/ledger" ]; then
pnpm run --silent publish-action-events -- \
--source-root "$publisher_ledger_root" \
--state-root "$CLAWSWEEPER_STATE_DIR" \
--expected-producer-job "$GITHUB_JOB" | jq -r '.paths[]?' >> "$event_paths_file"
fi
sort -u -o "$event_paths_file" "$event_paths_file"
if [ ! -s "$event_paths_file" ]; then
echo "No exact review action events to publish."
exit 0
fi
while IFS= read -r event_path; do
test -f "$CLAWSWEEPER_STATE_DIR/$event_path"
mkdir -p "$(dirname "$event_path")"
cp "$CLAWSWEEPER_STATE_DIR/$event_path" "$event_path"
done < "$event_paths_file"
node dist/clawsweeper.js publish-action-event-paths \
--paths-file "$event_paths_file" \
--message "chore: append exact review action ledger"

- name: Release unsuccessful publisher-owned review lease
if: ${{ always() && steps.publication-context.outputs.claimed == 'true' && steps.publication-context.outputs.live_proceeded == 'true' && (steps.publish-event-result.outcome != 'success' || (steps.publish-event-result.outputs.requeue_latest == 'true' && steps.queue-source-drift-review.outcome != 'success')) }}
env:
Expand Down Expand Up @@ -1799,12 +1790,15 @@ jobs:
GUARDED_OPEN: ${{ steps.publish-event-result.outputs.guarded_open }}
POLICY_NOOP: ${{ steps.publish-event-result.outputs.policy_noop }}
REQUEUE_LATEST: ${{ steps.publish-event-result.outputs.requeue_latest }}
LEGACY_TUPLELESS: ${{ steps.legacy-exact-artifact.outputs.legacy_tupleless }}
SOURCE_DRIFT_OUTCOME: ${{ steps.queue-source-drift-review.outcome }}
DEFERRED_ROUTE_OUTCOME: ${{ steps.queue-deferred-verdict-router.outcome }}
REVIEW_ONLY: ${{ fromJSON(steps.publication-context.outputs.decision).sourceAction == 'failed_review_shard_recovery' && 'true' || 'false' }}
run: |
outcome=failure
if [ "$PRIOR_JOB_STATUS" = "cancelled" ]; then
if [ "$LEGACY_TUPLELESS" = "true" ] && [ "$SOURCE_DRIFT_OUTCOME" = "success" ]; then
outcome=success
elif [ "$PRIOR_JOB_STATUS" = "cancelled" ]; then
outcome=cancelled
elif [ "$PRIOR_JOB_STATUS" = "success" ]; then
if [ "$PUBLISH_OUTCOME" = "success" ] && [ "$REQUEUE_LATEST" = "true" ] && [ "$SOURCE_DRIFT_OUTCOME" = "success" ]; then
Expand Down
3 changes: 1 addition & 2 deletions test/clawsweeper-action-ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,6 @@ test("sweep publishes complete immutable shards for every review and apply produ
"Publish selected review comment action ledger",
"Publish failed-review retry action ledger",
"Finalize exact event action ledger",
"Publish exact review action ledger",
"Finalize apply proof action ledger",
"Publish apply proof action events",
"Publish apply action events",
Expand All @@ -801,7 +800,7 @@ test("sweep publishes complete immutable shards for every review and apply produ
assert.match(workflow, /include-hidden-files: true/);
assert.match(workflow, /--state-root "\$CLAWSWEEPER_STATE_DIR"/);
assert.match(workflow, /durable_event_path="\$CLAWSWEEPER_STATE_DIR\/\$event_path"/);
assert.equal((workflow.match(/publish-action-event-paths/g) ?? []).length, 7);
assert.equal((workflow.match(/publish-action-event-paths/g) ?? []).length, 6);
assert.doesNotMatch(
workflow,
/--message "chore: append (?:review|apply).*action ledger"[\s\S]{0,180}--path "ledger\/v1\/events"/,
Expand Down
24 changes: 2 additions & 22 deletions test/repair/command-ops-action-ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,38 +76,18 @@ test("direct repair requeues forward a stable dispatch receipt and publish it",
assert.match(workflow, /--max-requeue-depth 1/);
});

test("exact review publisher includes command status receipts after the status mutation", () => {
const setupAction = readText(".github/actions/setup-action-ledger/action.yml");
const source = readText("src/repair/update-command-status.ts");
test("exact review publisher bypasses the legacy action ledger", () => {
const workflow = readText(".github/workflows/sweep.yml");
const publisherJob = workflow.indexOf("\n event-review-publish:");
const statusMutation = workflow.indexOf("- name: Mark re-review complete", publisherJob);
const ledgerPublish = workflow.indexOf(
"- name: Publish exact review action ledger",
publisherJob,
);
const nextStep = workflow.indexOf(
"- name: Release unsuccessful publisher-owned review lease",
ledgerPublish,
);
const publishStep = workflow.slice(ledgerPublish, nextStep);

assert.ok(publisherJob >= 0);
assert.ok(statusMutation > publisherJob);
assert.ok(ledgerPublish > statusMutation);
assert.ok(nextStep > ledgerPublish);
assert.match(setupAction, /CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT=\$output_root/);
assert.match(source, /await flushCommandActionEvents\(\)/);
assert.match(publishStep, /if: \$\{\{ always\(\)[^\n]*\}\}/);
assert.match(publishStep, /steps\.publication-context\.outputs\.claimed == 'true'/);
assert.match(publishStep, /steps\.setup-state\.outcome == 'success'/);
assert.match(publishStep, /continue-on-error: true/);
assert.match(publishStep, /source_root="\.artifacts\/exact-review-bundle\/action-ledger"/);
assert.match(publishStep, /CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT:-/);
assert.match(publishStep, /publisher_ledger_root\/ledger/);
assert.match(publishStep, /--expected-producer-job "\$GITHUB_JOB"/);
assert.match(publishStep, /sort -u -o "\$event_paths_file" "\$event_paths_file"/);
assert.match(publishStep, /--message "chore: append exact review action ledger"/);
assert.equal(ledgerPublish, -1);
});

function assertCommandFinalizerUsesCanonicalRoot(step: string): void {
Expand Down
37 changes: 25 additions & 12 deletions test/sweep-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ test("review and apply primary boundaries ignore ledger-only failures", () => {
"event-review-apply",
"Queue durable exact review publication",
);
const exactLedger = step("event-review-publish", "Publish exact review action ledger");
const exactSteps = job("event-review-apply").steps;
assert.match(exactPrimary.run ?? "", /outcome=(?:failure|cancelled|success)/);
assert.match(exactPrimary.run ?? "", /REVIEW_OUTCOME.*cancelled/);
Expand All @@ -141,7 +140,12 @@ test("review and apply primary boundaries ignore ledger-only failures", () => {
assert.ok(exactSteps.indexOf(exactUpload) < exactSteps.indexOf(exactPublicationQueue));
assert.ok(exactSteps.indexOf(exactPublicationQueue) < exactSteps.indexOf(exactQueue));
assert.ok(exactSteps.indexOf(exactQueue) > exactSteps.indexOf(exactPrimary));
assert.match(exactLedger.run ?? "", /expected-producer-run-attempt/);
assert.equal(
job("event-review-publish").steps.some(
(candidate) => candidate.name === "Publish exact review action ledger",
),
false,
);

const ledgerDownload = job("publish").steps.find(
(candidate) => candidate.id === "download-review-action-ledger",
Expand Down Expand Up @@ -438,20 +442,29 @@ test("exact event review hands immutable artifacts to the queue-bounded publishe

const download = step(publisher, "Download exact review artifact bundle");
const validate = step(publisher, "Validate exact review artifact bundle");
const legacyArtifact = step(publisher, "Identify legacy tuple-less exact artifact");
const targetWriteStep = step(publisher, "Create target write token");
const stateSetup = publisher.steps.find((candidate) => candidate.uses?.endsWith("/setup-state"));
assert.ok(stateSetup);
const publisherCheckout = publisher.steps.find(
(candidate) => candidate.uses === "actions/checkout@v7",
);
assert.ok(publisherCheckout);
assert.equal(publisherCheckout.with?.ref, "main");
assert.equal(download.uses, "actions/download-artifact@v8");
assert.equal(download.with?.name, "${{ steps.publication-context.outputs.artifact_name }}");
assert.equal(
download.with?.["run-id"],
"${{ steps.publication-context.outputs.producer_run_id }}",
);
assert.match(validate.run ?? "", /repair:exact-review-bundle validate/);
assert.match(legacyArtifact.run ?? "", /review_lease_owner/);
assert.match(legacyArtifact.run ?? "", /review_lease_comment_id/);
assert.doesNotMatch(create.run ?? "", /repair:exact-review-bundle -- create/);
assert.doesNotMatch(validate.run ?? "", /repair:exact-review-bundle -- validate/);
assert.ok(publisher.steps.indexOf(validate) < publisher.steps.indexOf(targetWriteStep));
assert.ok(publisher.steps.indexOf(validate) < publisher.steps.indexOf(stateSetup));
assert.match(stateSetup.if ?? "", /legacy-exact-artifact\.outputs\.legacy_tupleless != 'true'/);

const publish = step(publisher, "Publish event result and apply safe close");
assert.match(publish.run ?? "", /live_state=.*gh api/);
Expand All @@ -473,6 +486,7 @@ test("exact event review hands immutable artifacts to the queue-bounded publishe
assert.match(deferredRoute.run ?? "", /repair-comment-router\.yml/);
const drift = step(publisher, "Queue fresh review after source drift");
assert.match(drift.if ?? "", /requeue_latest == 'true'/);
assert.match(drift.if ?? "", /legacy-exact-artifact\.outputs\.legacy_tupleless == 'true'/);
assert.match(drift.run ?? "", /x-clawsweeper-exact-review-signature/);
assert.match(drift.run ?? "", /internal\/exact-review\/enqueue/);
assert.match(drift.run ?? "", /decision\.sourceAction === "failed_review_shard_recovery"/);
Expand All @@ -484,13 +498,10 @@ test("exact event review hands immutable artifacts to the queue-bounded publishe
const reaction = step(publisher, "React to target item completion");
assert.match(reaction.if ?? "", /requeue_latest != 'true'/);
assert.doesNotMatch(reaction.if ?? "", /publication-context.*live_guarded_open/);
const ledger = step(publisher, "Publish exact review action ledger");
assert.equal(ledger["continue-on-error"], true);
assert.match(ledger.run ?? "", /CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT:-/);
assert.match(ledger.run ?? "", /--expected-producer-job event-review-apply/);
assert.match(ledger.run ?? "", /--expected-producer-run-id/);
assert.match(ledger.run ?? "", /--expected-producer-sha/);
assert.match(ledger.run ?? "", /--expected-producer-run-attempt "\$GENERATION_ATTEMPT"/);
assert.equal(
publisher.steps.some((candidate) => candidate.name === "Publish exact review action ledger"),
false,
);
const publishResult = step(publisher, "Export exact review publication result");
const publishComplete = step(publisher, "Complete durable exact review publication");
const releaseTerminal = step(publisher, "Release terminal review leases");
Expand All @@ -500,7 +511,9 @@ test("exact event review hands immutable artifacts to the queue-bounded publishe
assert.match(releaseUnsuccessful.run ?? "", /\.user\.login == \\"clawsweeper\[bot\]\\"/);
assert.match(releaseUnsuccessful.run ?? "", /content == "eyes"/);
assert.match(publishResult.env?.PRIOR_JOB_STATUS ?? "", /job\.status/);
assert.match(publishResult.env?.LEGACY_TUPLELESS ?? "", /legacy-exact-artifact/);
assert.match(publishResult.run ?? "", /REQUEUE_LATEST.*SOURCE_DRIFT_OUTCOME/);
assert.match(publishResult.run ?? "", /LEGACY_TUPLELESS.*SOURCE_DRIFT_OUTCOME/);
assert.doesNotMatch(publishResult.run ?? "", /LIVE_TERMINAL_NOOP/);
assert.match(publishComplete.run ?? "", /internal\/exact-review\/complete/);
assert.ok(publisher.steps.indexOf(publishResult) < publisher.steps.indexOf(publishComplete));
Expand Down Expand Up @@ -1748,8 +1761,8 @@ test("trusted comment router owns command ledger capacity retries", () => {
const eventEnd = sweepWorkflow.indexOf("\n target-fanout:", eventStart);
const eventJob = sweepWorkflow.slice(eventStart, eventEnd);

assert.match(eventJob, /publish-action-events/);
assert.match(eventJob, /publish-action-event-paths/);
assert.doesNotMatch(eventJob, /publish-action-events/);
assert.doesNotMatch(eventJob, /publish-action-event-paths/);
assert.doesNotMatch(eventJob, /count-command-actions/);
assert.doesNotMatch(eventJob, /--wait-for-capacity/);
assert.match(routerWorkflow, /Commit comment router ledger/);
Expand Down Expand Up @@ -2300,7 +2313,7 @@ test("every action-ledger publication authenticates the expected producer job",
/pnpm run --silent publish-action-events -- \\\n(?:\s+.*\\\n)*\s+--expected-producer-job [^\n]+/g,
);
assert.ok(commands);
assert.equal(commands.length, 9);
assert.equal(commands.length, 7);
assert.ok(commands.every((command) => command.includes("--expected-producer-job")));
assert.match(workflow, /--expected-producer-job review/);
assert.match(workflow, /--expected-producer-job apply-proof/);
Expand Down