Skip to content

fix(audit): judge resolved behaviour, not syntax, in four audit rules - #314

Open
candidate2-vapi wants to merge 1 commit into
PostHog:mainfrom
candidate2-vapi:fix/audit-rule-precision
Open

fix(audit): judge resolved behaviour, not syntax, in four audit rules#314
candidate2-vapi wants to merge 1 commit into
PostHog:mainfrom
candidate2-vapi:fix/audit-rule-precision

Conversation

@candidate2-vapi

Copy link
Copy Markdown

What

Four audit rules judged syntax where they should have judged resolved behaviour. I ran the audit against a large TypeScript monorepo (Vite + React dashboard, Node backend, both SDKs) and three of its five findings were either false or materially overstated. This tightens the four rules and fixes the reporting bug that inflated them.

Markdown only — no check ids, areas, or labels change, so there's no wizard PR. AUDIT_SEED_CHECKS in the wizard stays as-is.

The reporting bug is the root cause of the inflated language

5-report.md tells the agent, twice, to use the "canonical why it matters copy" and the "Canonical area copy" verbatim. Neither block exists anywhere in the repo — I grepped context/ and dist/. So the agent is instructed to copy text that isn't there, and it does the only thing it can: invents a plausible harm narrative. That's where "an unbounded number of distinct event definitions… can also be rate-limited" and "corrupting person counts, funnel attribution, and retention cohorts" came from on a codebase where neither was true.

Both blocks are now defined — one line per check id, one paragraph per area — plus a proportionality rule: when the subagent's details describe a bounded or already-mitigated problem, describe what was found rather than substituting the generic worst case.

The four rules

capture-event-names-static said error: any call uses a template literal or variable as the event name. Two problems. A reference to a constant registry (POSTHOG_EVENTS.BILLING.UPGRADED) is a variable by that wording, so the pattern we actually recommend graded as an error. And a ternary over two string literals got the same severity and the same "unbounded" narrative as `${action}_clicked`, even though its name count is fixed and enumerable. The rule now resolves the first argument before judging: constant references pass, a bounded ternary is a warning framed on its real costs (the literals aren't greppable from the PostHog UI, and a dimension folded into the name belongs in a property), and only genuine runtime interpolation is an error.

capture-growth-events grepped posthog\.capture\( and nothing else. Signup and purchase are usually emitted from backend or worker code through an in-house wrapper — on the repo I tested, 'user created' fires from a Temporal activity and '[subscription] plan purchased' from a billing function, both via a wrapper that forwards to PostHog. The check reported all three growth events missing; only one actually was. It now also greps the server-SDK object form, and must follow analytics wrappers and any central event-name registry before declaring an event absent. It also has to state where it looked, so the claim is auditable.

cross-runtime-distinct-id read the distinct_id expression but not the properties object next to it. A random-UUID fallback accompanied by $process_person_profile: false — PostHog's documented mechanism for exactly this — creates no person profile and so cannot corrupt person records. The rule now requires reading the properties, caps that case at warning, and prefers recommending an already-in-scope stable id (org/account/tenant) over the previous advice to drop the capture entirely.

identify-reset-on-logout correctly found a missing reset(), but said nothing about where it may safely go — and the tempting answer is dangerous. The single shared sign-out helper is usually also invoked when no session existed (initial load, or an auth listener firing for anonymous visitors). reset() there mints a new anonymous ID on every visit, detaching pre-signup pageviews from the account that follows and inflating unique-visitor counts — a worse problem than the merge being fixed. The rule now requires tracing the helper's callers, defines a safe site as a real identity transition (explicit sign-out, or a branch guarded by an actual sign-out event), and requires enumerating every sign-out path so forced sign-outs routed through a listener aren't missed.

Testing

npm test — 139 passed, 18 files. npm run build — clean, 8 plugins / 121 skills.

I unzipped dist/skills/audit.zip and confirmed the new guidance ships in references/3-identification.md, 4-event-capture.md, and 5-report.md, and that all four in-file pointers to the canonical blocks now resolve to real sections.

Not verified: I haven't re-run the wizard end-to-end against a live project to watch the graded output change, so the claim that these rules produce the right verdicts rests on reading them against the codebase that produced the wrong ones, not on a fresh audit run. Worth one live run against a repo with a constant-registry capture pattern and server-side growth events before release — that combination triggers three of the four fixes at once.

Four rules judged syntax rather than resolved behaviour, which produced
false positives and overstated harm on a real codebase:

- capture-event-names-static flagged any non-literal first argument, so a
  constant-registry reference (the recommended pattern) was an error, and
  a ternary over two literals was reported as unbounded event definitions.
  The rule now resolves the name to its possible values first.
- capture-growth-events only grepped the browser SDK, so signup and
  purchase events emitted server-side or behind an in-house wrapper were
  reported as missing entirely.
- cross-runtime-distinct-id ignored $process_person_profile, so a fallback
  that deliberately opts out of person profiles was reported as corrupting
  person counts.
- identify-reset-on-logout gave no guidance on where reset() may safely
  go, and the obvious choke point is often a shared helper that also runs
  for anonymous visitors, where reset() breaks pre-signup attribution.

5-report.md also told the agent to use "canonical why it matters copy"
and "Canonical area copy" verbatim, but neither block existed anywhere in
the repo. With nothing to anchor to the agent invented the harm narrative,
which is where the inflated claims above surfaced in the report. Both
blocks are now defined, with a proportionality rule that keeps the claim
matched to what the check actually found.
@candidate2-vapi
candidate2-vapi requested a review from a team as a code owner August 4, 2026 18:54
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