Skip to content

feat(audit): stream audit progress as areas on the headless path - #1263

Merged
gewenyu99 merged 3 commits into
mainfrom
posthog/audit-area-task-stream
Sep 18, 2026
Merged

gewenyu99 merged 3 commits into
mainfrom
posthog/audit-area-task-stream

Conversation

@gewenyu99

@gewenyu99 gewenyu99 commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Audit runs publish live state that nothing could see. This makes the run legible — in the terminal, in a local dump, and on the wire — and fixes the two defects that made the ledger inert.

What was wrong

  • The wire carried nothing useful. TaskStreamUpdate.tasks holds the program's step rows, which a headless or CI run does not have. A five-minute audit pushed tasks: [].
  • Shipping every check would be noise, and each check row carries a file path and a details string derived from the user's code.
  • The pi harness had no ledger tools at all. The three audit_* tools existed only on the MCP facade, and pi mounts no MCP server. The switchboard binds audit to harness=pi, so the agent searched, found nothing, and completed the audit without resolving a single check. The Audit plan tab sat at 13 pending rows for the whole run and the report was composed from the agent's own notes instead of the ledger it documents as its source of truth.
  • The seed was missing a row the skill resolves. audit_resolve_checks rejects a batch atomically, so the absent init-not-duplicated discarded init-correct with it and cost a retry.

What this does

Area rollup. rollUpAuditAreas folds the ledger into one task row per audit area, in seed order, and those rows extend the program rows in tasks while continuing their integer ids. No wire-schema change, so no backend change. A finding is progress, not a failure: error, warning, and suggestion all resolve an area to completed, and an area never reports failed. Check labels, file, and details never leave the machine.

One ledger reader. startAuditLedgerWatcher mirrors .posthog-audit-checks.json into the session through getUI().setFrameworkContext, and runAgent owns the lifecycle, so the interactive runner, the headless runner, and the e2e host all get it. It starts before the run recipe resolves, because an audit seeds the ledger from inside run(). HeadlessUI now tees that setter into the store; without it a headless run mirrors nothing.

pi ledger tools. Native mirrors of audit_seed_checks / audit_add_checks / audit_resolve_checks, opt-in per program like the skill-menu pair. Descriptions, write semantics, and error strings moved into shared helpers so the two facades cannot drift — mcp.ts got 105 lines lighter. They register as parallel behind the ledger mutex, which is what lets the skill fan checks out across subagents without losing an update.

Destinations follow the surface.

mode PostHog local dump
interactive TUI telemetry on --task-stream-log
headless telemetry on --task-stream-log
--ci never always, /tmp/posthog-wizard-task-stream.jsonl

A CI run is synthetic, so a push would create a session row in a real project. The dump records every attempted sync, one JSON line per push, truncated per run. Dev and CI builds only: createFileDestination returns null in a published build and the flag is undeclared there. Telemetry consent now governs the PostHog destination only, so --no-telemetry with a dump still logs.

Harness sampling. tui-snapshots signed framework-context by key, so a screen that rerenders from an artifact updated in place was captured once, empty. It digests the values now.

Verified on a real run

PROGRAM=audit through the e2e host against a Next.js app with PostHog already installed, project 228144, real gateway:

  • ledger on disk: 13 rows, 7 pass / 3 error / 2 warning / 1 suggestion, nothing pending
  • 28 ledger tool calls, 0 errors (was: unknown check id(s): init-not-duplicated)
  • 58 attempted syncs, every area stepping pending → in_progress → completed
  • grep -c '"details"\|"file"' → 0
  • 14 snapshots, up from 7, with the checks pane showing Installation (4/4) and per-check glyphs
  • runPhase: completed, notebook uploaded, skills cleaned up

pnpm test — 174 files, 2804 tests. 20 new tests cover the rollup, the destination, the pi tools (seed/resolve/append, unknown-id refusal, duplicate refusal, concurrent fan-out), the per-program gate, and the seed invariant.

Known gap, outside this repo

The app subscribes per workflow_id, and wizardActiveSessionDetectorLogic watches posthog-integration plus conditionally self-driving. An audit session is stored and streamed correctly — writes are confirmed 201/200, no allowlist, flag, or team gate drops it, and task titles render generically — but no surface watches that channel. Making it visible is two lines in posthog: an AUDIT_WORKFLOW_ID constant in wizard-sync/workflows.ts and that id in the detector's base watched set.

Separately, the narrow audit leaves push workflow_id: 'agent-skill', because that is the config id behind wizard audit events. Only audit all pushes audit.


Created with PostHog Desktop

@gewenyu99
gewenyu99 requested a review from a team as a code owner September 18, 2026 15:12
@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci mcp-analytics
  • /wizard-ci replay-vision
  • /wizard-ci revenue
  • /wizard-ci self-driving
  • /wizard-ci warehouse
  • /wizard-ci warehouse-seeded

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/google-adk
  • /wizard-ci ai-observability/groq
Show more apps
  • /wizard-ci ai-observability/manual-capture
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/opentelemetry
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/flutter
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci replay-vision/javascript-node
  • /wizard-ci replay-vision/next-js
  • /wizard-ci replay-vision/react-vite
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit
  • /wizard-ci warehouse/monorepo-env
  • /wizard-ci warehouse/multi-source-next
  • /wizard-ci warehouse/stripe-node
  • /wizard-ci warehouse/zero-source
  • /wizard-ci warehouse-seeded/next-stripe
  • /wizard-ci warehouse-seeded/next-stripe-declined

Test against a Context Mill branch:

  • /wizard-ci all context-mill:my-branch

Add context-mill:<branch> to any command above to pin the Context Mill branch. It defaults to main.

Results will be posted here when complete.

@gewenyu99
gewenyu99 marked this pull request as draft September 18, 2026 15:17
Comment thread src/lib/task-stream/audit-areas.ts Outdated
if (typeof value !== 'string') return null;
const trimmed = value.trim();
if (trimmed === '') return null;
return trimmed.slice(0, MAX_AREA_TITLE_LENGTH);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Low: Agent-controlled content is sent without security scanning

A malicious repository or downloaded skill can instruct the agent to place readable source or configuration content in a check's area; this function only truncates that value, and TaskStreamPush then sends it to the PostHog sessions endpoint. Audit MCP arguments are not covered by the Write/Edit or publish_handoff output scans. Restrict streamed titles to trusted, predefined area identifiers, or apply the same output scanning and redaction used for other agent-authored outbound content.

@veria-ai

veria-ai Bot commented Sep 18, 2026

Copy link
Copy Markdown

PR overview

This pull request changes audit task progress streaming to report audit areas rather than individual checks. The implementation updates the audit-area task stream payloads sent during an audit.

One issue remains open: agent-controlled audit area text can be transmitted to the session analytics endpoint without content scanning or redaction. A malicious repository or downloaded skill could use this path to expose readable source or configuration data, although exploitation depends on untrusted instructions influencing the agent. No issues have yet been addressed.

Open issues (1)

Fixed/addressed: 0 · PR risk: 4/10

@gewenyu99 gewenyu99 changed the title feat(audit): stream audit progress as areas, not checks feat(audit): stream audit progress as areas on the headless path Sep 18, 2026
An audit resolved tens of ledger checks and none of it left the machine:
`tasks` holds the program's step rows, which a headless or CI run does not
have, so a five-minute audit pushed `tasks: []`. Shipping every check would
be noise, and each row carries a file path and a details string derived from
the user's code.

- `rollUpAuditAreas` folds the ledger into one row per audit area, appended
  after the program rows. No wire-schema change, so no backend change. A
  finding is progress, not a failure, so an area never reports `failed`.
- `startAuditLedgerWatcher` mirrors the ledger into the session through the
  UI's frameworkContext seam, owned by `runAgent` so every path gets it, and
  started before the run recipe seeds the ledger. `HeadlessUI` tees that
  setter into the store, without which a headless run mirrors nothing.
- pi gets native mirrors of the three `audit_*` tools. They existed only on
  the MCP facade, and pi mounts no MCP server, so an audit on the harness the
  switchboard binds it to could not move a check off pending. Descriptions
  and write semantics move to shared helpers so the facades cannot drift.
- `--task-stream-log` dumps every attempted sync as JSONL. `--ci` always
  dumps and never pushes, since a synthetic run would create a session row in
  a real project. Consent now gates the push, not the dump.
- Seeds `init-not-duplicated`, which the skill resolves in a batch with
  `init-correct`; a batch resolve rejects atomically, so its absence
  discarded both.
- The audit program declares an e2e profile, and the snapshot harness signs
  framework-context by value so a screen rerendering from the ledger is
  captured more than once.

Generated-By: PostHog Desktop
Task-Id: 6490a5fd-5ee9-4203-b9c4-0b4ff180c45b

@edwinyjlim edwinyjlim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@gewenyu99
gewenyu99 enabled auto-merge (squash) September 18, 2026 19:44
@gewenyu99
gewenyu99 merged commit b300b40 into main Sep 18, 2026
17 checks passed
@gewenyu99
gewenyu99 deleted the posthog/audit-area-task-stream branch September 18, 2026 19:46
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