Skip to content

feat(validation): internal validation log page + live per-criterion progress checklist - #307

Closed
KCSAbeywickrama wants to merge 8 commits into
wso2:mainfrom
KCSAbeywickrama:validation-phase
Closed

KCSAbeywickrama wants to merge 8 commits into
wso2:mainfrom
KCSAbeywickrama:validation-phase

Conversation

@KCSAbeywickrama

@KCSAbeywickrama KCSAbeywickrama commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two user-facing additions to the validation phase, plus a naming-consistency sweep that
qualifies the criteria/validation surface across the stack.

1. Internal validation log page

The Deployments board's validation chip now always opens an internal validation log page
(/projects/$projectName/deployments/validation/$issueNumber) in every state — running, failed,
and completed. It shows live progress while the run is in flight and the historical runner log once
it has finished. Validation dispatches through the shared coding executor as a kind=coding
execution, so its pod log is captured durably exactly like an implementation task's — no
validation-specific backend log work was needed. The page also carries jump-to-issue and
jump-to-PR buttons. The external validationUrl remains the fallback only when the status
payload carries no issue number.

2. Real-time per-criterion validation checklist

The validation page renders the acceptance-criteria checklist (authored in the design phase, read
from specs/validation/validation-criteria.json) and overlays each criterion's live status
(validating / passed / failed / skipped) as the runner works, with a current-row highlight
and an "N/M done" tally.

Pipeline:

  • A Playwright reporter (criterion-reporter.cjs) emits per-test begin/end over a Unix socket to a
    harness listener in the runner. (The SDK captures Playwright's own stdout as a tool_result and
    drops it, so a side-channel is required for the live path.)
  • The listener fans out to (a) the live NDJSON progress stream (kind:"criterion" frames)
    consumed by the existing SSE task-log, and (b) a POST to the platform that persists
    per-criterion status in a durable store.
  • The console seeds from the store (one-shot GET) and overlays the live stream — so a fresh
    page load, or a run that has already finished, still shows the per-criterion results.

Contract changes

  • Public: GET /projects/{projectName}/tasks/{issueNumber}/validation-criteria →
    ValidationCriterionStatus[] (one-shot seed for the checklist).
  • Internal: POST /executions/{executionId}/validation-criteria (runner → platform report),
    sitting in the runner-validation-* subfamily.
  • DeployStage.validationIssue (int64) and TaskView/TaskDetail.prUrl added.

Durable store

validation_criterion_statuses table, keyed by (repo, issue_number, criterion_id), last-write-wins
upsert, org-fenced reads; execution_id kept for provenance only. Registered as the
validation_criterion_statuses migration step (appended last — no FK, ordering not load-bearing).

Naming consistency

Qualified the validation surface so bare criteria/criterion names read unambiguously wherever
their location doesn't already scope them: the endpoints, the entity/table/repository, the
ingest ports + adapters, InternalDeps.ValidationCriteria, Reads.ValidationCriteria, and the
runner files (validation-criterion-client, validation-criterion-listener). Symbols already
scoped by a validation package/dir, or whose peer fields are bare, were left bare to match their
neighbors.

Result

image

Testing (proof of execution)

All run locally on this branch:

  • Console (pnpm --filter @aep/console test): 37 files, 246 tests pass — includes new
    ValidationPage, validationCriterionStatus merge/split, and DeploymentsPage chip-routing cases.
  • Runner (remote-worker pnpm test): 68 tests pass — includes the criterion reporter and
    the Unix-socket criterion listener.
  • Go (aep-api): go build ./... + go vet clean; delivery, delivery/validation,
    delivery/task, edge, migrate packages all pass (migration step_order, method_origin
    ledger, and internal_surface goldens updated).

Docs

services/aep-api/internal/delivery/README.md updated (persistence line + ports table now carry the
validation-qualified names, plus the criteria-checklist invariant).

Notes

  • The aep-validation-runner:dev image bakes the runner TS; it needs a rebuild
    (make build-validation-runner FORCE=1) before deploy since this branch renamed baked runner files.

🤖 Generated with Claude Code

KCSAbeywickrama and others added 7 commits July 22, 2026 11:40
…ernal log page regardless of the validation state

Co-Authored-By: Claude <noreply@anthropic.com>
- Introduced CriterionReport type to represent acceptance criterion status.
- Added RunnerCriteriaReport handler to process incoming reports.
- Created RunnerCriteriaReportRequestObject and response types for handling requests and responses.
- Implemented database migration for criterion_statuses table to store validation criteria statuses.
- Updated migration steps to include criterion_statuses creation.
- Adjusted step order tests to reflect the new migration step.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
… validation consistency

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds durable per-criterion validation status storage and runner callback APIs. The remote worker reports Playwright criterion transitions through a Unix-socket listener, live progress events, and an optional platform callback. The API persists and serves criterion statuses. The console merges durable statuses with live events to render checklist state and progress, while completed deployment validation links now route to the internal validation log.

Sequence Diagram(s)

sequenceDiagram
  participant Playwright
  participant RemoteWorker
  participant AEPAPI
  participant DurableStore
  participant Console
  Playwright->>RemoteWorker: Emit criterion status
  RemoteWorker->>AEPAPI: POST validation criterion report
  AEPAPI->>DurableStore: Upsert latest criterion status
  Console->>AEPAPI: GET validation criteria
  AEPAPI-->>Console: Return durable status rows
  Console->>Console: Overlay live events and render checklist
Loading

Suggested reviewers: xlight05

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers the feature summary and testing, but it omits most required template sections. Fill in the required sections: Purpose, Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title is concise and accurately summarizes the main validation-log and per-criterion checklist changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@KCSAbeywickrama

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 6

🧹 Nitpick comments (1)
apps/console/src/features/tasks/lib/validationCriterionStatus.test.ts (1)

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

Add coverage for the skipped status.

skipped is a supported criterion state but is not asserted in the merge contract. Add a durable or live-frame case to prevent it from being filtered as unknown.

🤖 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 `@apps/console/src/features/tasks/lib/validationCriterionStatus.test.ts` around
lines 41 - 68, Extend the mergeCriterionStatus tests with a durable-row or
live-frame case using status “skipped,” and assert that the resulting criterion
map preserves that status. Keep the test focused on confirming skipped is
accepted rather than filtered as unknown.
🤖 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 `@apps/console/src/features/tasks/components/ValidationPage.tsx`:
- Line 137: Update the validation-state branch in ValidationPage to check the
raw timeline’s log.lines length instead of the derived logLines length, so
criterion-only activity is recognized as validation having started. Preserve the
existing behavior for genuinely empty timelines and the surrounding status
handling.

In `@packages/contracts/api/internal/v1/openapi.yaml`:
- Around line 246-265: Close the criterion status contract by adding the enum
values validating, passed, failed, and skipped to
ValidationCriterionReport.status in
packages/contracts/api/internal/v1/openapi.yaml; validate the same set in
criterion_ingest.go and return the established typed validation error for
invalid or blank values; update internal.go to map that validation error to
errBadRequest.

In `@packages/contracts/api/v1/openapi.yaml`:
- Around line 4393-4395: Define the criterion-status enum with validating,
passed, failed, and skipped in packages/contracts/api/v1/openapi.yaml at lines
4393-4395, and add the same enum to
packages/contracts/api/internal/v1/openapi.yaml; then regenerate
services/aep-api/internal/igen/igen_gen.go at lines 88-98 so the generated
artifact enforces these values.

In `@runners/remote-worker/src/lib/progress/validation-criterion-listener.ts`:
- Around line 60-90: Update the HTTP server setup in the criterion listener to
enforce a short request lifetime: configure a request timeout and destroy any
request that remains unfinished after the timeout, while preserving normal
completion and error handling. Ensure criterionListener.close() can finish even
when a client stops after sending only request headers.

In `@services/aep-api/internal/delivery/validation_criterion_status.go`:
- Around line 35-43: Include OrgID in the persisted conflict identity: update
validation_criterion_status.go so OrgID participates in the primary/unique key,
update repository_validation_criterion_status.go so the upsert conflict columns
include org_id, and extend repository_validation_criterion_status_dbtest_test.go
with a same repo/issue/criterion key across different orgs to verify both rows
persist independently.

In `@services/aep-api/internal/delivery/validation/criterion_ingest.go`:
- Around line 60-67: Require validation-task eligibility before persisting
criterion reports: update LookupExecutionTask in
services/aep-api/internal/app/validation_adapters.go:77-85 to verify and return
whether the execution is associated with a validation task, then update the
ingest flow in
services/aep-api/internal/delivery/validation/criterion_ingest.go:60-67 to
reject lookup results that are not eligible before calling UpsertCriterion.

---

Nitpick comments:
In `@apps/console/src/features/tasks/lib/validationCriterionStatus.test.ts`:
- Around line 41-68: Extend the mergeCriterionStatus tests with a durable-row or
live-frame case using status “skipped,” and assert that the resulting criterion
map preserves that status. Keep the test focused on confirming skipped is
accepted rather than filtered as unknown.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 47277e8a-4cb0-4ae6-806f-caa59b8772da

📥 Commits

Reviewing files that changed from the base of the PR and between a7b127a and f42900a.

⛔ Files ignored due to path filters (2)
  • services/aep-api/internal/gen/models_gen.go is excluded by !**/gen/**
  • services/aep-api/internal/gen/server_gen.go is excluded by !**/gen/**
📒 Files selected for processing (48)
  • apps/console/src/features/projects/components/DeploymentsPage.test.tsx
  • apps/console/src/features/projects/components/DeploymentsPage.tsx
  • apps/console/src/features/tasks/api/keys.ts
  • apps/console/src/features/tasks/api/queries.ts
  • apps/console/src/features/tasks/components/ValidationPage.test.tsx
  • apps/console/src/features/tasks/components/ValidationPage.tsx
  • apps/console/src/features/tasks/lib/validationCriterionStatus.test.ts
  • apps/console/src/features/tasks/lib/validationCriterionStatus.ts
  • apps/console/src/mocks/fixtures/project.ts
  • apps/console/src/mocks/handlers/project.ts
  • packages/contracts/api/internal/v1/openapi.yaml
  • packages/contracts/api/v1/openapi.yaml
  • packages/ui/validation-view/src/ValidationView.tsx
  • packages/ui/validation-view/src/index.ts
  • runners/remote-worker/package.json
  • runners/remote-worker/plugin/skills/aep-validation/references/criterion-reporter.cjs
  • runners/remote-worker/plugin/skills/aep-validation/references/playwright.config.template.ts
  • runners/remote-worker/src/lib/progress/criterion-reporter.test.ts
  • runners/remote-worker/src/lib/progress/schema.ts
  • runners/remote-worker/src/lib/progress/validation-criterion-client.ts
  • runners/remote-worker/src/lib/progress/validation-criterion-listener.test.ts
  • runners/remote-worker/src/lib/progress/validation-criterion-listener.ts
  • runners/remote-worker/src/lib/runner.ts
  • runners/remote-worker/src/oneshot.ts
  • services/aep-api/internal/app/app.go
  • services/aep-api/internal/app/validation_adapters.go
  • services/aep-api/internal/delivery/README.md
  • services/aep-api/internal/delivery/repository_validation_criterion_status.go
  • services/aep-api/internal/delivery/repository_validation_criterion_status_dbtest_test.go
  • services/aep-api/internal/delivery/task/handlers.go
  • services/aep-api/internal/delivery/task/ports.go
  • services/aep-api/internal/delivery/task/reads.go
  • services/aep-api/internal/delivery/task/reads_test.go
  • services/aep-api/internal/delivery/task/task_component_test.go
  • services/aep-api/internal/delivery/task/task_test.go
  • services/aep-api/internal/delivery/validation/criterion_ingest.go
  • services/aep-api/internal/delivery/validation/criterion_ingest_test.go
  • services/aep-api/internal/delivery/validation/ports.go
  • services/aep-api/internal/delivery/validation_criterion_status.go
  • services/aep-api/internal/edge/errors.go
  • services/aep-api/internal/edge/internal.go
  • services/aep-api/internal/edge/internal_surface_test.go
  • services/aep-api/internal/edge/internal_test.go
  • services/aep-api/internal/edge/method_origin_test.go
  • services/aep-api/internal/igen/igen_gen.go
  • services/aep-api/internal/migrate/run_all.go
  • services/aep-api/internal/migrate/step_order_test.go
  • services/aep-api/internal/migrate/validation_criterion_statuses.go

Comment thread apps/console/src/features/tasks/components/ValidationPage.tsx Outdated
Comment thread packages/contracts/api/internal/v1/openapi.yaml
Comment thread packages/contracts/api/v1/openapi.yaml
Comment thread services/aep-api/internal/delivery/validation_criterion_status.go
Comment thread services/aep-api/internal/delivery/validation/criterion_ingest.go
…ed structures

- Added ValidationCriterionReportStatus enum with values: Failed, Passed, Skipped, and Validating.
- Updated ValidationCriterionReport to use ValidationCriterionReportStatus for the Status field.
- Enhanced Valid method to validate known members of the ValidationCriterionReportStatus enum.
- Modified validation_criterion_statuses table schema to include org_id as part of the primary key, ensuring org-fenced data integrity.

Co-Authored-By: Claude <noreply@anthropic.com>
@KCSAbeywickrama

Copy link
Copy Markdown
Contributor Author

After an offline discussion with @xlight05, decided to design a generalized event system for all coding agents instead of a validation-specific event system.

@KCSAbeywickrama
KCSAbeywickrama marked this pull request as draft July 23, 2026 06:08
KCSAbeywickrama added a commit to KCSAbeywickrama/labs-agentic-engineer that referenced this pull request Aug 31, 2026
… does it

A validation cycle is dispatched with a 7200s deadline, and until it merges its
pull request and the platform reads report.json at that merge commit, nothing on
the page can say anything about any individual criterion. wso2#669 put a row per
criterion on screen; every one of them read "Pending" for up to two hours. Worse
on a repeat attempt, which carries the previous attempt's report and so froze the
failed rows on last attempt's verdict for the whole run that was re-working them.

Rows now carry what the run is doing to them — planned, exploring, authoring,
running, healing — settling on report.json's own pass/fail. A tenth progress kind
carries it: progress_item, with an itemId to fold on and a status, which is one
new field on the wire. Every other kind reports something that happened once at a
moment, and none of them carries an identity a consumer can repaint a row by.

Inferred from the agent's own calls, never declared by it. A PreToolUse hook
reads statuses off work the run has to do anyway: the test plan it commits, the
spec file it writes, the command it runs. An instruction to report progress is
one an agent can skip with nobody noticing for a whole run.

PR wso2#307 built this and was closed for being validation-specific, so the kind is
generic — cycleKind already says whether the ids are criteria or issues, which is
genericity without a field nobody uses yet. wso2#307's Unix socket is gone too, and
needed no workflow change to remove: authoring.md already requires every spec to
pass alone and twice consecutively, and healing.md re-runs the same shape, so the
criterion id is already on commands the hook sees.

Making the RUN step itself per-spec was tried and reverted. The suite runs
serially against one shared environment, so a spec that passes alone can fail in
sequence — healing.md's data-collision class — and isolated runs cannot find it.
Replacing the integrated run with them moved that signal past the heal budget,
where it lands in report.json as a genuine failure and mints a repair issue
against code that is not broken.

The config now picks its reporters from whether the command names a spec, so the
per-spec runs in steps 6 and 8 write no results.json and cannot leave a one-spec
file where the report expects the suite — which only call ordering hid before.
Inferred from the command rather than a flag, because naming a spec is how you
run one spec and there is nothing to remember; the cost is that a sharded
authoritative run writes nothing, so the skill forbids sharding that one call.
generate-report.mjs is untouched. The config is also re-copied every run now
rather than skipped when present, or a repo scaffolded earlier keeps the old
behaviour silently.

Exploration is the longest unobservable stretch and playwright-cli calls name
URLs, never criteria — so the skill writes a spec's mandatory `// spec:` header
before exploring rather than after. Not a new artifact, only a different moment
for a line that was already required. Newly created specs only: blanking an
existing one reads as a pre-existing spec modified with no heal-log entry, which
the report generator fails the run for.

healing fires only for a criterion that passed and then broke, which is what
healing.md scopes a heal to. authoring.md requires a spec to pass twice
consecutively, so failing on the way to a first pass is the normal path and
reporting it as healing would make a healthy run read as a struggling one.

Live statuses outrank the report while a cycle is open, and the report wins again
the moment it settles — which also fixes the unreported re-dispatch, where the
page fetched no report and rows rendered no chip at all. The one run-wide line
above the rows is derived FROM the rows, so it cannot contradict them, and speaks
only in the two windows where they say nothing: before any criterion is picked up
and after they have all settled. No durable store — the stream replays the cycle
log on reconnect and the archive after the pod is reaped.

Renderers format progress_item to no text, as activity already does. A criterion
moving through five statuses would otherwise be five log rows narrating what the
row above already shows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
KCSAbeywickrama added a commit to KCSAbeywickrama/labs-agentic-engineer that referenced this pull request Aug 31, 2026
… does it

A validation cycle is dispatched with a 7200s deadline, and until it merges its
pull request and the platform reads report.json at that merge commit, nothing on
the page can say anything about any individual criterion. wso2#669 put a row per
criterion on screen; every one of them read "Pending" for up to two hours. Worse
on a repeat attempt, which carries the previous attempt's report and so froze the
failed rows on last attempt's verdict for the whole run that was re-working them.

Rows now carry what the run is doing to them — planned, exploring, authoring,
running, healing — settling on report.json's own pass/fail. A tenth progress kind
carries it: progress_item, with an itemId to fold on and a status, which is one
new field on the wire. Every other kind reports something that happened once at a
moment, and none of them carries an identity a consumer can repaint a row by.

Inferred from the agent's own calls, never declared by it. A PreToolUse hook
reads statuses off work the run has to do anyway: the test plan it commits, the
spec file it writes, the command it runs. An instruction to report progress is
one an agent can skip with nobody noticing for a whole run.

PR wso2#307 built this and was closed for being validation-specific, so the kind is
generic — cycleKind already says whether the ids are criteria or issues, which is
genericity without a field nobody uses yet. wso2#307's Unix socket is gone too, and
needed no workflow change to remove: authoring.md already requires every spec to
pass alone and twice consecutively, and healing.md re-runs the same shape, so the
criterion id is already on commands the hook sees.

Making the RUN step itself per-spec was tried and reverted. The suite runs
serially against one shared environment, so a spec that passes alone can fail in
sequence — healing.md's data-collision class — and isolated runs cannot find it.
Replacing the integrated run with them moved that signal past the heal budget,
where it lands in report.json as a genuine failure and mints a repair issue
against code that is not broken.

The config now picks its reporters from whether the command names a spec, so the
per-spec runs in steps 6 and 8 write no results.json and cannot leave a one-spec
file where the report expects the suite — which only call ordering hid before.
Inferred from the command rather than a flag, because naming a spec is how you
run one spec and there is nothing to remember; the cost is that a sharded
authoritative run writes nothing, so the skill forbids sharding that one call.
generate-report.mjs is untouched. The config is also re-copied every run now
rather than skipped when present, or a repo scaffolded earlier keeps the old
behaviour silently.

Exploration is the longest unobservable stretch and playwright-cli calls name
URLs, never criteria — so the skill writes a spec's mandatory `// spec:` header
before exploring rather than after. Not a new artifact, only a different moment
for a line that was already required. Newly created specs only: blanking an
existing one reads as a pre-existing spec modified with no heal-log entry, which
the report generator fails the run for.

healing fires only for a criterion that passed and then broke, which is what
healing.md scopes a heal to. authoring.md requires a spec to pass twice
consecutively, so failing on the way to a first pass is the normal path and
reporting it as healing would make a healthy run read as a struggling one.

Live statuses outrank the report while a cycle is open, and the report wins again
the moment it settles — which also fixes the unreported re-dispatch, where the
page fetched no report and rows rendered no chip at all. The one run-wide line
above the rows is derived FROM the rows, so it cannot contradict them, and speaks
only in the two windows where they say nothing: before any criterion is picked up
and after they have all settled. No durable store — the stream replays the cycle
log on reconnect and the archive after the pod is reaped.

Renderers format progress_item to no text, as activity already does. A criterion
moving through five statuses would otherwise be five log rows narrating what the
row above already shows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants