fix(audit): judge resolved behaviour, not syntax, in four audit rules - #314
Open
candidate2-vapi wants to merge 1 commit into
Open
fix(audit): judge resolved behaviour, not syntax, in four audit rules#314candidate2-vapi wants to merge 1 commit into
candidate2-vapi wants to merge 1 commit into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Four
auditrules 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_CHECKSin the wizard stays as-is.The reporting bug is the root cause of the inflated language
5-report.mdtells 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 greppedcontext/anddist/. 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
detailsdescribe a bounded or already-mitigated problem, describe what was found rather than substituting the generic worst case.The four rules
capture-event-names-staticsaiderror: 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 awarningframed 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 anerror.capture-growth-eventsgreppedposthog\.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-idread thedistinct_idexpression 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 atwarning, and prefers recommending an already-in-scope stable id (org/account/tenant) over the previous advice to drop the capture entirely.identify-reset-on-logoutcorrectly found a missingreset(), 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.zipand confirmed the new guidance ships inreferences/3-identification.md,4-event-capture.md, and5-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.