Generate logs (open a PR)
A repository scan only reports which log statements are missing. The generator is the step that writes them: it instruments the scan’s checkout, build-checks the project, pushes a new branch and opens a pull request against the branch that was scanned. In the app this is the Generate button on a scan result (Generator page).
Scan profiles and logging rules (2026-10-15)
Section titled “Scan profiles and logging rules (2026-10-15)”The rules a scan and its generation apply come from two policy kinds in
Rules & Policies, feature code:
- Scan profiles (
scan_profile) — what used to be a project’s template (project_analyzer_rules_config): one per project and framework, holding the catalogue rules (strategyName,canonicalName,categoryName,priority,loggingLevel, enabled) andsettings.generationOptions.templateId/templates[]on scans now carry scan-profile policy ids, andlog_scan_result.template_idreferencespolicy(id). Configure Rules (“Save as profile”, “Update Rules”) writes them through/api/v1/policies; choosing a framework without a profile in the Initiate Scan modal callsPOST /api/v1/policies/scan-profiles/ensure. - Logging rules (
logging_rules) — the plain-English “Your own rules” (formerlycustom_log_rule//api/v1/custom-rules). Eachcustom_logitem is compiled on save (LoggingRuleCompiler; the validator now sendsX-Analyzer-Token, so validation actually runs); only enabled, compiledACTIVEitems of policies that are On (enforcing) and apply to the project run. They now run on every scan path — interactive scans, IDE analysis and Gatekeeper pull-request scans — not just interactive scans, so Gatekeeper outcomes can shift once a workspace has logging rules.
Logging rules and scan profiles: fixes and open gaps (2026-10-21)
Section titled “Logging rules and scan profiles: fixes and open gaps (2026-10-21)”The gaps were measured on dev on 21 October 2026 in three live runs:
- a custom rule on
spring-guides/tut-rest(scans 1451–1452); - an owner-run scan of
Deployd-io/BankJava_TestRepo(scan 1455, PR #197); - a rules-only test on
Java-Techie-jt/spring-boot-crud-example(scans 1456–1470).
The fixes were deployed to dev the same day: analyzer f31632b/bd70de0/5290c50, backend
9cdf59d7…e6b327c0, frontend d0577d1a/f09ae787. They were re-tested live with the same
repository and rule (scans 1471–1479): 35 cases, 29 pass, and all 20 API regression cases pass.
Prod is not promoted yet.
“Custom rules only” scans
Section titled ““Custom rules only” scans”- A project can have a built-in scan profile “Custom rules only” (framework
custom_only) with zero catalogue rules. Create it withPOST /api/v1/policies/scan-profiles/ensure {projectId, framework: "custom_only"}; the call is idempotent. The scan profile picker offers it when the project has a logging rule that can run. - A scan with it runs only the workspace’s logging rules for that project.
- The result is labelled
appliedTemplates ["Custom rules only"]andrun_details.scanMode "custom_only", and its key findings are written from the numbers, not by the model. The same label applies to any profile whose catalogue rules are all switched off. - When neither the profile nor any logging rule has something to run, the scan is refused before
it starts: 400
errorCode: no_rules_to_run, “Scan profile ‘Custom rules only’ runs only your logging rules, and no logging rule is On and active for …”. No scan row is created. - The orchestrator and MCP accept
custom_only(alsocustom-rulesor “Custom rules only”) as a template name.
What a scan reports for each logging rule
Section titled “What a scan reports for each logging rule”When a scan ran any logging rule, the result carries customRules (LogScanResultDTO.customRules,
run_details.customRules, also on IDE responses). Each entry has:
ruleId,titleandmatchedMethods;generated,shipped,mergedanddropped(alwaysgenerated = shipped + merged + dropped);byCause, andcaptures: per named value, how many methods resolved it and where it was not found.
The scan result screen shows this as Your logging rules, e.g. “Product deletion audit:
matched 8 methods · 8 written inside another rule’s line · 0 could not be placed”. Scans without
logging rules are unchanged: they have no customRules field.
- When a template statement already holds a short method’s slots, the rule’s statement is merged into the neighbouring template line (entry preferred), never silently dropped. Custom rule types now rank right after the entry log.
Off scan profiles
Section titled “Off scan profiles”- A scan with a profile set Off is refused: 409
errorCode: scan_profile_off, “Scan profile ‘General Logging’ is Off, so its rules don’t run. Turn it on, or pick another profile.”, withprofileIdandprofileName. This applies to interactive, orchestrator and MCP scans, including a profile listed intemplateIds. - Monitoring setup skips Off profiles. Project responses carry each profile’s
status, and the picker shows an Off profile disabled with Turn on (Developer and above). - IDE and Gatekeeper scans follow the backend flag
scan.profiles.honourOff(envSCAN_PROFILES_HONOUROFF). The default iswarn, which runs the scan and addswarnings: ["scan_profile_off"](Gatekeeper also storesrun_details.profileOff=true).enforcerefuses. On 21 October dev had 0 Off scan profiles out of 723 and prod 0 out of 51.
Values a rule names, and secret-bearing objects
Section titled “Values a rule names, and secret-bearing objects”- A rule’s named value (
capture, e.g. “with the product id”) is written into its statement when it can be resolved in the method:- from a parameter of that name;
- from an accessor of a parameter whose type the backend described;
- through the alias
<noun>Id→id. For example:log.warn("CUSTOM-AUDIT: ENTERING: deleteProduct() method: productId={}", id);. This is Java only for now; other languages reportcapture-unsupported-language.
- The backend indexes the repository’s types that hold a secret-named field (password, pin, otp,
token, secret, …) and sends them to the analyzer as
type_hints. Such an object is then logged by its safe fields, e.g."[username=" + request.getUsername() + "]", never whole throughtoString(). This applies to every hole of the statement, including the new value of a variable change (user → {}) and a field (this.login); with no safe field the hole is<redacted>. - Only plain-value fields count as safe: primitives and their boxes, strings, UUIDs, numbers,
dates and enums. Collections, maps, arrays,
Optional, JPA associations (@OneToMany,@ManyToMany,@ManyToOne,@OneToOne,@ElementCollection,@Embedded,@Lob) and other classes are never expanded, so a log line cannot load a lazy relation (backende2ef2fef). - A value whose name ends in
jwt(jwt,signedJwt,raw_jwt) is redacted like a token;jwtProviderandjwtConfigare not.
Status of each gap
Section titled “Status of each gap”| Id | Severity | Gap | Status (re-tests 2026-10-21 and 2026-10-23) |
|---|---|---|---|
| RO-F2 | High | No “custom rules only” scan: a scan profile was mandatory and logging rules were only added to it. | Fixed. Scan 1471: 8 rows, all from the rule, labelled “Custom rules only”. With the rule Off: 400 no_rules_to_run. |
| RO-F1 | High | A template silently displaced a custom rule’s statements. The line coordinator ranked custom types last (priority 50), and the Java final placement guard then removed them as over-subscribed, before the generated count. | Fixed (Java). General: 0 of 8 kept → 8 generated, 8 merged, 0 dropped. Entry and exit only: 2 of 8 → 2 shipped, 6 merged. SOC 2: 2 of 8 → 7 shipped, 1 merged. Every outcome is reported in customRules. On other languages the merge does not exist yet: losses still happen (JavaScript 1, Lua 2 in the measurement corpus) but are now reported. |
| RO-F5 / CS-F1 | High | The value a rule names was not logged. | Fixed (Java). productId={} is in the line. Since 2026-10-23 (backend 1a807861) an accessor on a parameter whose type holds no secret works too: when a rule names a value, type_hints carry every type’s plain fields, and updateProduct(Product product) logs product.getId() (re-test FX4-2). |
| CS-F12 | High | Generated logs printed passwords and PINs through whole request objects. | Fixed. Login and register requests and the User entity are logged by safe fields (scan 1479, 56 statements). The “variable change” gap (FX-F3) is fixed too: re-test scan 1480 passes no secret-bearing object whole in any hole (5 → 0). |
| RO-F3 | Medium | A scan profile set Off still ran. | Fixed for interactive, orchestrator and MCP scans (409 scan_profile_off). IDE and Gatekeeper warn by default. The orchestrator/MCP path could not be exercised live from a fresh workspace (RO-F6). |
| FX-F1 | High | New. A local named jwt holding a fresh access token is logged in clear (login(request={}): jwt → {}). Redaction knows token, accessToken and authToken but not jwt. | Fixed (analyzer 7c7ac36). Scan 1480: jwt → <redacted>; secret-named values logged in clear across the scan 13 → 0. |
| FX-F2 | Medium | New. Every expansion of User includes user.getTeams(), a lazy @ManyToMany: it runs even with DEBUG off and can throw LazyInitializationException or load the whole association. | Fixed on dev (backend e2ef2fef, deployed 22 Oct once the security gate passed). With the new hint shape the analyzer expands no association (getTeams() 5 of 5 → 0 of 5). |
| FX-F3 | High | New. The new value of a variable change (user → {}, this.login → {}) passed the whole object. | Fixed (analyzer 7665ebe). |
| FX2-F1 | Low | New. Names that only contain a secret word are over-redacted: tokenType, tokenProvider, passwordEncoder. Nothing is exposed; harmless detail is lost. | Fixed on dev 2026-10-23 (analyzer f65c918, backend 2fe12b0d). A name ending in provider, filter, encoder, service, type, expiration, length and similar is not a credential name. config, properties, store, header, hash and key are left off on purpose. taskMaster: name-only redactions 0; tokenProvider stays redacted because its type JwtTokenProvider holds jwtSecret (the secret-bearing-object rule, by design). |
| OTP | Low | New (2026-10-23). A request record whose NAME holds a secret word was printed as <redacted> although its type holds no secret or holds one beside safe fields: BankingPortal verifyOtpAndLogin(otpVerificationRequest=<redacted>) and generateOtp(otpRequest=<redacted>), while login(loginRequest=…) printed [identifier=…]. | Fixed on dev 2026-10-23 (analyzer ff874a3, b0f5297; backend 488b4bf1). The safe-field rewrite is no longer undone by the name redactor, and a type named like a secret is sent as a type hint even when it holds none. Live scan: 10 → 0 redacted request objects, .otp() never read, raw otp values still <redacted>. Without a type hint the name still decides. |
| CS-F2 | High | A rule can show “Running” and match nothing: the compiler guesses method names from the English and never checks the project’s code. | Fixed on dev (backend 4f1cb4df, a91a625c, d77b991a, 62af493c). A rule scoped to one project is checked against its latest scan: when no named method exists the rule is a DRAFT naming it (“Did you mean deleteProduct?” for a typo); invented names beside real ones are left out with a note; a missing file is always a DRAFT. A name the author typed is always kept. Check rule shows how many methods the rule reaches. Workspace-wide rules are not checked. |
| RO-F4 / CS-F3 | Medium | Targeting is OR, not AND: naming a method and a class matches either. | Fixed on dev (analyzer 2a16e0f, in 06b7a83). Re-test 2026-10-22: “deleteProduct in ProductService” wrote 8 lines before (scan 1510) and 1 after (scan 1521). Prod not promoted. |
| D2 | High | New (2026-10-22). A Java method whose throws clause wraps onto its own line was not detected, so no rule or template could reach it (BankingPortal login, verifyOtpAndLogin). | Fixed on dev (analyzer 06b7a83). Sign-in rule 22 → 73 on the BankingPortal matrix. Prod not promoted. |
| D3 | Medium | New (2026-10-22). 9 of 9 lines reported “could not be placed, reason unrecorded” were in Java record headers (FundTransferRequest, LoginRequest, OtpRequest): matched by a fragment, then dropped by the Java placement guard, whose drops never reached the rule’s report. | Fixed in code (analyzer 19381fb, ed039d4), on dev since 22 Oct and verified in the live re-run (101 cases, 92 pass): every drop names its cause, and an empty record header is no longer a rule target. |
| D5 | Medium | New (2026-10-22). A matched method that already writes a log gets no rule line, and nothing is reported dropped: the generic fallback only writes for a method with no logging, exception handling or database work (logout, both generateToken overloads). | Fixed in code (analyzer 00d2f8c), on dev since 22 Oct and verified in the live re-run (101 cases, 92 pass): a custom rule ignores existing logging that does not carry its label. |
| D6 | High | New (2026-10-22). The SOC 2 authentication template (soc2Access.ts, “Authentication operation completed for user ”) passes user whole; on a Lombok @Data entity that prints the password hash. Seen on BankingPortal login, verifyOtpAndLogin and verifyOtpAndIssueResetToken through sign-in and OTP rules. The same lines, where user is a parameter, printed user.getAccount() — Account.toString() prints the PIN. | Fixed in code (analyzer 6b917e2: an untyped local whose type holds a secret prints its safe fields when made with new T(…), else <redacted> when its name is that type — a guessed type never becomes a getter; the nested account half is backend FX-F2 e2ef2fef), on dev since 22 Oct. Live matrix run 4 (all 24 wordings recompiled): 0 leaking lines in every scenario. Offline matrix run 3 at analyzer main (calibrated 24/24 line-for-line against the live run 2 scans): 0 leaking lines in every scenario, detailed rules 89 → 96. |
| CS-F5 | Medium | Generation starts even when it cannot push, and fails after about 8.5 minutes with NO_TOKEN. | Fixed on dev 2026-10-23 (backend 20d19079). POST /api/v1/generator asks push readiness first and answers 409 push_not_possible with the reason and connectOptions; nothing is queued. |
| RO-F6 | Medium | /scan/initiate (orchestrator and MCP) cannot scan a public repository without a GitHub credential. | Fixed on dev 2026-10-23 (backend e686c439). With no user, forwarded or App token, one anonymous ls-remote (10 s) decides: a public repository is scanned with no token; a private one still answers no_user_token. Re-test: fresh workspace, run COMPLETED. |
| CR-F2 | High | New (2026-10-22). “Do not log getProducts” compiles to kind: leak, but the generator never reads kind, so the scan adds a CUSTOM-AUDIT line to getProducts (scans 1511, 1526). | Fixed. Analyzer bf1b48c (on dev) never writes a line for a leak rule; backend 4f1cb4df (on dev since 22 Oct and verified in the live re-run (101 cases, 92 pass)) refuses one at save: “Logging rules add log lines, so they can’t stop something from being logged.” |
| CR-F3 | High | New. “Log the user’s password and credit card number…” compiles ACTIVE with capture: [password, creditCardNumber]. Capture names are not checked against the secret-word list. | Fixed on dev (backend 8b521e44). A secret value is left out of the rule with a note (“password and creditCardNumber were left out: logging rules never record secrets.”); the first version refused the whole rule and also refused values the model volunteered (“Audit tokens” → token). Names that describe a secret (otpId, tokenId) are kept. |
| CR-F4 | Medium | New. A rule that targets an annotation (@PostMapping, @Transactional, …) is always DRAFT: the dry-run puts the annotation above the probe method, but the Java method text starts at the signature, so annotationPatterns never match. | Fixed on dev (analyzer bf1b48c): annotation patterns also read the annotation lines above the method. |
| CR-F6 | Medium | New. The title steered the model: the same misspelled text compiled to deleteProduct under one title and to delteProdcut (ACTIVE, matches nothing) under another. | Fixed, backend f3374ef7, on dev 2026-10-23; on prod 2026-10-23. The target check corrects a plain typo itself, with a note (““delteProdcut” was read as deleteProduct, the closest method in …”): at most one edit per five letters (a swap of neighbours counts once), same first letter, not a word added or dropped, no rival name at the same distance (RuleTargetCheck.typoOf). Anything else stays a DRAFT with “Did you mean …?” — invalidateOtp is two edits from validateOTP and means the opposite, so it is never substituted. Re-run: X07, X21 (one typo worded three ways), A14, S05 pass. |
| CR-F7 | Medium | New. A logging rule’s key is a hash of workspace + text + title, so a copy or an import with the same title and text gets the same key. With both On, a scan reports one rule and writes two CUSTOM-AUDIT lines per method (scan 1516). | Fixed in code (backend 4f1cb4df), on dev since 22 Oct and verified in the live re-run (101 cases, 92 pass): a new rule whose key another policy holds gets _2, _3, …. |
| CR-F8 | Low | New. Renaming a rule without changing its text leaves compiled.title (used in the log message) at the old name. | Fixed in code (backend a91a625c), on dev since 22 Oct and verified in the live re-run (101 cases, 92 pass). |
| CR-F9 | Low | New. Vague, match-everything and two-obligation rules were refused only with “The rule could not be understood.”; then “Log every method in the application” got “no concrete target”, the same as “improve the logs”. | Fixed on dev 2026-10-23; on prod 2026-10-23 (backend bb2af293). After a no-target refusal, a sentence that asks for every method and names no folder is refused as “The rule matches every method in the repository.” — without a prompt line (the one tried on 22 Oct made the model refuse folder rules). Re-run: N02, X22 pass. |
| CR-F11 | Medium | New (re-run 2026-10-22). Some positive rules were refused with “Logging rules add log lines, so they can’t stop something from being logged.” — “Audit tokens”, “Log the user’s password and credit card number…” — because the model labelled them a leak rule. | Fixed, backend 17738dc0 + 2f4ffd52, on dev 2026-10-23; on prod 2026-10-23. The author’s sentence decides, not the model’s label: a rule is refused as “don’t log” only when one of its clauses is a negation and none asks to log (CustomRuleNegation), and then whatever kind the model chose; otherwise a leak proposal compiles as a coverage rule, secrets still left out with a note. Re-run: X17/X18/X19 pass; the ranking’s “Audit tokens” (L) compiles and scores 82. |
| CR-F13 | Medium | New (re-run). The same sentence did not always compile to the same rule: the event class (the message wording) varied, a folder rule once came back as a glob, refused as an invalid pattern, and every save recompiled. | Fixed, backend 17738dc0, on dev 2026-10-23; on prod 2026-10-23. A successful compile is remembered in the backend for 24 h per workspace, project, latest scan and sentence (CompiledRuleMemo, in memory, 512 entries); a save of the text just checked reuses it under its own id and title. A DRAFT is never remembered, so trying again still asks the model; a new scan or new text compiles fresh; a backend restart forgets. Globs are read as globs (*controller* → controller, get*By* → ^get.*By), and a lone * beside a real target is dropped. Re-run: A13, S04 pass; X20 shows Save = Check rule even when the model refuses (see CR-F14). Different sentences, and the same sentence in another workspace, can still compile differently. |
| CR-F14 | Low | New (re-run 2026-10-23). “Log every method in the controller package at DEBUG” compiled in three cases of a run and was refused as “no concrete target” in a fourth: the sentence shares no word with a method name, so the model gets no candidate list. | Fixed on dev 2026-10-23; on prod 2026-10-23 (backend bb2af293). After a no-target refusal, RuleScope reads “in the X package / folder / module”; when the checkout has that directory the rule is built from the sentence (path X/, the level it names, audit_record) through the same compiler and dry run, with the note “Read as every method in the X/ folder.” Analyzer 01322a0 (another session) also retries a no-target answer once. Re-run: X20, S04 pass. |
| D8 | Low | New (ranking 2026-10-22). Loose wordings were mostly refused (6 of 8 in run 4): “Audit money movements” shares no word with cashDeposit or fundTransfer, so the model was shown no names and returned no_target; “Audit tokens” later listed 13 real methods and was refused as “too many things at once”. | Fixed on dev 2026-10-23; on prod 2026-10-23 (backend f3374ef7, cc348a1f; analyzer 8586f5a). RuleAreas maps the usual audit areas — payments, sign-in, credentials, one-time codes, sessions, onboarding, errors, data access — to the words their methods use, widening only the candidate list; the prompt says an area rule means the listed methods of that area; a rule may list up to 30 methods its project declares (typed or invented names keep 12). Ranking run 4: Loose 20 → 88, no wording refused, 0 leaks. Area rules stay noisier than detailed ones (tokens, OTPs: the model picks a fragment). |
| D9 | Medium | New (ranking 2026-10-23). Rows below 100 lost on NOISE — fragments and the model’s picks reached constructors, exception types and handlers, text templates, yes/no checks (isTokenValid), extractors (getUsernameFromToken), configuration beans, private helpers (sendOtpEmail) and controller copies of service methods — and on VALUES: the account number was one step away (user.getAccount()), only at the exit (registerUser), a record field under another name (userIdentifier → identifier), or only the signed-in user (logout(String token)). | Fixed on dev 2026-10-23; on prod 2026-10-23. Backend 22c642b5, 0150cf91, 5697ebcf: RuleTargets leaves those shapes and private helpers out of the candidates and lists them in match.excludeMethods (Class#method) when a name the author did not type reaches them (never emptying a rule); acronyms split as words (sendOTPByEmail); type hints carry links (User.account → Account), never printed. Analyzer 0337834, 46127de, 99c8c78: excludes honoured; one-link null-guarded reads; suffix names; local types from a same-file call or a get<Type>By… lookup; one line at the exit when any value exists only there; the signed-in user for “who” in Spring Security files; an audit line records its subject. Ranking run 4 final: Detailed 100, Medium 99.5, Loose 98.3, 21 of 24 at 100, 0 leaks. The 3 below 100 are choices between public methods the wording does not settle. |
| CS-F6 | Low | The custom “ENTERING” line is placed after the first statement. | Fixed on dev 2026-10-23 (backend 2902834e). The analyzer named the right line; the backend’s per-rule offset (+1 for every rule but entry and database) moved it. A rule line in the entry-style wording now takes offset 0: deleteProduct 37 → 36, before deleteById. Lines in other wordings are after-the-change by design and keep the offset. |
| RO-F7 / FX-F6 | Low | A General + rule scan named branch main for a scan of master and did not mention the rule. | Fixed on dev 2026-10-23 (backend e2e31b54, 3311f75a). The branch is the one scanned and each rule’s outcome is a key finding (“Your logging rule Product deletion audit: matched 1 method(s) · …”). Gatekeeper uses the same path, so a custom-only Gatekeeper scan no longer asks the model. |
| RO-F8 | Low | Preview and save gave different severities. | Not reproduced 2026-10-23: preview and saved rule both medium (compile memo, CR-F13). |
| CS-F7 | Low | A policy whose only logging rule is DRAFT read On. | Fixed on dev 2026-10-23 (backend 668c9eaf, frontend 69d626f4). A DRAFT rule is not counted in enabledRuleCount, every read and save carries “Logging rule “…” could not be compiled, so it does not run: …”, and the list reads Not running. |
| CS-F9 | Low | No log line said which logging rules a scan loaded. | Fixed on dev 2026-10-23 (backend 539c6256): [logging-rules] project N: K logging rule(s) loaded for this scan [ids] and one outcome line per rule when the scan is saved. |
| CS-F10 | Info | The page could not start a scan for a project with no stored branch list. | Fixed on dev 2026-10-23 (frontend 30d2c018): the commit is no longer required on the page; the backend fills it in. |
| CS-F11 | Low | Account deletion left the scan checkout on disk. | The daily 04:20 sweep already removes the checkout of a deleted project (verified on dev). Since backend f6f9cea6 it also removes that project’s scan-log bundle instead of keeping it 30 days. |
| FX-F4 | Low | updateProduct placements moved between runs. | Not reproduced 2026-10-23: exit 46, class variable 42, database 45, as in the original run. |
| FX-F5 | Low | GET /api/v1/project/{id} templates lacked the profile status. | Fixed on dev 2026-10-23 (backend 0dfca846): each template row has id and status. |
RO-F4 was also measured on a 24-rule ranking matrix over abhi9720/BankingPortal-API (scans
1486–1509 before, 1518–1544 after): detailed rules’ precision 48% → 97%, unwanted lines 102 → 1,
and eight detailed rules together no longer overlap (27 methods with lines from two rules → 0).
The 2026-10-22 run (87 cases on dev: plain-English compile, policy API, and real “Custom rules only” scans) is published as a report; the CR-* rows above come from it.
Writing a logging rule that matches today
Section titled “Writing a logging rule that matches today”On dev these fixes are live; on prod, until they are promoted:
- Name the method and the class in the rule, e.g. “Log every fund transfer in the fundTransfer
method in AccountServiceImpl at WARN”. Once backend
a91a625cis deployed, an exact method name in such a rule matches that method only (^fundTransfer$), and Check rule lists the methods the rule reaches in the project. - After Check rule, read the compiled
methodNamePatterns/filePathPatternsand confirm those names exist in the repository. name*means names starting withname(backenda91a625c; before it, a regex).- In a Java exception handler, “with the exception message” logs
ex.getMessage()(analyzer6afa33e). - A value the method produces (“with the new account number” in
createAccount) is logged in a second rule line just before the method’s last top-levelreturn, read from a local that is in scope there —(account == null ? null : account.getAccountNumber())— never from the return expression, which would runsave()twice (analyzer89fc54d, Java only, one getter deep; early returns are not covered). - On prod, expect the rule to match every method in a named class (OR targeting; fixed on dev), and expect no named value in the message unless it is one of the method’s parameters.
- After the scan, confirm the result has rows whose
ruleCanonicalNameis the rule’s key (custom_<ws>_…), and that the PR diff contains the rule’s tag (defaultCUSTOM-AUDIT).
Request
Section titled “Request”curl -s -i -X POST https://leapfrogapi.deployd.io/api/v1/generator \ -H "Authorization: Bearer $LOGRITE_API_KEY" -H "Content-Type: application/json" \ -d '{"scanId": 280, "timezone": "Europe/London"}'# HTTP/2 202 (empty body)Auth: an API key (lgp_live_…) or a signed-in session JWT. Body (LogGeneratorRequestDTO):
| Field | Required | Notes |
|---|---|---|
scanId | yes | The scanResultId of a finished scan — see Scan a repository step 4 |
timezone | no | IANA zone used to stamp the branch name in your wall clock; unparseable or absent → the server’s zone |
clientId | no | Browser-tab id, so progress streams back to the tab that started the run. API callers omit it |
userId | — | Ignored. Overwritten with the caller’s own id; a body cannot act as somebody else |
token | — | Not needed. The push uses your GitHub sign-in token if you have one, otherwise the workspace’s GitHub App installation |
The caller must be able to see the scan (scan → project → workspace membership). Measured on
dev, 2026-10-01 (the {} row as of the feat/sandbox-2 backend — it used to be a 500
The given id must not be null):
| Call | Answer |
|---|---|
{"scanId": 999999999} | 404 "scan not found" |
{} (no scanId) | 400, a body naming the missing field: {"scanId": "must not be null"}. userId is never listed — it is not required |
No Authorization header | 401 |
The levels the generated logs use
Section titled “The levels the generated logs use”The statements are written at the level each rule in the project’s template carries. The
default general template (2026-10-01) is: method entry, method exit and variable changes at
DEBUG; database calls and external-service calls (with their timings) at INFO; exceptions at
ERROR. It used to put method entry and every variable change at ERROR and exit at WARN.
A saved project template keeps the levels it was saved with — only the catalogue the next
template is built from moved. See services/backend → “Default general levels”.
What happens after the 202
Section titled “What happens after the 202”The run happens off the request thread; the 202 only means “accepted”. Generation then:
- Checks the scan has not already been generated. If it has, the run stops and reports the existing PR rather than opening a second one.
- Instruments the checkout and runs the build/syntax gates for the language.
- Creates the branch
feat/add_logging_<yyyy-MM-dd_HH-mm-ss>(stamped intimezone), e.g.feat/add_logging_2026-08-15_07-02-12, and pushes it. - Opens a pull request from that branch into the scanned branch, titled
Add logs. On GitHub, if the build failed after instrumentation, the PR is opened as a draft with a build-failure banner at the top of its body, and the run is recorded asFailed. Bitbucket repositories get a Bitbucket PR. - Emails the requesting user on a fully successful run (build passed and PR created).
If nothing changed, or the push is refused, there is no PR and the run is still recorded,
with the reason. Logrite’s own webhook ignores PRs on feat/add_logging_* branches, so a
generated PR never triggers a Gatekeeper review of itself.
Progress is streamed to a signed-in browser over the WebSocket /ws/stream, ending in a frame
whose message starts END followed by the PR URL (or the reason there is none). An API caller
has no socket, so poll the results instead.
Where the result appears
Section titled “Where the result appears”/api/v1/log-generator-results/* — all accept a key or a session, scoped to what the caller can
see:
| Route | Returns |
|---|---|
GET /api/v1/log-generator-results/project/{projectId} | Every generator run for the project |
GET /api/v1/log-generator-results/{id} | One run |
GET /api/v1/log-generator-results/keyFindings/{scanId} | Key findings for a scan id |
GET /api/v1/log-generator-results/user/{userId} | The caller’s roll-up |
GET /api/v1/log-generator-results/workspace/{workspaceId} | The workspace roll-up |
A row, as returned on dev for project 4252:
{ "id": 208, "scanId": 280, "branchName": "feat/add_logging_2026-08-15_07-02-12", "branchLink": "https://github.com/Deployd-io/BankJava_TestRepo/pull/167", "logType": {"id": 1, "name": "general"}, "appliedTemplates": ["general"], "criticalPercent": 13.22, "startLogCoveragePercent": 6.74, "endLogCoveragePercent": null, "result": "Success", "errorDetails": null, "runDate": "2026-08-15 07:02:11"}branchLink is the PR URL when the PR opened, otherwise the pushed branch. result is
Success or Failed (with errorDetails saying why — e.g. the build failure).
Worked example
Section titled “Worked example”API=https://leapfrogapi.deployd.io/api/v1AUTH="Authorization: Bearer $LOGRITE_API_KEY"
# 1. a finished scan of project 4252 (see Scan a repository)SCAN=$(curl -s "$API/scan-runs/latest?projectId=4252" -H "$AUTH" | jq -r .scanResultId)
# 2. generate — 202, no bodycurl -s -o /dev/null -w '%{http_code}\n' -X POST "$API/generator" -H "$AUTH" \ -H "Content-Type: application/json" -d "{\"scanId\": $SCAN}"
# 3. poll until a row for this scan appearscurl -s "$API/log-generator-results/project/4252" -H "$AUTH" \ | jq --argjson s "$SCAN" '.[] | select(.scanId == $s) | {result, branchName, branchLink, errorDetails}'Code fixes — /api/v1/codeFixes
Section titled “Code fixes — /api/v1/codeFixes”A separate path: POST /api/v1/codeFixes (the Fix it action in the AI chat panel) and
POST /api/v1/codeFixes/auto (autonomous build-verified fix, one PR only if the project
compiles) fix findings rather than add logs. Both take {projectName, levels?, category?, userPrompt?, userComments?, maxFiles?} and need a signed-in session — they read the
workspace from the JWT, so an API key is refused with 401 "this endpoint does not accept API keys — sign in and use your session". Details:
Backend → Autonomous Fix-it.