Skip to content

fix: make loop goals converge reliably - #335

Merged
pedronauck merged 7 commits into
mainfrom
fix/loop-convergence
Aug 10, 2026
Merged

pedronauck merged 7 commits into
mainfrom
fix/loop-convergence

Conversation

@pedronauck

@pedronauck pedronauck commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Summary

This fixes two critical Loop failures that prevented production convergence:

  • Loop inputs are now materialized exactly once before Goal agents and nested gates consume them. This covers Goal parameters, contract narrative fields, and gate inputs while preserving literal template-shaped user data.
  • Goal completion now converges reliably. Command judges execute in the run workspace, catalog-origin Goal sessions can discover and call compozy__goal_report, and rejected turns retain durable, sanitized diagnostics.

The public contract now exposes both views deliberately:

  • the authored contract remains raw for inspection and round-tripping;
  • materialized_contract is the immutable execution snapshot shown in run previews and progress surfaces.

Root cause

The production incident was reproduced from looprun-944d89b8ccf4999d in .tmp/compozy-dev-home:

  1. {{ .inputs.slug }} survived compilation and reached agents and command judges literally;
  2. command judges used the daemon process directory instead of the run workspace;
  3. Goal sessions created through catalog/runtime paths could miss the Goal reporting capability;
  4. failed criteria were reduced to a generic command_expectation_failed, so the UI could not explain the repeated rejection;
  5. the coordinator kept starting new Goal turns because it never received a valid terminal verdict.

Together these made completed work look incomplete and left the Loop running until its round limit.

Runtime and contract changes

  • Add one-pass materialization helpers for Goal params, contract references, and nested gate inputs.
  • Keep raw definitions immutable and store/expose a separate materialized execution contract.
  • Bind command evaluators to the owning run workspace.
  • Make Goal report discovery consistent across catalog, API, and runtime-created sessions, with run/session ownership fences.
  • Persist structured blocking issues, failed criteria, and warnings on Goal turn verdicts.
  • Sanitize diagnostic output before persistence and projection.
  • Project diagnostics through core API, native tools, SSE/event handling, generated OpenAPI types, CLI output, and the Web Goal timeline.
  • Add append-only global database migration 00058_schema.sql and regenerate sqlc/OpenAPI artifacts.

Web and operator experience

  • Run preview and progress views consume the materialized snapshot.
  • Inspect/definition surfaces continue to show the raw authored contract.
  • Goal turns show the actual failed criteria and warnings instead of an opaque rejection.
  • Story and mock materialization follow the same supported one-pass direct-reference contract and fail fast on invalid fixture references.

Documentation and official skill

  • Update Loop authoring, Goal, grammar, and running documentation.
  • Update skills/compozy/references/loops.md with the raw/materialized contract and Goal reporting behavior.
  • Record the incident, charters, scenarios, and dated QA report under docs/qa/.

Verification

  • make gate-full: PASS
    • evidence fingerprint: 2d227f0e372e750587de36c6646e290867080ade
    • evidence log: .cache/gate/logs/full-1786337342.log
    • 21,341 Go tests passed; 2 documented skips
    • codegen, installer checks, source-size checks, Bun lint/typecheck/test/build, Go fmt/lint/test/build, and boundaries all passed
  • Focused race tests for Loop, Goal, gate, GlobalDB, and daemon paths: PASS
  • Targeted Web Loop tests: 24/24 PASS
  • make codegen-check: PASS

Isolated real-scenario QA

  • QA run: looprun-63a9f3bae9fa958d
  • Workspace: ws_e258a8baa7d7851c
  • Goal session: sess_f445c65958bca2c8d289de70f9582edc
  • Public surfaces exercised: CLI, HTTP API, Web, native Goal tool, SSE/runtime projection
  • Scenarios:
    • LP-goal-command-judge: PASS
    • LP-loop-template-snapshot-round-trip: PASS
    • LP-run-detail-story-redesign: PASS
  • Strict audit: PASS, 0 blockers, 0 warnings
  • Mandatory teardown: PASS, teardown.json reports "clean": true

The QA run proved a rejected command turn with durable diagnostics, a succeeding second turn, successor execution, and terminal run state. It also proved that literal template-shaped input remains literal and that raw/materialized contracts remain distinct.

Settings and compatibility

  • No config.toml keys, defaults, or settings surfaces changed.
  • This is an intentional greenfield contract hardening; no legacy aliases or storage fallbacks were added.

Compozy Impact Audit

  • Native tools: compozy__goal_report availability is consistent for Goal sessions; compozy__loop_status and compozy__loop_turns expose structured verdict diagnostics. Descriptors, generated schemas, catalog fixtures, and native-tool tests were updated.
  • Extensibility and hooks: Goal and gate inputs are materialized exactly once before execution. Existing extension, hook, capability, resource, registry, bridge SDK, and MCP identifiers remain unchanged; their checked data path now receives the materialized contract. No config lifecycle change.
  • Workspace data isolation: command judges receive the owning workspace_id and execute in that workspace. Goal reporting validates run/session ownership. Turn diagnostics are run-scoped in GlobalDB and projected only through the owning run/API/SSE/native-tool paths, with cross-workspace tests.
  • Official Compozy skill: skills/compozy/references/loops.md documents the public raw/materialized contract, Goal reporting capability, and diagnostic behavior.

Commits

  • 9eaaf307 — runtime, storage, contracts, Web, migration, docs, and regression coverage
  • 05f511d7 — release-gate refactors and isolated QA evidence

Summary by CodeRabbit

  • New Features

    • Loop previews and run details now show contracts with resolved input values.
    • Goal turns display criterion results, execution evidence, blocking issues, and diagnostic warnings.
    • Loop status responses include the materialized contract.
    • Commands run from the selected workspace.
    • Unsafe command interpolation is detected and prevented.
  • Bug Fixes

    • Improved validation for contract templates, workspace resolution, and goal parameters.
    • Preserved diagnostic details across retries, recovery, persistence, and live updates.
    • Report targets no longer require an origin session.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Too many files changed for review (136 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@vercel

vercel Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
compozy-site Ready Ready Preview Aug 10, 2026 1:45pm

Request Review

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 1f539a0.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 41c0ace0-6226-4690-aa31-cbd4faef0a11

📥 Commits

Reviewing files that changed from the base of the PR and between 27ac3f3 and 1f539a0.

⛔ Files ignored due to path filters (14)
  • .github/workflows/ci.yml is excluded by !**/*.yml
  • docs/qa/reports/2026-08-10-loop-browser-runtime-closeout.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-extension-detail.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-marketplace-installed-management.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-window-tab-deck-lifecycle.md is excluded by !**/*.md
  • docs/qa/scenarios/RT-home-usage-window-persistence.md is excluded by !**/*.md
  • web/e2e/__tests__/dashboard.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/extensions.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/gateway-operator.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/knowledge.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/marketplace.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/settings.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/triggers-hardening.spec.ts is excluded by !web/e2e/**
  • web/e2e/fixtures/selectors.ts is excluded by !**/fixtures/**, !web/e2e/**
📒 Files selected for processing (11)
  • web/src/systems/gateway/mocks/handlers.ts
  • web/src/systems/marketplace/components/__tests__/marketplace-action-controller.test.tsx
  • web/src/systems/marketplace/components/__tests__/marketplace-detail-manage.test.tsx
  • web/src/systems/marketplace/components/extension-trust-dialog.tsx
  • web/src/systems/marketplace/components/marketplace-action-dialogs.tsx
  • web/src/systems/marketplace/components/marketplace-detail-skill-installed.tsx
  • web/src/systems/marketplace/components/use-extension-install-dialog.tsx
  • web/src/systems/os/components/__tests__/os-window.test.tsx
  • web/src/systems/os/hooks/__tests__/os-interaction-hooks.test.tsx
  • web/src/systems/os/hooks/use-window-merge-target.ts
  • web/src/systems/os/lib/window-merge-target-coordinator.ts

Walkthrough

The change adds runtime contract materialization, structured goal criteria and warnings, diagnostic persistence, workspace-aware command execution, expanded API responses, and web rendering for materialized contracts and goal diagnostics.

Changes

Loop contract materialization

Layer / File(s) Summary
Contract compilation and runtime materialization
internal/loop/*
Contract narratives, goal parameters, gate inputs, and dry-run previews now use runtime inputs. Validation and tests cover rendered values and template references.
Runtime gate execution
internal/loop/gate/*, internal/daemon/loop_runtime_adapters.go
Gate criteria render typed inputs. Command criteria resolve a workspace and execute from its root.
Goal diagnostics
internal/loop/goal/*, internal/session/goal_contract.go, internal/api/contract/goals.go
Goal turns and judge attempts now carry criteria, blocking issues, evidence, payloads, and warnings. Diagnostics are sanitized, cloned, persisted, and restored.

Persistence and API projection

Layer / File(s) Summary
Database diagnostic storage
internal/store/globaldb/*
Judge attempts and goal turns store criteria and warnings JSON. Queries, generated mappings, migrations, event payloads, decoding, and idempotency checks include the new fields.
Runtime and API responses
internal/daemon/*, internal/api/*, internal/cli/*, internal/tools/builtin/*
Run and plan responses expose materialized_contract. API conversions expose criteria and warnings as non-null arrays.
Report target lookup
internal/loop/goal/types_tool.go, internal/store/globaldb/*
Report targets no longer require or compare OriginSessionID and can resolve through the bound session.

Web loop views

Layer / File(s) Summary
Materialized contract display
web/src/systems/loops/components/run-form/*, web/src/systems/os/apps/loops/*, web/src/systems/loops/mocks/*
Dry-run previews and run pages use materialized contracts. Mock responses materialize contract fields from run inputs.
Goal diagnostic display and propagation
web/src/systems/loops/lib/*, web/src/systems/loops/hooks/*, web/src/systems/loops/components/run-page/*
Completed goal-turn events validate and propagate criteria and warnings. Timeline rows render criterion output, status, issues, and warnings.

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

Possibly related PRs

  • compozy/compozy#271: Shared goal-turn diagnostics and session transcript rendering consume criterion and warning data.
  • compozy/compozy#290: Both changes modify loop contracts, gate diagnostics, materialization, persistence, and evaluator behavior.
  • compozy/compozy#302: Both changes modify Loop UI components and backend runtime paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 change: improving Loop goal convergence reliability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 fix/loop-convergence

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.

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
internal/loop/goal/templates.go (1)

125-181: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Treat prior diagnostics as untrusted prompt data.

Stdout, Stderr, and warning messages can contain evaluator-controlled instructions. This code writes them directly into the next Goal agent prompt. A command criterion can emit text that changes the next agent behavior.

Render diagnostic values in a bounded, explicitly untrusted representation. Add an instruction that the agent must not follow instructions contained in diagnostic output.

🤖 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 `@internal/loop/goal/templates.go` around lines 125 - 181, Update
appendPreviousCriterionStream and appendPreviousWarnings to render stdout,
stderr, and warning messages as bounded, explicitly untrusted diagnostic data
rather than injecting unrestricted text into the prompt. Add clear prompt text
near the existing diagnostic sections instructing the Goal agent not to follow
instructions contained in diagnostic output, while preserving the existing
criterion and warning context.
🧹 Nitpick comments (1)
internal/loop/gate/evaluator_test.go (1)

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

Add a regression for literal template-shaped rubric text.

This test now uses an already materialized rubric. It does not protect the boundary where RenderAgentJudgeRubric must preserve authored {{ ... }} text and must not render it a second time. Add a focused case in this canonical suite.

Suggested test shape
 	t.Run("Should append a materialized rubric to contract context", func(t *testing.T) {
 		t.Parallel()
 		// existing assertions
 	})
+	t.Run("Should preserve literal template-shaped rubric text", func(t *testing.T) {
+		t.Parallel()
+		const literal = `Check {{ .inputs.value }} as authored text`
+		rendered, err := RenderAgentJudgeRubric(
+			dsl.GateCriterion{
+				ID: "judge", Type: dsl.CriterionAgentJudge, Rubric: literal,
+			},
+			validContract(),
+			JudgeEvidence{},
+		)
+		if err != nil {
+			t.Fatalf("RenderAgentJudgeRubric() error = %v", err)
+		}
+		if !stringsContains(rendered, literal) {
+			t.Fatalf("rendered rubric does not preserve %q:\n%s", literal, rendered)
+		}
+	})

As per path instructions, update the canonical Go test suite and keep the new case under t.Run("Should …").

🤖 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 `@internal/loop/gate/evaluator_test.go` around lines 1492 - 1499, Add a focused
t.Run("Should …") regression in the canonical suite for RenderAgentJudgeRubric
that supplies a rubric containing literal template-shaped {{ ... }} text after
materialization and verifies the output preserves it unchanged without a second
render. Keep the existing materialized-rubric test intact and assert the
expected rendered rubric and error behavior.

Source: Path instructions

🤖 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 `@internal/daemon/loop_runtime_adapters.go`:
- Around line 188-189: Update the command execution flow around
exec.CommandContext in the runtime adapter to prevent shell-interpolated command
checks from executing untrusted template-rendered values. Require command edit
or configuration-author authorization for command.Check and command.Contains
inputs, or replace shell execution with an explicit executable-and-argv contract
that cannot interpret injected shell syntax.

In `@internal/store/globaldb/global_db_goal_judge_integration_test.go`:
- Around line 114-118: Strengthen the assertions in the completion and reload
checks for the goal judge result: verify the criterion’s ID, Type, and Outcome,
plus the diagnostic warning’s Code and Message, rather than only collection
sizes. Apply the same field-level assertions to both completed and loaded
results, using the expected values shown in the fixture.

In `@internal/store/globaldb/global_db_goal_turn_runtime_integration_test.go`:
- Around line 619-626: Strengthen the completed-event assertions after the
criteria and warnings length checks by validating representative serialized
fields: the criterion’s id, type, passed status, and stdout, plus the warning’s
code and message. Keep the existing cardinality checks and use the decoded
entries from completedEvent.

In `@web/src/systems/loops/components/stories/loop-run-scenario-props.ts`:
- Line 43: Update the props construction in the loop scenario mapping to pass
the scenario’s resolved inputs to materializeContractFixture and assign its
result to materializedContract instead of using definition.contract directly.
Preserve the existing resolved-input source and return shape so LoopRunPageBody
receives the materialized contract.

In `@web/src/systems/loops/lib/loop-events.ts`:
- Around line 291-305: Update goalCriteria to validate record?.passed is
explicitly a boolean before accepting a criterion; reject entries with missing
or invalid passed values instead of coercing them to false, while preserving
valid true/false values. Add a malformed-event test in the loop-events test
suite covering a criterion with an approved outcome but no Boolean passed field.

---

Outside diff comments:
In `@internal/loop/goal/templates.go`:
- Around line 125-181: Update appendPreviousCriterionStream and
appendPreviousWarnings to render stdout, stderr, and warning messages as
bounded, explicitly untrusted diagnostic data rather than injecting unrestricted
text into the prompt. Add clear prompt text near the existing diagnostic
sections instructing the Goal agent not to follow instructions contained in
diagnostic output, while preserving the existing criterion and warning context.

---

Nitpick comments:
In `@internal/loop/gate/evaluator_test.go`:
- Around line 1492-1499: Add a focused t.Run("Should …") regression in the
canonical suite for RenderAgentJudgeRubric that supplies a rubric containing
literal template-shaped {{ ... }} text after materialization and verifies the
output preserves it unchanged without a second render. Keep the existing
materialized-rubric test intact and assert the expected rendered rubric and
error behavior.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 49305a19-fe19-45fe-8632-038af06c4f07

📥 Commits

Reviewing files that changed from the base of the PR and between b1885a1 and 05f511d.

⛔ Files ignored due to path filters (18)
  • docs/design/opendesign/_done/loops/LOOP-RUN-REDESIGN-SPEC.md is excluded by !docs/design/**/*, !**/*.md
  • docs/qa/bugs/BUG-20260808-goal-command-judge-unavailable.md is excluded by !**/*.md
  • docs/qa/charters/CH-loop-goal-command-convergence.md is excluded by !**/*.md
  • docs/qa/charters/CH-loop-template-materialization.md is excluded by !**/*.md
  • docs/qa/reports/2026-08-10-loop-convergence.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-goal-command-judge.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-loop-template-snapshot-round-trip.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-run-detail-story-redesign.md is excluded by !**/*.md
  • internal/store/globaldb/schema/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
  • internal/tools/builtin/testdata/native-tool-catalog.json is excluded by !**/*.json
  • openapi/compozy.json is excluded by !**/*.json
  • packages/site/content/docs/loops/authoring.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/loops/goals.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/loops/reference-grammar.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/loops/running.mdx is excluded by !**/*.mdx
  • skills/compozy/references/loops.md is excluded by !**/*.md
  • web/src/generated/compozy-openapi.d.ts is excluded by !**/generated/**, !**/generated/**, !**/*.d.ts
  • web/src/systems/loops/components/run-page/stories/goal-turn-timeline.stories.tsx is excluded by !**/*.stories.tsx
📒 Files selected for processing (86)
  • internal/api/contract/goals.go
  • internal/api/contract/loop_runs.go
  • internal/api/contract/loops.go
  • internal/api/core/conversions_goals.go
  • internal/api/core/loops_test.go
  • internal/cli/loop_runs.go
  • internal/cli/loop_runs_output.go
  • internal/daemon/daemon_review_and_fix_e2e_integration_test.go
  • internal/daemon/loop_api_goal_turns.go
  • internal/daemon/loop_api_runs.go
  • internal/daemon/loop_goal_executor.go
  • internal/daemon/loop_runtime_adapters.go
  • internal/daemon/loop_runtime_adapters_test.go
  • internal/daemon/loop_runtime_selection_integration_test.go
  • internal/daemon/native_loop_tools_test.go
  • internal/daemon/task_runtime_boot_roles.go
  • internal/loop/action_contract.go
  • internal/loop/action_goal_params.go
  • internal/loop/compiler.go
  • internal/loop/contract_references.go
  • internal/loop/control_contract_gate.go
  • internal/loop/control_gate.go
  • internal/loop/control_gate_config_test.go
  • internal/loop/coordinator_action.go
  • internal/loop/coordinator_snapshot_test.go
  • internal/loop/coordinator_test.go
  • internal/loop/gate/evaluator.go
  • internal/loop/gate/evaluator_command.go
  • internal/loop/gate/evaluator_judge_human.go
  • internal/loop/gate/evaluator_test.go
  • internal/loop/gate/judge.go
  • internal/loop/gate/types.go
  • internal/loop/goal/executor.go
  • internal/loop/goal/executor_test.go
  • internal/loop/goal/executor_test_support_test.go
  • internal/loop/goal/route.go
  • internal/loop/goal/templates.go
  • internal/loop/goal/types.go
  • internal/loop/goal/types_judge.go
  • internal/loop/goal/types_tool.go
  • internal/loop/goal/types_turn.go
  • internal/loop/linter_references.go
  • internal/loop/linter_test.go
  • internal/loop/service.go
  • internal/loop/service_test.go
  • internal/loop/service_types.go
  • internal/session/goal_contract.go
  • internal/store/globaldb/global_db_goal_checkpoint_integration_test.go
  • internal/store/globaldb/global_db_goal_judge_helpers.go
  • internal/store/globaldb/global_db_goal_judge_integration_test.go
  • internal/store/globaldb/global_db_goal_judges.go
  • internal/store/globaldb/global_db_goal_schema_integration_test.go
  • internal/store/globaldb/global_db_goal_tools.go
  • internal/store/globaldb/global_db_goal_turn_complete.go
  • internal/store/globaldb/global_db_goal_turn_complete_helpers.go
  • internal/store/globaldb/global_db_goal_turn_reader.go
  • internal/store/globaldb/global_db_goal_turn_runtime_integration_test.go
  • internal/store/globaldb/global_db_task_claim_test.go
  • internal/store/globaldb/goal_judge_sqlc_mapping.go
  • internal/store/globaldb/goal_runtime_mapping.go
  • internal/store/globaldb/goal_verdict_diagnostics.go
  • internal/store/globaldb/queries/goal_turn_judge.sql
  • internal/store/globaldb/schema/definitions/50_loops.sql
  • internal/store/globaldb/schema/migrations/00058_schema.sql
  • internal/store/globaldb/sqlcgen/goal_runtime.sql.go
  • internal/store/globaldb/sqlcgen/goal_turn_judge.sql.go
  • internal/store/globaldb/sqlcgen/models.go
  • internal/tools/builtin/builtin_test.go
  • internal/tools/builtin/loops_output_schemas.go
  • web/src/systems/loops/components/__tests__/goal-turn-timeline.test.tsx
  • web/src/systems/loops/components/__tests__/loop-run-form.test.tsx
  • web/src/systems/loops/components/run-form/loop-run-preview.tsx
  • web/src/systems/loops/components/run-page/goal-turn-timeline.tsx
  • web/src/systems/loops/components/run-page/loop-run-page-body.tsx
  • web/src/systems/loops/components/stories/loop-run-scenario-props.ts
  • web/src/systems/loops/hooks/__tests__/use-loop-actions.test.tsx
  • web/src/systems/loops/hooks/__tests__/use-loop-run-form-state.test.tsx
  • web/src/systems/loops/hooks/__tests__/use-loops.test.tsx
  • web/src/systems/loops/hooks/use-goal-turns.ts
  • web/src/systems/loops/lib/__tests__/loop-events.test.ts
  • web/src/systems/loops/lib/loop-events.ts
  • web/src/systems/loops/mocks/fixture-run-details.ts
  • web/src/systems/loops/mocks/handlers.ts
  • web/src/systems/loops/mocks/materialize-contract-fixture.ts
  • web/src/systems/os/apps/loops/loop-run-detail-location.tsx
  • web/src/systems/os/apps/loops/use-loop-run-page.ts

Comment on lines 188 to +189
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = rootDir

Copy link
Copy Markdown
Contributor

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
set -euo pipefail

rg -n -C 4 --type go \
  'renderGateCriterion|CriterionCommand|RunCommand\(|Inputs:|StartLoop|CreateLoopRun|ExecuteLoop' \
  internal

Repository: compozy/compozy

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the gate runner and relevant loop runtime definitions without loading large search output.
wc -l internal/daemon/loop_runtime_adapters.go
sed -n '1,240p' internal/daemon/loop_runtime_adapters.go

# Locate exact gate criterion command definitions and the structs handling loop commands/criteria.
sed -n '1,220p' internal/daemon/task_runtime_adapters.go 2>/dev/null || true
rg -n --type go 'type .*CommandRequest|CommandRequest|CommandResult|type .*Criterion|Check:|Start:|gate\.' internal/internal internal/daemon internal/contract internal/task internal/api/core/loops.go internal/contract internal/dsl internal/loop internal/looppkg 2>/dev/null | head -n 250

Repository: compozy/compozy

Length of output: 32610


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- loop command evaluator implementation'
sed -n '1,120p' internal/loop/gate/evaluator_command.go

echo '--- loop command request type'
sed -n '248,275p' internal/loop/gate/types.go

echo '--- command criterion construction sites'
rg -n --type go 'Check:|CriterionCommand|Inputs:|=.*Check|Render.*Check|command|check' internal/loop/gate/evaluator_command.go internal/loop/dsl/gate_start.go internal/loop 2>/dev/null | rg -n 'Render|evaluator_command|gate_start|CriterionCommand|Check:' | head -n 220

echo '--- files containing Render functions for gate criteria'
rg -n --type go 'func .*Render|Render("check"|check|Check|criterion)' internal/loop internal/dsl internal/contract | head -n 220

echo '--- run start/criteria request APIs'
rg -n --type go 'RunLoopRequest|StartLoop|CreateLoopRun|Run:|LoopPlan|loopDefinition|Contract|Inputs' internal/api/core/loops.go internal/api/core/loops_test.go internal/cli/clients.go internal/contract internal/dsl | head -n 260

Repository: compozy/compozy

Length of output: 15085


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- RenderGateCriterion and callers'
sed -n '1,180p' internal/loop/control_gate_config.go
rg -n --type go 'RenderGateCriterion|GateCriteria|Criteria|loopDefinition|Definition' internal/loop/control_gate_config.go internal/loop internal/daemon internal/api/core/loops.go internal/contract internal/dsl | head -n 280

echo '--- GateCriterion and CommandRequest fields in source'
sed -n '1,40p' internal/loop/dsl/gate_start.go
sed -n '267,272p' internal/loop/gate/types.go

echo '--- auth/api contracts for run command and loop update paths'
sed -n '1,240p' internal/api/core/loops.go
rg -n --type go 'HandleRunLoop|HandleUpdateLoop|HandleCreateLoop|Permission|Editor|LoopDefinition|Contract|Start|Criteria|Inputs' internal/api/core/loops.go internal/api/core/loops_test.go internal/contract internal/dsl | head -n 320

Repository: compozy/compozy

Length of output: 30927


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- internal/loop/service_start resolve/run implementation'
sed -n '1,270p' internal/loop/service_start.go

echo '--- template rendering function and gateway'
sed -n '1,140p' internal/loop/dsl/refs/template.go
rg -n --type go 'RenderTemplateString|ResolveEffectiveInputs|Build.*Effective|effectiveInputs|Values|StartMetadata' internal/loop internal/daemon internal/api/core/loops.go | head -n 260

echo '--- API loop handlers relevant sections'
sed -n '1,180p' internal/api/core/loops.go
sed -n '180,420p' internal/api/core/loops.go

Repository: compozy/compozy

Length of output: 36603


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- control_gate.go command check rendering'
sed -n '230,305p' internal/loop/control_gate.go
sed -n '1,220p' internal/loop/service.go

echo '--- RunLoop service implementation'
rg -n --type go 'func \(s \*service\) RunLoop|func \(s \*service\) RunLoopDry|effectiveConfig|effectiveConfig' internal/loop/service.go internal/loop internal/daemon/loop_api_service.go | head -n 240
sed -n '220,340p' internal/loop/service.go

echo '--- GateCriterion Inputs and RenderTemplateString check target sites'
rg -n --type go 'Inputs\[\].*Raw|RenderTemplateString\(["'']check|checkRaw|checkRaw' internal/loop/internal internal/loop/control_gate.go internal/loop/gate internal/loop/dsl/gate_start.go internal/loop/dsl 2>/dev/null | head -n 220
sed -n '1,80p' internal/loop/dsl/gate_start.go

Repository: compozy/compozy

Length of output: 12677


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- contract RunLoopRequest and validation'
rg -n --type go 'type RunLoopRequest|struct .*ConfigOverrides|type ConfigOverride|type EffectiveConfig|type LoopConfig|ConfigOverrides|enabled_checks' internal/contract internal/api/contract internal/loop internal/api/core/loops_test.go | head -n 260

echo '--- relevant source files'
for f in internal/api/contract/loop_payloads.go internal/contract/loops.go internal/loop/types.go internal/loop/config.go internal/loop/effective.go internal/loop/gate/evaluator.go internal/loop/gate/types.go; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    case "$f" in
      internal/api/contract/loop_payloads.go) sed -n '1,220p' "$f" ;;
      internal/contract/loops.go) sed -n '1,220p' "$f" ;;
      internal/loop/types.go) sed -n '1,220p' "$f" ;;
      internal/loop/config.go) sed -n '1,220p' "$f" ;;
      internal/loop/effective.go) sed -n '1,260p' "$f" ;;
      internal/loop/gate/evaluator.go) sed -n '180,260p' "$f" ;;
      internal/loop/gate/types.go) sed -n '248,275p' "$f" ;;
    esac
  fi
done

echo '--- validation tests that use enabled_checks'
rg -n --type go 'enabled_checks|PatchLoopRequest|LoopConfig|ConfigOverrides' internal/api/core/loops_test.go internal/daemon/loop_api_runs_test.go internal/daemon/loop_goal_command_e2e_integration_test.go | head -n 220

Repository: compozy/compozy

Length of output: 2391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- LoopConfig contract'
sed -n '180,270p' internal/api/contract/loops.go

echo '--- RunLoopRequest validation'
sed -n '205,250p' internal/api/core/loops.go

echo '--- loop config construction and enabled check parsing'
sed -n '200,455p' internal/loop/config.go
sed -n '27,45p' internal/loop/service_types.go

Repository: compozy/compozy

Length of output: 11002


Do not execute shell-interpolated command checks. command.Check and command.Contains are template-rendered before exec.CommandContext(..., "sh", "-c", command), so a template from inputs.values or config_overrides.enabled_checks_json can insert shell syntax and run arbitrary commands in the resolved workspace. Require command edit/config-author permissions for these fields, or move to an explicit executable-with-argv contract.

🧰 Tools
🪛 OpenGrep (1.26.0)

[ERROR] 188-188: Dynamic command passed to exec.Command with a shell invocation. Pass arguments directly to exec.Command without a shell wrapper.

(coderabbit.command-injection.go-exec-command)

🤖 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 `@internal/daemon/loop_runtime_adapters.go` around lines 188 - 189, Update the
command execution flow around exec.CommandContext in the runtime adapter to
prevent shell-interpolated command checks from executing untrusted
template-rendered values. Require command edit or configuration-author
authorization for command.Check and command.Contains inputs, or replace shell
execution with an explicit executable-and-argv contract that cannot interpret
injected shell syntax.

Source: Linters/SAST tools

Comment thread internal/store/globaldb/global_db_goal_judge_integration_test.go
Comment thread web/src/systems/loops/components/stories/loop-run-scenario-props.ts Outdated
Comment thread web/src/systems/loops/lib/loop-events.ts
coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
internal/daemon/loop_runtime_adapters.go (1)

188-190: ⚠️ Potential issue | 🟠 Major

Do not treat the #nosec comment as a shell-injection fix.

exec.CommandContext(ctx, "sh", "-c", command) still interprets req.Command as shell code. The comment does not enforce shellQuote for every dynamic value or restrict authored commands to trusted principals. Replace this with an executable-and-argv contract, or prove and test the shell-quoting and authorization invariants before keeping the suppression.

Verification command
#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 --type go \
  'shellQuote|Render.*Command|CommandRequest|enabled_checks_json|exec\.CommandContext\(ctx,\s*"sh",\s*"-c"' \
  internal/loop internal/daemon internal/api
🤖 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 `@internal/daemon/loop_runtime_adapters.go` around lines 188 - 190, Replace the
shell-based execution in the command handling path around exec.CommandContext
with an executable-and-argv contract so req.Command is not interpreted as shell
code. Update the command rendering/request flow to return a validated executable
and argument list, and invoke them directly; if shell execution must remain,
establish authorization and complete shellQuote invariants with tests before
retaining the `#nosec` suppression.

Source: Linters/SAST tools

🤖 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 `@internal/loop/dsl/refs/template_command.go`:
- Around line 49-101: Extend validateShellCommandNode and its branch traversal
to track whether each action is inside an active shell quote context, and reject
shellQuoteTemplateFunction interpolations when surrounding literal text uses
shell quotes that the rendered value can terminate. Apply this validation to
complete shell word content rather than only the action pipeline, and add a
regression proving `echo "{{ .inputs.value | shellQuote }}"` fails compilation.

In `@internal/loop/goal/executor_test.go`:
- Around line 1559-1562: Replace the invalid new(1) initializer in the
CriterionResult test fixture with a declared int variable initialized to 1, then
assign its address to ExitCode while preserving the existing rejected verdict
and hostileStderr values.

---

Duplicate comments:
In `@internal/daemon/loop_runtime_adapters.go`:
- Around line 188-190: Replace the shell-based execution in the command handling
path around exec.CommandContext with an executable-and-argv contract so
req.Command is not interpreted as shell code. Update the command
rendering/request flow to return a validated executable and argument list, and
invoke them directly; if shell execution must remain, establish authorization
and complete shellQuote invariants with tests before retaining the `#nosec`
suppression.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e7c30f56-ec29-4863-b51f-60245ee5aa84

📥 Commits

Reviewing files that changed from the base of the PR and between 05f511d and 388472e.

⛔ Files ignored due to path filters (5)
  • docs/qa/reports/2026-08-10-loop-convergence.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-loop-template-snapshot-round-trip.md is excluded by !**/*.md
  • packages/site/content/docs/loops/dsl-reference.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/loops/goal-node.mdx is excluded by !**/*.mdx
  • skills/compozy/references/loops.md is excluded by !**/*.md
📒 Files selected for processing (19)
  • internal/daemon/loop_runtime_adapters.go
  • internal/loop/compiler.go
  • internal/loop/control_gate.go
  • internal/loop/coordinator_test.go
  • internal/loop/dsl/refs/namespace.go
  • internal/loop/dsl/refs/refs_test.go
  • internal/loop/dsl/refs/template.go
  • internal/loop/dsl/refs/template_command.go
  • internal/loop/gate/evaluator_test.go
  • internal/loop/goal/executor_test.go
  • internal/loop/goal/prompt_diagnostics.go
  • internal/loop/goal/templates.go
  • internal/loop/linter_references.go
  • internal/loop/linter_test.go
  • internal/store/globaldb/global_db_goal_judge_integration_test.go
  • internal/store/globaldb/global_db_goal_turn_runtime_integration_test.go
  • web/src/systems/loops/components/stories/loop-run-scenario-props.ts
  • web/src/systems/loops/lib/__tests__/loop-events.test.ts
  • web/src/systems/loops/lib/loop-events.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • web/src/systems/loops/components/stories/loop-run-scenario-props.ts
  • internal/store/globaldb/global_db_goal_turn_runtime_integration_test.go
  • internal/loop/compiler.go
  • internal/loop/coordinator_test.go
  • internal/loop/control_gate.go
  • internal/store/globaldb/global_db_goal_judge_integration_test.go
  • internal/loop/gate/evaluator_test.go
  • web/src/systems/loops/lib/loop-events.ts

Comment thread internal/loop/dsl/refs/template_command.go Outdated
Comment thread internal/loop/goal/executor_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/loop/dsl/refs/template_command.go (2)

192-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider comparing branch states with the entry state for IfNode and WithNode.

validateShellCommandRange requires both branches to equal the entry state (line 184). validateShellCommandBranches only requires the two branches to equal each other (line 205). A template such as {{if .x}}'{{else}}'{{end}}foo{{if .x}}'{{else}}'{{end}} therefore passes with a conditional quote span. The final check at line 75 still rejects an unbalanced end state, so this is not exploitable today. The asymmetry is intentional if you want to allow authored conditional quoting; add a short comment stating that intent.

🤖 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 `@internal/loop/dsl/refs/template_command.go` around lines 192 - 211, Update
validateShellCommandBranches to require both validated branch states to match
the entry state, consistent with validateShellCommandRange, preventing
conditional quote-context changes across IfNode and WithNode branches. If
conditional quoting is intentionally supported, retain the current comparison
and add a brief comment documenting that behavior instead.

78-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the global scope of the << rejection.

hasDoubleLess and emitsDynamic are validator-wide flags. Any authored << anywhere in the template blocks every dynamic interpolation in that template, even in an unrelated branch. This is a safe, conservative choice. Add a short comment so a later reader does not narrow it to positional tracking without understanding the heredoc risk.

🤖 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 `@internal/loop/dsl/refs/template_command.go` around lines 78 - 82, Add a
concise comment immediately before the unsafeCommandTemplateError check
explaining that the validator-wide hasDoubleLess and emitsDynamic flags
intentionally reject any dynamic interpolation when << appears anywhere in the
template, including unrelated branches, as a conservative heredoc safety rule.
🤖 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.

Nitpick comments:
In `@internal/loop/dsl/refs/template_command.go`:
- Around line 192-211: Update validateShellCommandBranches to require both
validated branch states to match the entry state, consistent with
validateShellCommandRange, preventing conditional quote-context changes across
IfNode and WithNode branches. If conditional quoting is intentionally supported,
retain the current comparison and add a brief comment documenting that behavior
instead.
- Around line 78-82: Add a concise comment immediately before the
unsafeCommandTemplateError check explaining that the validator-wide
hasDoubleLess and emitsDynamic flags intentionally reject any dynamic
interpolation when << appears anywhere in the template, including unrelated
branches, as a conservative heredoc safety rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 789f000f-ba74-4581-bdc4-f7985ed953f6

📥 Commits

Reviewing files that changed from the base of the PR and between 388472e and 9adc345.

⛔ Files ignored due to path filters (5)
  • docs/qa/reports/2026-08-10-loop-convergence.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-loop-template-snapshot-round-trip.md is excluded by !**/*.md
  • packages/site/content/docs/loops/dsl-reference.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/loops/goal-node.mdx is excluded by !**/*.mdx
  • skills/compozy/references/loops.md is excluded by !**/*.md
📒 Files selected for processing (5)
  • internal/loop/action_goal_params.go
  • internal/loop/dsl/refs/namespace.go
  • internal/loop/dsl/refs/refs_test.go
  • internal/loop/dsl/refs/template_command.go
  • internal/loop/goal/executor_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/loop/dsl/refs/namespace.go
  • internal/loop/dsl/refs/refs_test.go
  • internal/loop/goal/executor_test.go

coderabbitai[bot]
coderabbitai Bot previously requested changes Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@internal/loop/dsl/refs/refs_test.go`:
- Line 217: Update the template case in the relevant Go test so the if and else
branches produce different quote contexts, allowing the rendered or compiled
result to depend on .inputs.done. Assert the resulting behavior for both
condition values; if quote transitions inside conditionals are intentionally
rejected, rename the test and assert that rejection instead.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39ba5b62-5b72-4d3d-b2ef-3486f2737baf

📥 Commits

Reviewing files that changed from the base of the PR and between 9adc345 and 27ac3f3.

⛔ Files ignored due to path filters (2)
  • docs/qa/reports/2026-08-10-loop-convergence.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-loop-template-snapshot-round-trip.md is excluded by !**/*.md
📒 Files selected for processing (2)
  • internal/loop/dsl/refs/refs_test.go
  • internal/loop/dsl/refs/template_command.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/loop/dsl/refs/template_command.go

t.Run("Should reject conditional shell quote context changes", func(t *testing.T) {
t.Parallel()

raw := `{{ if .inputs.done }}'{{ else }}'{{ end }}value{{ if .inputs.done }}'{{ else }}'{{ end }}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the conditional branches exercise different quote contexts.

Both if and else branches emit ' in both conditionals. The template always renders 'value', regardless of .inputs.done. This does not test a branch-dependent quote-context change. Make the branches produce different quote states, or rename the test if the compiler intentionally rejects any quote transition inside a conditional.

As per path instructions, Go tests must test meaningful behavior and verify behavior outcomes.

🤖 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 `@internal/loop/dsl/refs/refs_test.go` at line 217, Update the template case in
the relevant Go test so the if and else branches produce different quote
contexts, allowing the rendered or compiled result to depend on .inputs.done.
Assert the resulting behavior for both condition values; if quote transitions
inside conditionals are intentionally rejected, rename the test and assert that
rejection instead.

Source: Path instructions

@pedronauck
pedronauck dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] August 10, 2026 13:43

Superseded by the verified fixes from CodeRabbit rounds 1-3 and final closeout commit 1f539a0.

@pedronauck

Copy link
Copy Markdown
Member Author

Final closeout: commit 1f539a0 adds the browser runtime/E2E stabilization found by the complete lane. The official daemon-served Playwright suite passed with no failed tests. make gate-full is pass CURRENT for fingerprint db836080f6df76ebf39dbc345852d95229d4179d (21,361 Go tests, 2 intentional skips). Isolated Web/runtime QA audit: PASS, 0 blockers, 0 warnings; teardown clean with zero survivors. Marketplace update-only manual paths remain honestly blocked-verify because the official isolated catalog exposed no update candidate; the seeded E2E update-confirm contract passed. This is the post-review closeout; no fourth CodeRabbit round is requested.

@pedronauck
pedronauck merged commit 467a0f8 into main Aug 10, 2026
18 of 19 checks passed
@pedronauck
pedronauck deleted the fix/loop-convergence branch August 10, 2026 13:51

This branch was successfully deployed

1 active deployment
Preview — 1f539a09 Deployed Aug 10, 2026 by vercel[bot]
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.

1 participant