Skip to content

Freeze workflow contracts and make PR reviews deterministic - #25

Merged
cnjack merged 1 commit into
mainfrom
codex/workflow-platform
Aug 1, 2026
Merged

Freeze workflow contracts and make PR reviews deterministic#25
cnjack merged 1 commit into
mainfrom
codex/workflow-platform

Conversation

@cnjack

@cnjack cnjack commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • freeze a versioned Workflow Contract on every new Run and expose a role-safe Console inspector
  • atomically freeze repository identity with the Plugin dispatch grant and converge source/push/PR/review consumers
  • freeze PR base/head SHAs, upload a deterministic changed-line Review Plan, and require structured anchored review results
  • add GitLab revision enrichment, Ready PR lifecycle semantics, benchmark/PRD/design/POC documents, and end-to-end runner coverage

Closes #24

Verification

  • cd orchestrator && go test ./...
  • cd console && corepack pnpm test (76 files / 606 tests)
  • corepack pnpm typecheck && corepack pnpm build && corepack pnpm lint:tokens
  • runner module tests, shell contract/scrub tests
  • runner/test-fetch-source.sh (credential-free source + exact-SHA Review Plan + structured output)
  • runner/test.sh (containerized no-TTY full loop)
  • Copilot CLI, Claude CLI glm-5.2, and Grok CLI grok-4.5 adversarial audits; final verdict GO

Delivery

This is intentionally a ready-for-review PR, not a draft. Cloud never auto-approves or auto-merges provider PRs.

Summary by CodeRabbit

  • New Features

    • Added immutable workflow contract details to run records, including execution settings, delivery targets, and verification requirements.
    • Added deterministic pull-request review coverage with frozen revisions, indexed files, changed-line metrics, and skipped-file details.
    • Added structured review-plan submission and validation for more reliable review results.
    • Added workflow contract and review coverage prototype views.
  • Bug Fixes

    • Improved pull-request handling by requiring complete revision information and failing safely when it is unavailable.
  • Documentation

    • Added product, API, workflow, review, and runner documentation covering the new capabilities.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds immutable workflow contracts, frozen SCM grants, exact pull-request revisions, canonical review plans, changed-line validation, structured runner review output, and Console inspection of execution and coverage metadata.

Changes

Workflow contract and deterministic review

Layer / File(s) Summary
Contract, review-plan, and persistence foundations
orchestrator/internal/domain/*, orchestrator/internal/store/*, orchestrator/internal/store/migrations/*, CONTEXT.md
Runs now freeze validated workflow contracts, PR revisions, repository grants, and canonical review plans. Storage supports deep copies, strict decoding, round trips, idempotent writes, and conflict detection.
Revision capture and review-plan ingestion
orchestrator/internal/provider/*, orchestrator/internal/scmevent/*, orchestrator/internal/api/*
Provider and webhook flows capture complete base/head SHAs. Review creation requires frozen revisions. The authenticated review-plan endpoint validates revisions, diff limits, plan hashes, and structured review prerequisites.
Frozen SCM dispatch and runtime delivery
orchestrator/internal/reconciler/*, orchestrator/internal/api/source.go, orchestrator/internal/store/plugins.go
Dispatch snapshots retain repository and acting-principal data. Runtime source, push, pull-request, and review operations use the frozen SCM context and contract policies, with legacy fallbacks for older runs.
Runner review bootstrap and result protocol
runner/entrypoint.sh, runner/orchclient/main.go, runner/README.md, runner/test-*.sh, runner/mockllm/main.go
Review runners verify exact commits, compute merge-base diffs, submit plans before execution, and upload structured REVIEW.json output. Runner tests cover plan receipt and generated artifacts.
Run inspector and review coverage presentation
console/src/api/types.ts, console/src/pages/RunDetailPage.tsx, console/src/pages/RunDetailPage.module.css, console/src/i18n/locales/*, design/*
The Console displays workflow contract metadata, revision data, coverage metrics, skipped files, and legacy fallbacks. Design prototypes add contract disclosure and review-tab behavior.
Product contracts and release validation
docs/11-api.md, docs/28-31-*.md, outputs/workflow-platform-audit-2026-08-01.md
API, product, design, benchmark, case-study, and audit documents define the Ship-R1 contract, deterministic review rules, scope boundaries, and verification evidence.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • cnjack/cloud#15: Both changes update Kanban-triggered run creation and contract persistence paths.
  • cnjack/cloud#22: Both changes update reconciler and runner behavior for PR review execution.
  • cnjack/cloud#23: Both changes align runner review prompts and output handling with the strict review contract.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning docs/31 adds a Video Prompt Studio MVP and unrelated workflow scenarios outside issue #24's Ship-R1 scope. Move the Video Prompt Studio and unrelated workflow-case content to a separate PR or link an issue that authorizes this scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The summaries cover most #24 requirements, but they do not confirm inspector exposure of SCM grant, runner digest, and review rules metadata. Add implementation or tests proving these inspector fields are exposed without secrets, and confirm fallback review summaries.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: freezing workflow contracts and making PR reviews deterministic.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/workflow-platform

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cnjack
cnjack merged commit 2054010 into main Aug 1, 2026
2 of 3 checks passed
@cnjack
cnjack deleted the codex/workflow-platform branch August 1, 2026 03:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
orchestrator/internal/api/review.go (1)

69-91: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove now-dead revision defaults from newReviewRun.

newReviewRun sets PRHeadBranch: src.GitBranch, PRBaseBranch: svc.DefaultBranch, and PRHeadSHA: src.CommitSHA (lines 146-148), but handleRequestReview unconditionally overwrites all three (plus PRBaseSHA, which newReviewRun never sets) right after construction, at lines 77-80. The constructor's values are never observable in the current code path.

Keep the source of truth in one place. Drop the dead assignments in newReviewRun and set PRHeadBranch/PRBaseBranch/PRHeadSHA/PRBaseSHA only where they are actually frozen (handleRequestReview). Leaving misleading defaults here creates a real risk: a future maintainer could see the constructor already "setting" these fields and remove the explicit overwrite in handleRequestReview, silently reintroducing unfrozen, potentially stale head/base values instead of the PR's live revisions.

🐛 Proposed fix
 func newReviewRun(src *domain.Run, svc *domain.Service, triggeredBy *string) *domain.Run {
 	return &domain.Run{
 		ID:                domain.NewID(),
 		ProjectID:         src.ProjectID,
 		ServiceID:         src.ServiceID,
 		Prompt:            "AI review of PR " + src.PRURL,
 		Status:            domain.StatusQueued,
 		Kind:              domain.RunKindReview,
 		Phase:             "Queued",
 		TriggeredByUserID: triggeredBy,
-		PRHeadBranch:      src.GitBranch,
-		PRBaseBranch:      svc.DefaultBranch,
-		PRHeadSHA:         src.CommitSHA,
 		PRURL:             src.PRURL,
 		PRNumber:          src.PRNumber,
 		Attempt:           1,
 		CreatedAt:         time.Now().UTC(),
 	}
 }

Also applies to: 136-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/review.go` around lines 69 - 91, Remove the PR
revision field assignments from newReviewRun, including PRHeadBranch,
PRBaseBranch, and PRHeadSHA; do not add PRBaseSHA there. Keep all four revision
fields assigned only in handleRequestReview after reviewTargetAtCreation
succeeds, preserving the frozen live PR revisions.
🟡 Minor comments (13)
orchestrator/internal/store/plugins_test.go-662-665 (1)

662-665: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fail when fixture setup fails.

The test ignores errors from project, service, provider-config, and provider-config lookup setup. A setup failure can make ClaimRunDispatch return ErrDispatchClaimUnavailable for an unrelated reason. Check each error with t.Fatal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/plugins_test.go` around lines 662 - 665, Update
the test fixture setup around CreateProject, CreateService,
UpsertProviderConfig, and GetProviderConfig to capture each returned error and
call t.Fatal when any operation fails. Ensure ClaimRunDispatch runs only after
all project, service, provider configuration, and lookup setup succeeds.
orchestrator/internal/reconciler/pr_test.go-130-131 (1)

130-131: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the discarded errors in the test setup.

Lines 130-131 discard the errors from cipher.EncryptString. Line 166 discards the error from st.GetPluginInstallation. If the lookup on Line 166 fails, live is nil and Line 167 panics with a nil dereference instead of reporting the real setup failure. Assert each error with t.Fatal so a setup break names its own cause.

💚 Proposed fix
-	oldToken, _ := cipher.EncryptString("old-bot-token")
-	newToken, _ := cipher.EncryptString("new-bot-token")
+	oldToken, err := cipher.EncryptString("old-bot-token")
+	if err != nil {
+		t.Fatal(err)
+	}
+	newToken, err := cipher.EncryptString("new-bot-token")
+	if err != nil {
+		t.Fatal(err)
+	}
-	live, _ := st.GetPluginInstallation(ctx, installation.ID)
+	live, err := st.GetPluginInstallation(ctx, installation.ID)
+	if err != nil {
+		t.Fatal(err)
+	}

Rename the later candidates, err := binding accordingly so the shadowing stays valid.

Also applies to: 166-166

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/pr_test.go` around lines 130 - 131, In the
test setup around the EncryptString calls and GetPluginInstallation lookup, stop
discarding returned errors and fail immediately with t.Fatal, including each
underlying error. Preserve the existing live lookup and candidate logic,
renaming the later candidates, err binding if needed to avoid shadowing after
introducing the checked error variable.
runner/entrypoint.sh-547-554 (1)

547-554: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Report oversized review diffs with review_input_too_large.

post-review-plan uploads the entire diff, and the orchestrator rejects any diff larger than MaxReviewDiffBytes (2 << 20) with review_input_too_large. The current die message cannot distinguish that from upload failures. Check REVIEW_DIFF_BYTES locally or include the server error reason when the upload fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runner/entrypoint.sh` around lines 547 - 554, The review-plan upload path
should distinguish oversized diffs from generic upload failures. Use the
existing REVIEW_DIFF_BYTES value after computing the diff and, when it exceeds
MaxReviewDiffBytes (2 << 20), fail with setup_failed and the
review_input_too_large reason; otherwise preserve the existing post-review-plan
upload handling and include any server-provided failure reason when available.
docs/29-workflow-contract-and-review-design.md-75-79 (1)

75-79: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the migration filename.

This design names 0064_workflow_contract.sql, but the reviewed migration is orchestrator/internal/store/migrations/0064_workflow_contract_review_plan.sql. Update the reference so operators and reviewers can locate the migration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/29-workflow-contract-and-review-design.md` around lines 75 - 79, Update
the migration reference in the Data design section to use
0064_workflow_contract_review_plan.sql, matching the reviewed migration filename
so operators and reviewers can locate it.
docs/30-open-source-agent-workflow-benchmark.md-60-61 (1)

60-61: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the timeout conclusion with the POC budgets.

The sentence says a 30-minute maximum covers the first POC scenarios. docs/31-workflow-case-conversations-and-video-agent-poc.md lists 45–180 minute budgets. A 30-minute limit cannot cover those cases. State the required maximum separately from the benchmark observation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/30-open-source-agent-workflow-benchmark.md` around lines 60 - 61, Update
the timeout conclusion in the benchmark document to distinguish the observed
30-minute server maximum from the POC requirement. State that the listed POC
scenarios require a higher maximum based on their 45–180 minute budgets, rather
than claiming 30 minutes covers them.
docs/31-workflow-case-conversations-and-video-agent-poc.md-61-66 (1)

61-66: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the unsupported deprecation assertion.

The template flow is described as the current /v1/video_template_generation create/query flow; only add a provider compatibility seam if the decision is product-risk, not because that endpoint is officially deprecated. If you keep the seam, add the official deprecation source and cutoff to avoid documenting false API lifecycle status.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/31-workflow-case-conversations-and-video-agent-poc.md` around lines 61 -
66, Update the “API 名称校正” section to remove the unsupported claim that
/v1/video_template_generation is officially deprecated. Describe the template
create/query flow as current, and retain the provider compatibility seam only if
justified as a product-risk decision; if retained, cite the official deprecation
source and cutoff.
design/README.md-102-105 (1)

102-105: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move these entries into the Page map table.

Line 102 adds a literal @@ marker. Lines 103-105 are outside the table that ends earlier in the document. Remove the marker and place both rows with the other Page map entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/README.md` around lines 102 - 105, Remove the literal @@ marker and
move the workflow-contract.html and review-coverage.html rows into the existing
Page map table, preserving their descriptions and table formatting.
design/review-coverage.html-3-3 (1)

3-3: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set the document language to match the fixture content.

Line 3 declares Simplified Chinese, but the main headings and descriptions are English. Screen readers can select the wrong pronunciation rules. Set lang="en" or translate the document content consistently.

Proposed fix
-<html lang="zh-CN" data-theme="light">
+<html lang="en" data-theme="light">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/review-coverage.html` at line 3, Update the document language
declaration on the root html element to lang="en" so it matches the existing
English headings and descriptions, leaving the fixture content unchanged.
orchestrator/internal/store/memory.go-632-634 (1)

632-634: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Both stores validate a trimmed revision pair but persist the raw value. The shared root cause is that strings.TrimSpace is applied only inside the ValidCommitSHA argument, so a padded PRHeadSHA or PRBaseSHA passes creation and is stored with whitespace. SetReviewPlan then compares the plan revisions against the stored values and returns ErrConflict, so the review Run can never accept a plan.

  • orchestrator/internal/store/memory.go#L632-L634: assign the trimmed values to r.PRHeadSHA and r.PRBaseSHA before validating them.
  • orchestrator/internal/store/pg.go#L585-L587: apply the same normalization in createRunTx before the validation and the INSERT.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/memory.go` around lines 632 - 634, Normalize and
persist trimmed review revision values before validation: in
orchestrator/internal/store/memory.go lines 632-634, update the review Run
fields r.PRHeadSHA and r.PRBaseSHA before calling domain.ValidCommitSHA; apply
the same change in orchestrator/internal/store/pg.go lines 585-587 within
createRunTx so validation and the INSERT use the normalized values.
orchestrator/internal/store/pg.go-703-720 (1)

703-720: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Return the serialization error instead of writing SQL NULL.

Both helpers map a marshal failure to nil, which persists NULL. The Run then looks like a legacy Run with no contract and no plan, so handleIngestReview stops requiring structured output and an accepted plan (orchestrator/internal/api/source.go Lines 368-387). A serialization failure would therefore downgrade the determinism guarantee without any error. Propagate the error so createRunTx aborts.

🔧 Proposed error propagation
-func workflowContractJSON(contract *domain.WorkflowContract) any {
+func workflowContractJSON(contract *domain.WorkflowContract) (any, error) {
 	if contract == nil {
-		return nil
+		return nil, nil
 	}
 	encoded, err := json.Marshal(contract)
 	if err != nil {
-		return nil
+		return nil, fmt.Errorf("encode execution contract: %w", err)
 	}
-	return encoded
+	return encoded, nil
 }
 
-func reviewPlanJSON(plan *domain.ReviewPlan) any {
+func reviewPlanJSON(plan *domain.ReviewPlan) (any, error) {
 	encoded, err := domain.MarshalStoredReviewPlan(plan)
-	if err != nil || len(encoded) == 0 {
-		return nil
+	if err != nil {
+		return nil, fmt.Errorf("encode review plan: %w", err)
 	}
-	return encoded
+	if len(encoded) == 0 {
+		return nil, nil
+	}
+	return encoded, nil
 }

Resolve both values before the INSERT and return early on error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/pg.go` around lines 703 - 720, Update
workflowContractJSON and reviewPlanJSON to return serialization errors instead
of converting failures to nil/SQL NULL, and adjust createRunTx to resolve both
values before the INSERT, returning immediately if either serialization fails so
the transaction aborts. Preserve nil handling for genuinely absent optional
values.
orchestrator/internal/api/source.go-312-320 (1)

312-320: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Classify the oversize case with a sentinel error, not a substring match.

strings.Contains(err.Error(), "review_input_too_large") ties the HTTP status to the exact wording of a domain error message. Any rewording of that message silently changes the response from 413 to 400, and no test in this package would catch it. Export a sentinel from domain and use errors.Is.

🔧 Proposed sentinel check
 	plan, err := domain.BuildReviewPlan(input)
 	if err != nil {
-		if strings.Contains(err.Error(), "review_input_too_large") {
+		if errors.Is(err, domain.ErrReviewInputTooLarge) {
 			writeError(w, http.StatusRequestEntityTooLarge, "review_input_too_large", err.Error())
 		} else {
 			writeError(w, http.StatusBadRequest, "invalid_review_plan", err.Error())
 		}
 		return
 	}

Define ErrReviewInputTooLarge in orchestrator/internal/domain/review_plan.go and wrap it in every cap violation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/source.go` around lines 312 - 320, Replace the
string-based check in the review-plan error handling with errors.Is against a
domain.ErrReviewInputTooLarge sentinel. Define and export that sentinel in
domain/review_plan.go, wrap it for every review-input cap violation while
preserving the original error context, and keep the 413 response for matching
errors and 400 for other failures.
orchestrator/internal/domain/workflow_contract.go-312-329 (1)

312-329: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Move run-identity fields out of the execution-contract hash input.

ResolveWorkflowContract hashes WorkflowContract, which includes Trigger. Trigger always has RequestID = run.ID, and IdempotencyKey falls back to run.ID when the run-specific keys are empty. Keep run identity in audit fields and hash only the shared frozen configuration if this Hash is meant to identify that configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/domain/workflow_contract.go` around lines 312 - 329,
Update ResolveWorkflowContract and the WorkflowContract hash input so
run-specific Trigger fields such as RequestID and the run.ID fallback for
IdempotencyKey are excluded from the hashed execution contract. Preserve these
values in the returned trigger and audit data, while hashing only the shared
frozen configuration used to identify the contract.
orchestrator/internal/api/plugin_webhook_test.go-480-490 (1)

480-490: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make the lookup counter race-free.

lookupCalls++ runs on the httptest server goroutine. Line 620 reads it on the test goroutine. Use atomic.Int64 so go test -race cannot report a data race on this counter.

🔒 Proposed atomic counter
-	lookupCalls := 0
+	var lookupCalls atomic.Int64
 	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/merge_requests/7") {
-			lookupCalls++
+			lookupCalls.Add(1)

Then compare with lookupCalls.Load() != 1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/plugin_webhook_test.go` around lines 480 - 490,
Replace the plain lookupCalls integer in the httptest server handler with an
atomic.Int64, increment it atomically inside the request handler, and use
lookupCalls.Load() when asserting the expected count on the test goroutine.
🧹 Nitpick comments (14)
orchestrator/internal/store/plugins_test.go (1)

684-687: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert that the rejected claim created no snapshots.

The test checks only the Run fields. Also require ListRunPluginSnapshots(ctx, "r") to return an empty result. This protects the atomic dispatch contract against a partial snapshot write.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/plugins_test.go` around lines 684 - 687, Extend
the rejected-claim assertions in the test around GetRun to call
ListRunPluginSnapshots(ctx, "r") and verify it returns no snapshots, while
preserving the existing Run status and TokenHash checks. Fail the test if either
the Run mutation or any partial snapshot creation occurs.
runner/test-integration.sh (1)

229-229: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Match JCODE_TASK_ in the Git diff file header.

Both tests can pass when the prefix occurs only in changed content.

  • runner/test-integration.sh#L229-L229: match ^+++ b/JCODE_TASK_ in ART_CONTENT.
  • runner/test.sh#L121-L124: match ^+++ b/JCODE_TASK_ in $OUT/diff.patch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runner/test-integration.sh` at line 229, Update the artifact diff assertion
in runner/test-integration.sh at lines 229-229 to match the Git diff file header
pattern ^+++ b/JCODE_TASK_ rather than any occurrence in changed content; apply
the same header-anchored match to the diff assertion in runner/test.sh at lines
121-124 against $OUT/diff.patch.
runner/test-fetch-source.sh (1)

92-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate the captured review-plan payload.

The mock accepts any non-empty request body. Parse received.review-plan and require the expected base_sha, head_sha, merge_base_sha, and a non-empty diff. This test must reject a plan with incorrect frozen revisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@runner/test-fetch-source.sh` around lines 92 - 95, Update the mock handling
for /review-plan near the received.review-plan write to parse the captured JSON
payload and validate base_sha, head_sha, and merge_base_sha against the expected
frozen revisions, while requiring diff to be non-empty. Make the test fail when
any revision is incorrect or the diff is missing, rather than accepting any
non-empty body.
orchestrator/internal/reconciler/session.go (1)

59-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider checking the delivery target here too.

shouldOpenPR in decision.go requires both Delivers(domain.OutputCreatePullRequest) and DeliveryTarget(...) == "service_repository". sessionPushEligible checks only the output type. A contract that targets trigger_pr therefore passes this gate, and pushSessionRun would open a new PR on the frozen repository for it. Session runs do not resolve trigger_pr today, so this is not currently reachable. Add the target check so the three delivery gates stay identical.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/session.go` around lines 59 - 63, Update
sessionPushEligible to require both Delivers(domain.OutputCreatePullRequest) and
DeliveryTarget(...) == "service_repository", matching shouldOpenPR in
decision.go; preserve the existing historical-run fallback when
ExecutionContract is nil.
orchestrator/internal/reconciler/pr_test.go (1)

844-866: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the target-mismatch cases.

The test covers the output-type branch of each gate. It does not cover the target branch. Add one case where a create_pull_request contract targets trigger_pr and shouldOpenPR must return false, and one where it targets service_repository and shouldUpdatePush must return false. Those two assertions are what pin the target checks added in decision.go at Lines 179 and 219.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/pr_test.go` around lines 844 - 866, Extend
TestDeliveryGatesUseFrozenContractAfterServiceModeChanges with target-mismatch
assertions: verify shouldOpenPR returns false for a create_pull_request output
targeting trigger_pr, and verify shouldUpdatePush returns false for a
create_pull_request output targeting service_repository. Keep the existing
output-type and frozen-contract coverage unchanged.
orchestrator/internal/reconciler/reconciler.go (2)

1077-1097: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the frozen-grant selection into one helper.

scmContextForRun selects the first snapshot with HasFrozenRepositoryGrant(). addGitEnv at Lines 2489-2496 repeats the same selection. If the selection rule changes (for example, preferring the snapshot bound to the Service), the two sites can diverge and send the pod to a different repository than the delivery path uses. Extract a single helper such as frozenRepositoryGrant(ctx, runID) (*domain.RunPluginSnapshot, error) and call it from both.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/reconciler.go` around lines 1077 - 1097,
Extract the frozen-grant snapshot selection currently in scmContextForRun into a
shared frozenRepositoryGrant helper, accepting ctx and runID and returning the
selected *domain.RunPluginSnapshot or an error. Update both scmContextForRun and
addGitEnv to use this helper, preserving the existing
first-HasFrozenRepositoryGrant selection behavior while ensuring both paths use
the same snapshot.

717-732: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consolidate the repeated "resolve frozen SCM context, park on credential error, split repository path" block. Five delivery passes now open with the same three steps: call scmContextForRun, classify credentials.ErrIntegrationCredential and credentials.ErrPluginCredentialUnavailable through noteIntegrationCredentialFailure, then call provider.SplitRepo. The shared root cause is a missing helper, so each new consumer must re-implement the credential classification correctly. A pass that forgets one sentinel would retry a permanent configuration failure on every tick instead of parking it. Add one helper that returns the context plus resolved owner/repo and a "handled" flag, then call it from each site.

  • orchestrator/internal/reconciler/reconciler.go#L717-L732: replace the resolve, park, and SplitRepo block in openPullRequest with the shared helper, passing the pass label "pull request delivery".
  • orchestrator/internal/reconciler/reconciler.go#L913-L922: replace the resolve and park block in updatePushRun with the helper, passing "update push".
  • orchestrator/internal/reconciler/reconciler.go#L1015-L1029: replace the resolve, park, and SplitRepo block in postReview with the helper, passing "review post".
  • orchestrator/internal/reconciler/session.go#L79-L94: replace the resolve, park, and SplitRepo block in pushSessionRun with the helper, passing "session push".
  • orchestrator/internal/reconciler/session.go#L258-L271: replace the resolve, park, and SplitRepo block in reconcileSessionPRReady with the helper, passing "mark PR ready", and keep continue as the loop-local control flow.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/reconciler.go` around lines 717 - 732, The
repeated SCM resolution, credential-error handling, and repository splitting
should be centralized in one helper returning the SCM context, owner/repo, and a
handled flag. Replace the duplicated blocks in
orchestrator/internal/reconciler/reconciler.go lines 717-732, 913-922, and
1015-1029 with calls using labels “pull request delivery”, “update push”, and
“review post”; replace the corresponding blocks in
orchestrator/internal/reconciler/session.go lines 79-94 and 258-271 with calls
using “session push” and “mark PR ready”, preserving continue for the loop-local
control flow at the latter site.
orchestrator/internal/reconciler/decision.go (1)

177-187: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use named constants for PR delivery targets.

DeliveryTarget returns hard-coded strings, and shouldOpenPR/shouldUpdatePush compare the same strings again. A typo in either place can silently disable contract-backed PR delivery. Export target constants from domain and use them in the contract resolver and reconciler checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/decision.go` around lines 177 - 187, Define
exported constants in domain for the supported PR delivery targets, then update
the contract resolver and the shouldOpenPR/shouldUpdatePush checks to compare
against those constants instead of hard-coded strings such as
"service_repository". Preserve the existing target values and behavior while
ensuring all contract-backed PR delivery paths reuse the named symbols.
docs/31-workflow-case-conversations-and-video-agent-poc.md (1)

77-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Encode the exact Hailuo-2.3 parameter matrix in tests.

The MVP requires validation but does not state the matrix. Current official documentation lists 6 seconds with 768P or 1080P, and 10 seconds with 768P only for MiniMax-Hailuo-2.3. Put this matrix in provider contract tests and reject all other combinations. (platform.minimaxi.com)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/31-workflow-case-conversations-and-video-agent-poc.md` around lines 77 -
83, Update the provider contract tests for MiniMax-Hailuo-2.3 to explicitly
allow only 6 seconds with 768P or 1080P, and 10 seconds with 768P. Add rejection
cases for every other duration and resolution combination, ensuring the
validation matrix is enforced for both supported generation modes.
orchestrator/internal/api/api_test.go (1)

339-374: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that retry preserves PRHeadSHA/PRBaseSHA.

The fixture at Line 354 now sets PRHeadSHA and PRBaseSHA, but the retry assertions at Line 371-373 check only PRHeadBranch/PRBaseBranch. Add assertions for the SHA fields so a regression that drops or resets the frozen revision pair on retry is caught here.

♻️ Proposed fix
 	if retry.PRHeadBranch != "jcode/run-abc12345" || retry.PRBaseBranch != "main" {
 		t.Fatalf("retry PR assoc = %q..%q, want preserved", retry.PRBaseBranch, retry.PRHeadBranch)
 	}
+	if retry.PRHeadSHA != rev.PRHeadSHA || retry.PRBaseSHA != rev.PRBaseSHA {
+		t.Fatalf("retry revision pair = %q..%q, want preserved", retry.PRBaseSHA, retry.PRHeadSHA)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/api_test.go` around lines 339 - 374, Add assertions
to TestRetryPreservesReviewIdentity verifying retry.PRHeadSHA and
retry.PRBaseSHA equal the seeded 40-character SHA values. Keep the existing
branch identity assertions and ensure both frozen revision fields are checked
for preservation.
orchestrator/internal/domain/review_plan.go (1)

262-321: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use named fields for storedReviewPlan conversions.

stored() (Line 282) and UnmarshalStoredReviewPlan (Line 312) build 16-field structs with positional literals. Several fields share the same type: BaseSHA, HeadSHA, and MergeBaseSHA are all string; ChangedFiles, EligibleFiles, IndexedFiles, ChangedHunks, IndexedHunks, and ChangedLines are all int. If a future change reorders fields in either ReviewPlan or storedReviewPlan, the compiler accepts the mismatch silently. The result is a corrupted plan_hash or a swapped counter, without a build error.

Use named-field struct literals in both conversions to make the mapping compiler-checked.

♻️ Proposed fix
 func (p ReviewPlan) stored() storedReviewPlan {
-	return storedReviewPlan{p.SchemaVersion, p.PlanHash, p.BaseSHA, p.HeadSHA, p.MergeBaseSHA, p.RulesRevision, p.Coverage, p.ChangedFiles, p.EligibleFiles, p.IndexedFiles, p.ChangedHunks, p.IndexedHunks, p.ChangedLines, p.Files, p.Anchors, p.CreatedAt}
+	return storedReviewPlan{
+		SchemaVersion: p.SchemaVersion, PlanHash: p.PlanHash, BaseSHA: p.BaseSHA, HeadSHA: p.HeadSHA,
+		MergeBaseSHA: p.MergeBaseSHA, RulesRevision: p.RulesRevision, Coverage: p.Coverage,
+		ChangedFiles: p.ChangedFiles, EligibleFiles: p.EligibleFiles, IndexedFiles: p.IndexedFiles,
+		ChangedHunks: p.ChangedHunks, IndexedHunks: p.IndexedHunks, ChangedLines: p.ChangedLines,
+		Files: p.Files, Anchors: p.Anchors, CreatedAt: p.CreatedAt,
+	}
 }
@@
-	p := &ReviewPlan{value.SchemaVersion, value.PlanHash, value.BaseSHA, value.HeadSHA, value.MergeBaseSHA, value.RulesRevision, value.Coverage, value.ChangedFiles, value.EligibleFiles, value.IndexedFiles, value.ChangedHunks, value.IndexedHunks, value.ChangedLines, value.Files, value.Anchors, value.CreatedAt}
+	p := &ReviewPlan{
+		SchemaVersion: value.SchemaVersion, PlanHash: value.PlanHash, BaseSHA: value.BaseSHA, HeadSHA: value.HeadSHA,
+		MergeBaseSHA: value.MergeBaseSHA, RulesRevision: value.RulesRevision, Coverage: value.Coverage,
+		ChangedFiles: value.ChangedFiles, EligibleFiles: value.EligibleFiles, IndexedFiles: value.IndexedFiles,
+		ChangedHunks: value.ChangedHunks, IndexedHunks: value.IndexedHunks, ChangedLines: value.ChangedLines,
+		Files: value.Files, Anchors: value.Anchors, CreatedAt: value.CreatedAt,
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/domain/review_plan.go` around lines 262 - 321, Replace
the positional struct literals in ReviewPlan.stored and
UnmarshalStoredReviewPlan with named-field literals. Explicitly map every
storedReviewPlan and ReviewPlan field, including the SHA values, counters,
Files, Anchors, and CreatedAt, while preserving the existing values and hash
validation behavior.
orchestrator/internal/store/workflow_contract_test.go (1)

67-92: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the review-plan status gate.

The test covers first write, identical retry, and hash conflict. It does not cover the ErrInvalidTransition gate for a Run that is queued, terminal, or of kind agent (orchestrator/internal/store/memory.go Lines 1894-1896). That gate is the only guard that stops a plan write outside the scheduling/running window. Add one sub-case that asserts ErrInvalidTransition for a queued review Run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/workflow_contract_test.go` around lines 67 - 92,
The TestMemStoreSetReviewPlanFirstWriteIdempotency test should also cover the
review-plan status gate. Add a sub-case creating a queued review Run, attempt
SetReviewPlan for it, and assert the returned error is ErrInvalidTransition
while preserving the existing first-write, retry, and conflict assertions.
orchestrator/internal/api/plugin_webhook_test.go (1)

350-376: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the frozen Gitea revision pair.

The payload now supplies head.sha and base.sha, but the assertion checks only branches and PR number. The GitLab test asserts PRHeadSHA and PRBaseSHA (Lines 617-619). Add the same assertion here so the Gitea path keeps proving that the exact revisions are frozen before the Run is queued.

💚 Proposed assertion
 	if runs[0].Kind != domain.RunKindReview || runs[0].PRHeadBranch != "feature" ||
 		runs[0].PRBaseBranch != "main" || runs[0].PRNumber != 7 {
 		t.Fatalf("review run=%+v", runs[0])
 	}
+	if runs[0].PRHeadSHA != strings.Repeat("a", 40) || runs[0].PRBaseSHA != strings.Repeat("b", 40) {
+		t.Fatalf("review revisions were not frozen: %+v", runs[0])
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/plugin_webhook_test.go` around lines 350 - 376,
Extend the final validation in the Gitea webhook test around the existing run
metadata assertion to also compare PRHeadSHA with the supplied 40-character “a”
revision and PRBaseSHA with the supplied 40-character “b” revision. Keep the
existing branch and PR number checks unchanged so the test verifies the complete
frozen revision pair before queueing.
orchestrator/internal/api/plugin_webhook.go (1)

307-363: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated PR-revision-fetch-and-validate logic.

The manual-review block (lines 307-363) and the automatic-enrichment block (364-393) both: resolve a provider client, split or take an owner/repo pair, call PRByNumber, check that HeadRef/BaseRef/HeadSHA/BaseSHA are all non-empty, and assign the same four event fields on success. Extract this into one helper function that takes a client, owner, repo, and PR number, and returns the validated *provider.PR or an error. Call it from both sites.

This reduces the risk that a future change to the revision contract (for example, adding a required field) gets applied to only one of the two call sites.

♻️ Proposed extraction
+func fetchCompletePR(ctx context.Context, client gitprovider.Provider, owner, repo string, number int) (*provider.PR, error) {
+	pr, err := client.PRByNumber(ctx, owner, repo, number)
+	if err != nil || pr == nil || pr.HeadRef == "" || pr.BaseRef == "" || pr.HeadSHA == "" || pr.BaseSHA == "" {
+		if err == nil {
+			err = errors.New("pull request revision pair is incomplete")
+		}
+		return nil, err
+	}
+	return pr, nil
+}

Also applies to: 364-393

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/api/plugin_webhook.go` around lines 307 - 363, Extract
the shared pull-request revision lookup and validation from the manual-review
flow and the automatic-enrichment flow into one helper that accepts a
gitprovider.Provider, owner, repository, and PR number, then returns a validated
*provider.PR or an error. Use the helper at both call sites, preserving the
required non-empty HeadRef, BaseRef, HeadSHA, and BaseSHA checks and the
existing event-field assignments and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/11-api.md`:
- Around line 1292-1310: Normalize the malformed review-plan error contract: in
docs/11-api.md lines 1292-1310, document the endpoint’s 400 malformed-input
response with the stable error code; in
docs/29-workflow-contract-and-review-design.md lines 434-448, replace
invalid_review_plan with the failure-table identifier and update the related
tests to assert that code consistently.

In `@docs/28-workflow-contract-and-deterministic-review-prd.md`:
- Around line 66-70: Clarify the timeout contract in the PRD by explicitly
defining whether execution.timeout_seconds represents the per-run RUN_TIMEOUT
budget or the Kubernetes activeDeadlineSeconds deadline. Document timeoutGrace
separately, specify the conversion between the two values, and require the
inspector, runner, scheduler, and JobSpec.TimeoutSeconds handling to use the
same effective deadline.

In `@docs/29-workflow-contract-and-review-design.md`:
- Around line 276-285: The Review Plan contract must require canonical full
40-character hexadecimal Git object IDs rather than abbreviated SHAs. Update the
schema example and surrounding documentation in
docs/29-workflow-contract-and-review-design.md (lines 276-285), and revise the
audit hardening wording in outputs/workflow-platform-audit-2026-08-01.md (lines
62-64) to match; ensure queue creation either canonicalizes IDs before
persistence or rejects values shorter than 40 hex characters.

In `@docs/30-open-source-agent-workflow-benchmark.md`:
- Around line 170-177: Update the PR Review row in the “对四个目标场景的落地” table to
include ripgrep alongside the existing required capabilities, keeping the row’s
other fields unchanged.

In `@orchestrator/internal/domain/workflow_contract.go`:
- Around line 375-411: Update Validate after resolving the built-in definition
in the switch to require c.Requirements to exactly match def.Requirements,
rejecting both missing and extra requirements; retain the existing whitelist
validation only as applicable, and perform this comparison before validating the
definition hash.

In `@orchestrator/internal/reconciler/reconciler.go`:
- Around line 2489-2496: Update addGitEnv and jobEnv to propagate
ListRunPluginSnapshots errors instead of silently retaining mutable Service
values; ensure createJob handles the propagated error by leaving the run queued
for retry, or reuse the snapshots already loaded for ClaimRunDispatch and pass
the resolved grant into addGitEnv.

In `@orchestrator/internal/store/pg.go`:
- Around line 21-23: Change PGStore’s workflowRunTimeoutSecs and
workflowSessionTTLSecs fields to atomic.Int64, then update
ConfigureWorkflowTimeoutDefaults to store values atomically and createRunTx to
load them atomically, following the existing MemStore pattern.
- Around line 505-523: Update scanRun’s execution-contract handling to decode
without DisallowUnknownFields and treat validation failures as a skipped
ExecutionContract projection rather than returning a row-scan error. Preserve
errors for malformed contract data, and report skipped validation failures
through the existing logging or counter path with the Run id so ListRuns and
ListRunsByService continue loading other rows.

---

Outside diff comments:
In `@orchestrator/internal/api/review.go`:
- Around line 69-91: Remove the PR revision field assignments from newReviewRun,
including PRHeadBranch, PRBaseBranch, and PRHeadSHA; do not add PRBaseSHA there.
Keep all four revision fields assigned only in handleRequestReview after
reviewTargetAtCreation succeeds, preserving the frozen live PR revisions.

---

Minor comments:
In `@design/README.md`:
- Around line 102-105: Remove the literal @@ marker and move the
workflow-contract.html and review-coverage.html rows into the existing Page map
table, preserving their descriptions and table formatting.

In `@design/review-coverage.html`:
- Line 3: Update the document language declaration on the root html element to
lang="en" so it matches the existing English headings and descriptions, leaving
the fixture content unchanged.

In `@docs/29-workflow-contract-and-review-design.md`:
- Around line 75-79: Update the migration reference in the Data design section
to use 0064_workflow_contract_review_plan.sql, matching the reviewed migration
filename so operators and reviewers can locate it.

In `@docs/30-open-source-agent-workflow-benchmark.md`:
- Around line 60-61: Update the timeout conclusion in the benchmark document to
distinguish the observed 30-minute server maximum from the POC requirement.
State that the listed POC scenarios require a higher maximum based on their
45–180 minute budgets, rather than claiming 30 minutes covers them.

In `@docs/31-workflow-case-conversations-and-video-agent-poc.md`:
- Around line 61-66: Update the “API 名称校正” section to remove the unsupported
claim that /v1/video_template_generation is officially deprecated. Describe the
template create/query flow as current, and retain the provider compatibility
seam only if justified as a product-risk decision; if retained, cite the
official deprecation source and cutoff.

In `@orchestrator/internal/api/plugin_webhook_test.go`:
- Around line 480-490: Replace the plain lookupCalls integer in the httptest
server handler with an atomic.Int64, increment it atomically inside the request
handler, and use lookupCalls.Load() when asserting the expected count on the
test goroutine.

In `@orchestrator/internal/api/source.go`:
- Around line 312-320: Replace the string-based check in the review-plan error
handling with errors.Is against a domain.ErrReviewInputTooLarge sentinel. Define
and export that sentinel in domain/review_plan.go, wrap it for every
review-input cap violation while preserving the original error context, and keep
the 413 response for matching errors and 400 for other failures.

In `@orchestrator/internal/domain/workflow_contract.go`:
- Around line 312-329: Update ResolveWorkflowContract and the WorkflowContract
hash input so run-specific Trigger fields such as RequestID and the run.ID
fallback for IdempotencyKey are excluded from the hashed execution contract.
Preserve these values in the returned trigger and audit data, while hashing only
the shared frozen configuration used to identify the contract.

In `@orchestrator/internal/reconciler/pr_test.go`:
- Around line 130-131: In the test setup around the EncryptString calls and
GetPluginInstallation lookup, stop discarding returned errors and fail
immediately with t.Fatal, including each underlying error. Preserve the existing
live lookup and candidate logic, renaming the later candidates, err binding if
needed to avoid shadowing after introducing the checked error variable.

In `@orchestrator/internal/store/memory.go`:
- Around line 632-634: Normalize and persist trimmed review revision values
before validation: in orchestrator/internal/store/memory.go lines 632-634,
update the review Run fields r.PRHeadSHA and r.PRBaseSHA before calling
domain.ValidCommitSHA; apply the same change in
orchestrator/internal/store/pg.go lines 585-587 within createRunTx so validation
and the INSERT use the normalized values.

In `@orchestrator/internal/store/pg.go`:
- Around line 703-720: Update workflowContractJSON and reviewPlanJSON to return
serialization errors instead of converting failures to nil/SQL NULL, and adjust
createRunTx to resolve both values before the INSERT, returning immediately if
either serialization fails so the transaction aborts. Preserve nil handling for
genuinely absent optional values.

In `@orchestrator/internal/store/plugins_test.go`:
- Around line 662-665: Update the test fixture setup around CreateProject,
CreateService, UpsertProviderConfig, and GetProviderConfig to capture each
returned error and call t.Fatal when any operation fails. Ensure
ClaimRunDispatch runs only after all project, service, provider configuration,
and lookup setup succeeds.

In `@runner/entrypoint.sh`:
- Around line 547-554: The review-plan upload path should distinguish oversized
diffs from generic upload failures. Use the existing REVIEW_DIFF_BYTES value
after computing the diff and, when it exceeds MaxReviewDiffBytes (2 << 20), fail
with setup_failed and the review_input_too_large reason; otherwise preserve the
existing post-review-plan upload handling and include any server-provided
failure reason when available.

---

Nitpick comments:
In `@docs/31-workflow-case-conversations-and-video-agent-poc.md`:
- Around line 77-83: Update the provider contract tests for MiniMax-Hailuo-2.3
to explicitly allow only 6 seconds with 768P or 1080P, and 10 seconds with 768P.
Add rejection cases for every other duration and resolution combination,
ensuring the validation matrix is enforced for both supported generation modes.

In `@orchestrator/internal/api/api_test.go`:
- Around line 339-374: Add assertions to TestRetryPreservesReviewIdentity
verifying retry.PRHeadSHA and retry.PRBaseSHA equal the seeded 40-character SHA
values. Keep the existing branch identity assertions and ensure both frozen
revision fields are checked for preservation.

In `@orchestrator/internal/api/plugin_webhook_test.go`:
- Around line 350-376: Extend the final validation in the Gitea webhook test
around the existing run metadata assertion to also compare PRHeadSHA with the
supplied 40-character “a” revision and PRBaseSHA with the supplied 40-character
“b” revision. Keep the existing branch and PR number checks unchanged so the
test verifies the complete frozen revision pair before queueing.

In `@orchestrator/internal/api/plugin_webhook.go`:
- Around line 307-363: Extract the shared pull-request revision lookup and
validation from the manual-review flow and the automatic-enrichment flow into
one helper that accepts a gitprovider.Provider, owner, repository, and PR
number, then returns a validated *provider.PR or an error. Use the helper at
both call sites, preserving the required non-empty HeadRef, BaseRef, HeadSHA,
and BaseSHA checks and the existing event-field assignments and error handling.

In `@orchestrator/internal/domain/review_plan.go`:
- Around line 262-321: Replace the positional struct literals in
ReviewPlan.stored and UnmarshalStoredReviewPlan with named-field literals.
Explicitly map every storedReviewPlan and ReviewPlan field, including the SHA
values, counters, Files, Anchors, and CreatedAt, while preserving the existing
values and hash validation behavior.

In `@orchestrator/internal/reconciler/decision.go`:
- Around line 177-187: Define exported constants in domain for the supported PR
delivery targets, then update the contract resolver and the
shouldOpenPR/shouldUpdatePush checks to compare against those constants instead
of hard-coded strings such as "service_repository". Preserve the existing target
values and behavior while ensuring all contract-backed PR delivery paths reuse
the named symbols.

In `@orchestrator/internal/reconciler/pr_test.go`:
- Around line 844-866: Extend
TestDeliveryGatesUseFrozenContractAfterServiceModeChanges with target-mismatch
assertions: verify shouldOpenPR returns false for a create_pull_request output
targeting trigger_pr, and verify shouldUpdatePush returns false for a
create_pull_request output targeting service_repository. Keep the existing
output-type and frozen-contract coverage unchanged.

In `@orchestrator/internal/reconciler/reconciler.go`:
- Around line 1077-1097: Extract the frozen-grant snapshot selection currently
in scmContextForRun into a shared frozenRepositoryGrant helper, accepting ctx
and runID and returning the selected *domain.RunPluginSnapshot or an error.
Update both scmContextForRun and addGitEnv to use this helper, preserving the
existing first-HasFrozenRepositoryGrant selection behavior while ensuring both
paths use the same snapshot.
- Around line 717-732: The repeated SCM resolution, credential-error handling,
and repository splitting should be centralized in one helper returning the SCM
context, owner/repo, and a handled flag. Replace the duplicated blocks in
orchestrator/internal/reconciler/reconciler.go lines 717-732, 913-922, and
1015-1029 with calls using labels “pull request delivery”, “update push”, and
“review post”; replace the corresponding blocks in
orchestrator/internal/reconciler/session.go lines 79-94 and 258-271 with calls
using “session push” and “mark PR ready”, preserving continue for the loop-local
control flow at the latter site.

In `@orchestrator/internal/reconciler/session.go`:
- Around line 59-63: Update sessionPushEligible to require both
Delivers(domain.OutputCreatePullRequest) and DeliveryTarget(...) ==
"service_repository", matching shouldOpenPR in decision.go; preserve the
existing historical-run fallback when ExecutionContract is nil.

In `@orchestrator/internal/store/plugins_test.go`:
- Around line 684-687: Extend the rejected-claim assertions in the test around
GetRun to call ListRunPluginSnapshots(ctx, "r") and verify it returns no
snapshots, while preserving the existing Run status and TokenHash checks. Fail
the test if either the Run mutation or any partial snapshot creation occurs.

In `@orchestrator/internal/store/workflow_contract_test.go`:
- Around line 67-92: The TestMemStoreSetReviewPlanFirstWriteIdempotency test
should also cover the review-plan status gate. Add a sub-case creating a queued
review Run, attempt SetReviewPlan for it, and assert the returned error is
ErrInvalidTransition while preserving the existing first-write, retry, and
conflict assertions.

In `@runner/test-fetch-source.sh`:
- Around line 92-95: Update the mock handling for /review-plan near the
received.review-plan write to parse the captured JSON payload and validate
base_sha, head_sha, and merge_base_sha against the expected frozen revisions,
while requiring diff to be non-empty. Make the test fail when any revision is
incorrect or the diff is missing, rather than accepting any non-empty body.

In `@runner/test-integration.sh`:
- Line 229: Update the artifact diff assertion in runner/test-integration.sh at
lines 229-229 to match the Git diff file header pattern ^+++ b/JCODE_TASK_
rather than any occurrence in changed content; apply the same header-anchored
match to the diff assertion in runner/test.sh at lines 121-124 against
$OUT/diff.patch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ffcb284-1283-47ed-bea1-78473b80f258

📥 Commits

Reviewing files that changed from the base of the PR and between 7d37301 and fc575ca.

📒 Files selected for processing (70)
  • CONTEXT.md
  • console/src/api/types.ts
  • console/src/i18n/locales/en.ts
  • console/src/i18n/locales/zh-Hans.ts
  • console/src/pages/RunDetailPage.module.css
  • console/src/pages/RunDetailPage.test.tsx
  • console/src/pages/RunDetailPage.tsx
  • design/README.md
  • design/assets/workflow-contract.css
  • design/assets/workflow-contract.js
  • design/review-coverage.html
  • design/workflow-contract.html
  • docs/11-api.md
  • docs/28-workflow-contract-and-deterministic-review-prd.md
  • docs/29-workflow-contract-and-review-design.md
  • docs/30-open-source-agent-workflow-benchmark.md
  • docs/31-workflow-case-conversations-and-video-agent-poc.md
  • orchestrator/cmd/orchestrator/main.go
  • orchestrator/internal/api/api.go
  • orchestrator/internal/api/api_test.go
  • orchestrator/internal/api/automation_test.go
  • orchestrator/internal/api/plugin_webhook.go
  • orchestrator/internal/api/plugin_webhook_test.go
  • orchestrator/internal/api/review.go
  • orchestrator/internal/api/review_test.go
  • orchestrator/internal/api/runs.go
  • orchestrator/internal/api/runs_projection_test.go
  • orchestrator/internal/api/source.go
  • orchestrator/internal/api/source_review_test.go
  • orchestrator/internal/api/webhook.go
  • orchestrator/internal/domain/domain.go
  • orchestrator/internal/domain/plugins.go
  • orchestrator/internal/domain/review_plan.go
  • orchestrator/internal/domain/review_plan_test.go
  • orchestrator/internal/domain/workflow_contract.go
  • orchestrator/internal/domain/workflow_contract_test.go
  • orchestrator/internal/provider/fake.go
  • orchestrator/internal/provider/gitea.go
  • orchestrator/internal/provider/github.go
  • orchestrator/internal/provider/gitlab.go
  • orchestrator/internal/provider/provider.go
  • orchestrator/internal/reconciler/decision.go
  • orchestrator/internal/reconciler/guardrails_test.go
  • orchestrator/internal/reconciler/permission_test.go
  • orchestrator/internal/reconciler/pr_test.go
  • orchestrator/internal/reconciler/reconciler.go
  • orchestrator/internal/reconciler/session.go
  • orchestrator/internal/reconciler/session_test.go
  • orchestrator/internal/scmevent/event.go
  • orchestrator/internal/scmevent/event_test.go
  • orchestrator/internal/scmevent/normalize.go
  • orchestrator/internal/store/automation_execution.go
  • orchestrator/internal/store/memory.go
  • orchestrator/internal/store/migrations/0064_workflow_contract_review_plan.sql
  • orchestrator/internal/store/pg.go
  • orchestrator/internal/store/pg_workflow_contract_integration_test.go
  • orchestrator/internal/store/plugin_kanban_occurrence.go
  • orchestrator/internal/store/plugins.go
  • orchestrator/internal/store/plugins_test.go
  • orchestrator/internal/store/store.go
  • orchestrator/internal/store/workflow_contract_test.go
  • outputs/workflow-platform-audit-2026-08-01.md
  • runner/README.md
  • runner/entrypoint.sh
  • runner/mockllm/main.go
  • runner/orchclient/main.go
  • runner/test-fetch-source.sh
  • runner/test-integration.sh
  • runner/test-session-scrub-matrix.sh
  • runner/test.sh

Comment thread docs/11-api.md
Comment on lines +1292 to +1310
### 5.7 `POST /internal/v1/runs/{id}/review-plan` — 冻结确定性评审输入

仅 `kind=review` 且处于 `scheduling|running` 的 Run;请求体为单个严格 JSON 对象:

```json
{
"base_sha": "1111111111111111111111111111111111111111",
"head_sha": "2222222222222222222222222222222222222222",
"merge_base_sha": "1111111111111111111111111111111111111111",
"diff": "diff --git a/main.go b/main.go\n..."
}
```

服务端核对 Run 已冻结的 base/head,解析右侧新增行 anchor,丢弃 raw diff,只持久化
canonical `review_plan`。首次写入 `201`,相同 plan 重试 `200`,不同 plan
`409 review_plan_conflict`,revision 不符 `409 review_revision_mismatch`,超过
2 MiB/400 files/2000 hunks 返回 `413 review_input_too_large`。公开 Run JSON 只返回
coverage 与逐文件计数,不返回私有 anchor;随后结构化 Review Result 的每个 finding
都必须命中该计划的 changed right-side line。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Normalize the review-plan error contract.

The endpoint, failure model, and API documentation must use one stable error code for malformed review-plan input.

  • docs/11-api.md#L1292-L1310: document the 400 malformed-input response.
  • docs/29-workflow-contract-and-review-design.md#L434-L448: align invalid_review_plan with the failure-table identifier and update the tests.
📍 Affects 2 files
  • docs/11-api.md#L1292-L1310 (this comment)
  • docs/29-workflow-contract-and-review-design.md#L434-L448
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/11-api.md` around lines 1292 - 1310, Normalize the malformed review-plan
error contract: in docs/11-api.md lines 1292-1310, document the endpoint’s 400
malformed-input response with the stable error code; in
docs/29-workflow-contract-and-review-design.md lines 434-448, replace
invalid_review_plan with the failure-table identifier and update the related
tests to assert that code consistently.

Comment on lines +66 to +70
Ship-R1 不交付 Runner Profile Catalog、Composer live readiness 或 Custom Profile
CRUD。合同中的 timeout 直接冻结当前 Project override 或 Cluster default 的实际
effective value,必须与 Kubernetes `activeDeadlineSeconds` 一致。Ship-R2 引入
operator-owned Runner manifest 和单一 evaluator,再检查 toolchain/capability 并在
创建前返回 typed blocker;任何阶段都不得静默换模型、换 profile、改 output 或降级

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Define which timeout the contract freezes.

The PRD requires the contract timeout to match Kubernetes activeDeadlineSeconds. The reconciler tests distinguish RUN_TIMEOUT as the per-run budget from JobSpec.TimeoutSeconds as RUN_TIMEOUT + timeoutGrace. State whether execution.timeout_seconds stores the budget or the deadline, and model the grace separately so the inspector, runner, and scheduler use the same value.

🧰 Tools
🪛 LanguageTool

[grammar] ~69-~69: Ensure spelling is correct
Context: ...单一 evaluator,再检查 toolchain/capability 并在 创建前返回 typed blocker;任何阶段都不得静默换模型、换 profile、改 ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/28-workflow-contract-and-deterministic-review-prd.md` around lines 66 -
70, Clarify the timeout contract in the PRD by explicitly defining whether
execution.timeout_seconds represents the per-run RUN_TIMEOUT budget or the
Kubernetes activeDeadlineSeconds deadline. Document timeoutGrace separately,
specify the conversion between the two values, and require the inspector,
runner, scheduler, and JobSpec.TimeoutSeconds handling to use the same effective
deadline.

Comment on lines +276 to +285
```json
{
"schema_version": 1,
"plan_hash": "sha256:…",
"rules_revision": "review-v2",
"base_ref": "main",
"head_ref": "feature/x",
"base_sha": "40hex",
"head_sha": "40hex",
"merge_base_sha": "40hex",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^docs/29-workflow-contract-and-review-design\.md$|^outputs/workflow-platform-audit-2026-08-01\.md$|REVIEW|workflow|PRReadyPolicy|contract)' || true

echo
echo "== target snippets =="
if [ -f docs/29-workflow-contract-and-review-design.md ]; then
  sed -n '250,310p' docs/29-workflow-contract-and-review-design.md
fi
echo
if [ -f outputs/workflow-platform-audit-2026-08-01.md ]; then
  sed -n '45,75p' outputs/workflow-platform-audit-2026-08-01.md
fi

echo
echo "== search SHA/validation references =="
rg -n "sha|SHA|40hex|abbreviated|abbreviate|longform|full .*id|object ID|commit ID|ready policy|PRReadyPolicy|REVIEW\.json|plan_hash|base_sha|head_sha|merge_base_sha" docs outputs -g '*.md' -g '*.json' -g '*.yaml' -g '*.yml' || true

echo
echo "== workflow/contract source files =="
git ls-files | rg -i 'review|workflow|contract|ready|policy|runner|platform|audit' | sed -n '1,200p'

Repository: cnjack/cloud

Length of output: 25170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== orchestrator review/domain files outline =="
for f in orchestrator/internal/api/review.go orchestrator/internal/domain/review.go orchestrator/internal/domain/review_plan.go orchestrator/internal/domain/workflow_contract.go; do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" || true
done

echo
echo "== relevant orchestrator snippets =="
for f in orchestrator/internal/api/review.go orchestrator/internal/domain/review.go orchestrator/internal/domain/review_plan.go orchestrator/internal/domain/workflow_contract.go; do
  echo "--- $f"
  rg -n "SHA|sha|Commit|commit|plan_hash|base_sha|head_sha|merge_base_sha|Validate|ValidateAgainst|IsZero|String" "$f" || true
done

echo
echo "== migrations and tests relevant to SHA validation =="
for f in orchestrator/internal/store/migrations/0007_runner_contract.sql orchestrator/internal/store/migrations/0064_workflow_contract_review_plan.sql orchestrator/internal/store/workflow_contract_test.go orchestrator/internal/store/pg_workflow_contract_integration_test.go orchestrator/internal/domain/review_test.go orchestrator/internal/domain/review_plan_test.go; do
  if [ -f "$f" ]; then
    echo "--- $f"
    rg -n "SHA|sha|Commit|commit|base_sha|head_sha|merge_base_sha|plan_hash|Validate|Invalid|regex|7-64|64" "$f" || true
    echo
  fi
done

Repository: cnjack/cloud

Length of output: 12945


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== review_plan.go SHA validation implementation =="
sed -n '53,214p' orchestrator/internal/domain/review_plan.go | cat -n

echo
echo "== behavior probe for ValidCommitSHA inputs =="
python3 - <<'PY'
import re

SHA_REGEX = re.compile(r'^[0-9a-fA-F]+$', re.ASCII)

def valid_commit_sha(value: str) -> bool:
    value = value.strip()
    return bool(SHA_REGEX.match(value)) and 7 <= len(value) <= 64

samples = [
    "1111111",
    "111111",
    "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd",
    "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "zzzzzzz",
    "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeA",
    "",
    "1111111 ",
]
for s in samples:
    print(f"{s!r}: length={len(s)} valid={valid_commit_sha(s)}")
PY

echo
echo "== source references to ValidCommitSHA and BuildReviewPlan =="
rg -n "ValidCommitSHA|BuildReviewPlan|review_revision_invalid|merge_base_sha|base_sha|head_sha" orchestrator runner docs outputs -g '*.go' -g '*.sh' -g '*.md' || true

Repository: cnjack/cloud

Length of output: 14830


Reject abbreviated commit SHA input for Review Plans.

docs/29-workflow-contract-and-review-design.md#L283-L285 shows canonical 40hex SHA placeholders, but orchestrator/internal/domain/review_plan.go#L208-L219 and the audit hardening line accept 7–64 hex strings. This contradicts freeze/replay requirements: queue creation should canonicalize full Git object IDs before persistence, or reject all values shorter than 40 hex. Correct the audit hardening wording to match the enforced canonical requirement.

📍 Affects 2 files
  • docs/29-workflow-contract-and-review-design.md#L276-L285 (this comment)
  • outputs/workflow-platform-audit-2026-08-01.md#L62-L64
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/29-workflow-contract-and-review-design.md` around lines 276 - 285, The
Review Plan contract must require canonical full 40-character hexadecimal Git
object IDs rather than abbreviated SHAs. Update the schema example and
surrounding documentation in docs/29-workflow-contract-and-review-design.md
(lines 276-285), and revise the audit hardening wording in
outputs/workflow-platform-audit-2026-08-01.md (lines 62-64) to match; ensure
queue creation either canonicalizes IDs before persistence or rejects values
shorter than 40 hex characters.

Comment on lines +170 to +177
## 7. 对四个目标场景的落地

| 场景 | Trigger adapter | Profile | Required capabilities | Typed outputs | 关键完成条件 |
| --- | --- | --- | --- | --- | --- |
| PR Review | SCM verified event | Reviewer | source.read、git、scm.review.write | provider_review | fixed SHA、coverage、valid anchors、idempotent publish |
| 竞品研究 → JType | Manual/Cron | Product Manager | web/MCP fetch、jtype.create-card | create_jtype_card、publish_report | sources、request schema、Card receipt |
| JType request → PR | JType receipt/claim | Developer | source.read/write、toolchain、scm.pr.write | create_pull_request | tests、commit、Ready PR、Card backlink |
| 周报/Changelog | Cron occurrence | Developer 或 PM | source.read、scm.read、optional write | publish_report/update_changelog | fixed time window、dedupe、report/PR receipt |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add ripgrep to the PR Review capability row.

docs/29-workflow-contract-and-review-design.md declares ripgrep as a Ship-R1 Reviewer requirement and requires rg during bootstrap. This table omits it. Add ripgrep or mark the table as non-authoritative.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/30-open-source-agent-workflow-benchmark.md` around lines 170 - 177,
Update the PR Review row in the “对四个目标场景的落地” table to include ripgrep alongside
the existing required capabilities, keeping the row’s other fields unchanged.

Comment on lines +375 to +411
for _, req := range c.Requirements {
if !slices.Contains(validWorkflowRequirements, req) {
return fmt.Errorf("unknown workflow requirement %q", req)
}
}
var def builtinWorkflowDefinition
switch c.Workflow.ID {
case BuiltinImplementationTaskID:
def = implementationDefinition
if c.Workflow.Revision != 1 || c.Profile.ID != BuiltinDeveloperProfileID || c.Execution.RunKind != RunKindAgent || c.Execution.WorkspaceAccess != WorkspaceReadWrite {
return errors.New("invalid implementation workflow mapping")
}
out := c.Delivery.Outputs[0]
if out.Type != OutputDiffOnly && out.Type != OutputCreatePullRequest {
return errors.New("implementation workflow has an invalid output")
}
if out.Type == OutputCreatePullRequest && !ValidPRReadyPolicy(out.ReadyPolicy) {
return errors.New("pull-request output requires ready_policy")
}
if out.Type == OutputDiffOnly && out.ReadyPolicy != "" {
return errors.New("diff output forbids ready_policy")
}
case BuiltinPullRequestReviewID:
def = reviewDefinition
out := c.Delivery.Outputs[0]
if c.Workflow.Revision != 1 || c.Profile.ID != BuiltinReviewerProfileID || c.Execution.RunKind != RunKindReview || c.Execution.WorkspaceAccess != WorkspaceReadOnly || out.Type != OutputProviderReview || out.ReadyPolicy != "" {
return errors.New("invalid review workflow mapping")
}
if c.Verification.Mode != "structured_review" || c.Verification.RulesRevision != "review-v2" {
return errors.New("invalid review verification contract")
}
default:
return errors.New("unknown built-in workflow")
}
if c.Workflow.DefinitionHash != hashJSON(def) {
return errors.New("workflow definition hash mismatch")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether the run-creation API can decode a client-supplied execution contract.
fd -t f 'runs.go' orchestrator/internal/api --exec rg -n -C6 'Decode|createRunRequest|ExecutionContract'
rg -n -C4 --type=go 'ExecutionContract\s*[:=]' orchestrator/internal/api

Repository: cnjack/cloud

Length of output: 5154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'orchestrator/internal/api/*.go' 'orchestrator/internal/domain/*.go' 'orchestrator/internal/store/*.go' | sed -n '1,160p'

echo
echo "== runs.go outline/size/slices =="
wc -l orchestrator/internal/api/runs.go
rg -n -C8 'createRun|CreateRun|Run\{|struct|json:"|Decode|Unmarshal|execution_contract|workflow_contract|WorkflowContract' orchestrator/internal/api/runs.go orchestrator/internal/domain/workflow_contract.go

echo
echo "== domain workflow_contract relevant =="
sed -n '320,425p' orchestrator/internal/domain/workflow_contract.go

echo
echo "== store relevant =="
rg -n -C8 'ExecutionContract|Validate\(.*WorkflowContract|workflow_contract|CreateRunTx|CreateRun' orchestrator/internal/store/pg.go orchestrator/internal/store/memory.go

Repository: cnjack/cloud

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== domain Run and CreateRun signatures =="
rg -n -C6 'type Run struct|ExecutionContract|SchemaVersion|func CreateRunTx|func CreateRun\b' orchestrator/internal/domain/orchestration.go orchestrator/internal/domain/domain.go orchestrator/internal/domain/workflow_contract.go orchestrator/internal/store/pg.go orchestrator/internal/store/memory.go

echo
echo "== api decode/util =="
rg -n -C8 'func decodeJSON|decodeJSON\(' orchestrator/internal/api

echo
echo "== run creation body =="
sed -n '65,180p' orchestrator/internal/api/runs.go

echo
echo "== retry and resume relevant =="
sed -n '465,516p' orchestrator/internal/api/runs.go
sed -n '580,670p' orchestrator/internal/api/runs.go

echo
echo "== decodeJSON implementations =="
rg -n -C8 'func decodeJSON' .

Repository: cnjack/cloud

Length of output: 16940


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== decodeJSON implementations =="
rg -n -C8 'func decodeJSON' .

echo
echo "== run creation body =="
sed -n '65,180p' orchestrator/internal/api/runs.go

echo
echo "== retry and resume relevant =="
sed -n '465,516p' orchestrator/internal/api/runs.go
sed -n '580,670p' orchestrator/internal/api/runs.go

echo
echo "== decodeJSON implementations across API files =="
rg -n -C5 'decodeJSON\b' orchestrator/internal/api

Repository: cnjack/cloud

Length of output: 50368


Bind Requirements to the resolved built-in definition.

Validate accepts any whitelisted requirement and permits extra requirements. A self-consistent contract can pass with a create_pull_request output while omitting RequirementSCMPullRequestWrite, or with arbitrary additional requirements. Compare c.Requirements with the def.Requirements selected by the built-in workflow hash.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/domain/workflow_contract.go` around lines 375 - 411,
Update Validate after resolving the built-in definition in the switch to require
c.Requirements to exactly match def.Requirements, rejecting both missing and
extra requirements; retain the existing whitelist validation only as applicable,
and perform this comparison before validating the definition hash.

Comment on lines +2489 to +2496
if snapshots, err := r.st.ListRunPluginSnapshots(ctx, run.ID); err == nil {
for i := range snapshots {
if snapshots[i].HasFrozenRepositoryGrant() {
defaultBranch, repoURL = snapshots[i].DefaultBranch, snapshots[i].CloneURL
break
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not silently fall back to mutable Service values when the snapshot read fails.

addGitEnv discards the error from ListRunPluginSnapshots. If that read fails transiently, defaultBranch and repoURL keep the mutable Service values, and the pod clones whatever the live binding points at. This is the drift this change prevents everywhere else: createJob at Lines 1562-1570 and scmContextForRun both fail or retry instead of degrading. A dispatched run already has a committed grant, so a read failure here is not an acceptable fallback.

Return the error from addGitEnv/jobEnv and let createJob leave the run queued for the next tick, or load the snapshots once in createJob (they are already read there for ClaimRunDispatch) and pass the resolved grant into addGitEnv.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/reconciler/reconciler.go` around lines 2489 - 2496,
Update addGitEnv and jobEnv to propagate ListRunPluginSnapshots errors instead
of silently retaining mutable Service values; ensure createJob handles the
propagated error by leaving the run queued for retry, or reuse the snapshots
already loaded for ClaimRunDispatch and pass the resolved grant into addGitEnv.

Comment on lines +21 to +23
pool *pgxpool.Pool
workflowRunTimeoutSecs int64
workflowSessionTTLSecs int64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check when ConfigureWorkflowTimeoutDefaults runs relative to serving traffic.
rg -n -C8 'ConfigureWorkflowTimeoutDefaults' orchestrator/cmd/orchestrator/main.go orchestrator/internal/reconciler/reconciler.go

Repository: cnjack/cloud

Length of output: 3048


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== orchestrator/internal/store/pg.go relevant symbols =="
ast-grep outline orchestrator/internal/store/pg.go --view compact || true
echo

echo "== ConfigureWorkflowTimeoutDefaults and createRunTx context =="
rg -n -C12 'func configureWorkflowTimeoutDefaults|func createRunTx|workflowRunTimeoutSecs|workflowSessionTTLSecs' orchestrator/internal/store/pg.go
echo

echo "== Memory store context =="
rg -n -C12 'workflowRunTimeoutSecs|workflowSessionTTLSecs|mu' orchestrator/internal/store/memory.go
echo

echo "== New calls around store.New and configure default in orchestrator =="
rg -n -C8 'New\(ctx, cfg\.DatabaseURL\)|ConfigureWorkflowTimeoutDefaults|Reconciler\.New|reconciler\.New' orchestrator -g '*.go'

Repository: cnjack/cloud

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== storage.go ConfigureWorkflowTimeoutDefaults =="
for f in $(git ls-files 'orchestrator/internal/store/*.go'); do
  if rg -q 'func ConfigureWorkflowTimeoutDefaults|workflowRunTimeoutSecs|workflowSessionTTLSecs' "$f"; then
    echo "--- $f ---"
    rg -n -C6 'func ConfigureWorkflowTimeoutDefaults|configureWorkflowTimeoutDefaults|workflowRunTimeoutSecs|workflowSessionTTLSecs' "$f"
  fi
done

echo "== callers and concurrent create-run paths =="
rg -n -C5 'ConfigureWorkflowTimeoutDefaults|CreateRun|createRunTx|http\.Handle|mux.*Create|Runs|POST.*run|Runs.*POST' orchestrator -g '*.go' | sed -n '1,220p'

Repository: cnjack/cloud

Length of output: 24309


Guard PGStore timeout defaults with synchronization.

ConfigureWorkflowTimeoutDefaults writes workflowRunTimeoutSecs and workflowSessionTTLSecs, while createRunTx reads them without any lock. Calls can occur from main and reconciler.New while reconcile paths are already using the same store. Use atomic.Int64 for both fields as shown for MemStore.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/pg.go` around lines 21 - 23, Change PGStore’s
workflowRunTimeoutSecs and workflowSessionTTLSecs fields to atomic.Int64, then
update ConfigureWorkflowTimeoutDefaults to store values atomically and
createRunTx to load them atomically, following the existing MemStore pattern.

Comment on lines +505 to +523
if len(executionContract) > 0 {
decoder := json.NewDecoder(strings.NewReader(string(executionContract)))
decoder.DisallowUnknownFields()
var contract domain.WorkflowContract
if err := decoder.Decode(&contract); err != nil {
return nil, fmt.Errorf("scan run execution contract: %w", err)
}
if err := contract.Validate(); err != nil {
return nil, fmt.Errorf("scan run execution contract: %w", err)
}
r.ExecutionContract = &contract
}
if len(reviewPlan) > 0 {
parsed, err := domain.UnmarshalStoredReviewPlan(reviewPlan)
if err != nil {
return nil, fmt.Errorf("scan run review plan: %w", err)
}
r.ReviewPlan = parsed
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not fail a row scan because a stored contract no longer validates.

scanRun rejects the whole row when the persisted execution_contract has an unknown field or fails Validate. Two normal events trigger that:

  • A rolling upgrade where a newer orchestrator writes a field this binary does not know. DisallowUnknownFields then fails the scan.
  • An intentional edit to a built-in definition. Validate compares Workflow.DefinitionHash with hashJSON(def) (orchestrator/internal/domain/workflow_contract.go Lines 409-411), so every already-persisted Run stops loading.

Because ListRuns and ListRunsByService return on the first scan error, one bad row hides an entire project's Run list, not just its contract projection. Decode tolerantly, skip the projection on validation failure, and log the Run id.

🛡️ Proposed tolerant decode
 	if len(executionContract) > 0 {
-		decoder := json.NewDecoder(strings.NewReader(string(executionContract)))
-		decoder.DisallowUnknownFields()
 		var contract domain.WorkflowContract
-		if err := decoder.Decode(&contract); err != nil {
+		if err := json.Unmarshal(executionContract, &contract); err != nil {
 			return nil, fmt.Errorf("scan run execution contract: %w", err)
 		}
-		if err := contract.Validate(); err != nil {
-			return nil, fmt.Errorf("scan run execution contract: %w", err)
+		if err := contract.Validate(); err == nil {
+			r.ExecutionContract = &contract
 		}
-		r.ExecutionContract = &contract
 	}

Report the validation failure through a counter or log at the call site so drift stays visible.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(executionContract) > 0 {
decoder := json.NewDecoder(strings.NewReader(string(executionContract)))
decoder.DisallowUnknownFields()
var contract domain.WorkflowContract
if err := decoder.Decode(&contract); err != nil {
return nil, fmt.Errorf("scan run execution contract: %w", err)
}
if err := contract.Validate(); err != nil {
return nil, fmt.Errorf("scan run execution contract: %w", err)
}
r.ExecutionContract = &contract
}
if len(reviewPlan) > 0 {
parsed, err := domain.UnmarshalStoredReviewPlan(reviewPlan)
if err != nil {
return nil, fmt.Errorf("scan run review plan: %w", err)
}
r.ReviewPlan = parsed
}
if len(executionContract) > 0 {
var contract domain.WorkflowContract
if err := json.Unmarshal(executionContract, &contract); err != nil {
return nil, fmt.Errorf("scan run execution contract: %w", err)
}
if err := contract.Validate(); err == nil {
r.ExecutionContract = &contract
}
}
if len(reviewPlan) > 0 {
parsed, err := domain.UnmarshalStoredReviewPlan(reviewPlan)
if err != nil {
return nil, fmt.Errorf("scan run review plan: %w", err)
}
r.ReviewPlan = parsed
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@orchestrator/internal/store/pg.go` around lines 505 - 523, Update scanRun’s
execution-contract handling to decode without DisallowUnknownFields and treat
validation failures as a skipped ExecutionContract projection rather than
returning a row-scan error. Preserve errors for malformed contract data, and
report skipped validation failures through the existing logging or counter path
with the Run id so ListRuns and ListRunsByService continue loading other rows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PRD: Workflow Contract, frozen SCM grant, and deterministic PR review

1 participant