penpot/frontend/stylelint.config.mjs
Andrey Antukh cd4a4da0f2
🎉 Add telemetry anonymous event collection (#9483)
* 🎉 Add telemetry anonymous event collection

Rewrite the audit logging subsystem to support three operating modes and
add anonymous telemetry event collection:

Modes:
- A (audit-log only): events persisted with full context
- B (audit-log + telemetry): same as A, plus events are collected for
  telemetry shipping
- C (telemetry-only): events stored anonymously with PII stripped,
  telemetry flag active, audit-log flag inactive

Audit system refactoring (app.loggers.audit):
- Replace qualified map keys (::audit/name etc.) with plain keywords
- Rename submit! -> submit, insert! -> insert, prepare-event ->
  prepare-rpc-event
- Add submit* as a lower-level public API
- Add process-event dispatch function that handles all three modes and
  webhooks in a single tx-run!
- Add :id to event schema (auto-generated if omitted)
- Add filter-telemetry-props: anonymises event props per event type.
  Keeps UUID/boolean/number values; for login/identify events preserves
  lang, auth-backend, email-domain; for navigate events preserves route,
  file-id, team-id, page-id; instance-start trigger passes through.
- Add filter-telemetry-context: retains only safe context keys.
  Backend: version, initiator, client-version, client-user-agent.
  Frontend: browser, os, locale, screen metrics, event-origin.
- Timestamps truncated to day precision via ct/truncate for telemetry
  storage
- PII stripped: props emptied, ip-addr zeroed, session-linking and
  access-token fields removed from context

Config (app.config):
- Derive :enable-telemetry flag from telemetry-enabled config option

Email utilities (app.email):
- Add email/clean and email/get-domain helper functions for domain
  extraction from email addresses

Setup (app.setup):
- Emit instance-start trigger event at system startup
- Simplify handle-instance-id (remove read-only check)

RPC layer (app.rpc):
- wrap-audit now activates when :telemetry flag is set
- Add :request-id to RPC params context for event correlation

RPC commands (management, teams_invitations, verify_token, OIDC auth,
webhooks): migrate all audit call sites to use the new plain-key API

SREPL (app.srepl.main):
- Migrate all audit/insert! calls to audit/insert with plain keys

Telemetry task (app.tasks.telemetry):
- Restructure legacy report into make-legacy-request; distinguish
  payload type as :telemetry-legacy-report
- Add collect-and-send-audit-events: loop fetching up to 10,000 rows
  per iteration, encodes and sends each page, deletes on success,
  stops immediately on failure for retry
- Add send-event-batch: POSTs fressian+zstd batch (base64 via
  blob/encode-str) to the telemetry endpoint with instance-id per event
- Add gc-telemetry-events: enforces 100,000-row safety cap by dropping
  oldest rows first
- Add delete-sent-events: deletes successfully shipped rows by id

Blob utilities (app.util.blob):
- Add encode-str/decode-str: combine fressian+zstd encoding with URL-
  safe base64 for JSON-safe string transport

Database:
- Add migration 0145: index on audit_log (source, created_at ASC) for
  efficient telemetry batch collection queries

Frontend:
- Always initialize event system regardless of :audit-log flag
- Defer auth events (signin identify) to after profile is set
- Refactor event subsystem for telemetry support

Tests (21 test vars, 94 assertions in tasks-telemetry-test):
- Cover all code paths: disabled/enabled telemetry, no-events no-op,
  happy-path batch send and delete, failure retention, payload anonymity,
  context stripping, timestamp day precision, batch encoding round-trip,
  multi-page iteration, GC cap enforcement, partial failure handling
- blob encode-str/decode-str round-trip tests (14 test vars)
- RPC audit integration tests (5 test vars)

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

* 📎 Add pr feedback changes

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-11 12:19:59 +02:00

69 lines
2.4 KiB
JavaScript

import postcssScss from "postcss-scss";
/** @type {import("stylelint").Config} */
export default {
extends: ["stylelint-config-standard-scss"],
plugins: ["stylelint-scss", "stylelint-use-logical-spec"],
overrides: [
{
files: ["**/*.scss"],
customSyntax: postcssScss,
},
],
rules: {
"at-rule-no-unknown": null,
"declaration-property-value-no-unknown": null,
"property-no-unknown": [true, { ignoreProperties: ["text-box"] }],
"selector-pseudo-class-no-unknown": [
true,
{ ignorePseudoClasses: ["global"] }, // TODO: Avoid global selector usage and remove this exception
],
// scss
"scss/comment-no-empty": null,
"scss/at-rule-no-unknown": true,
// TODO: this rule should be enabled to follow scss conventions
"scss/load-no-partial-leading-underscore": null,
// This allows using the characters - or _ as a prefix and is ISO compliant with the Sass specification.
"scss/dollar-variable-pattern": "^[-_]?([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
// This allows using the characters - or _ as a prefix and is ISO compliant with the Sass specification.
"scss/at-mixin-pattern": "^[-_]?([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
// TODO: Enable rules secuentially
// // Using quotes
"font-family-name-quotes": "always-unless-keyword",
"function-url-quotes": "always",
"selector-attribute-quotes": "always",
// // Disallow vendor prefixes
"at-rule-no-vendor-prefix": true,
"media-feature-name-no-vendor-prefix": true,
"property-no-vendor-prefix": true,
"selector-no-vendor-prefix": true,
"value-no-vendor-prefix": true,
// // Specificity
"no-descending-specificity": null,
// "max-nesting-depth": 3,
"selector-max-compound-selectors": 3,
"selector-max-specificity": "1,2,1",
// // Miscellanea
"color-named": "never",
// "declaration-no-important": true,
"declaration-property-unit-allowed-list": {
"font-size": ["rem", "lh"],
"/^animation/": ["s"],
},
// // 'order/properties-alphabetical-order': true,
"selector-max-type": 1,
"selector-type-no-unknown": true,
// // Notation
"font-weight-notation": "numeric",
// // URLs
"function-url-no-scheme-relative": true,
// "liberty/use-logical-spec": "always",
"selector-class-pattern": null,
"alpha-value-notation": "number",
"color-function-notation": "modern",
"value-keyword-case": "lower",
},
};