Every store (model, table, collection) holding personal data has a declared retention period in code, configuration or a retention document that names the store. No AI engine is involved: the verdict is reproducible from the source alone.
Inputs
Non-test code files of the checkout (JavaScript, TypeScript, Python, PHP) plus Prisma, SQL, YAML, TOML, JSON, INI, .env, Markdown and text files. Schema files (models, entities, migrations, Prisma, SQL) yield the stores and their personal-data fields, split into one block per declaration (Prisma model, Django/SQLAlchemy class, CREATE TABLE, Schema::create, Mongoose Schema, TypeORM @Entity, Sequelize define, Drizzle table). Declarations are looked for by NAME (singular or plural, any case style, not a foreign-key reference such as userId): a TTL/expiry/prune mechanism in the store's own block or a retention annotation in its comments; a document or configuration line naming the store with a retention statement within three lines; a code line naming it with a retention setting or TTL within three lines; a function whose hard-delete statement names the store and bounds the rows by age. Paths listed in excludes (test, spec and tmp files, __tests__, __mocks__, fixtures, test, tests and audit-scripts folders) are never analysed. Nothing of the checkout is executed.
Decision rule
Not applicable when no store with personal-data fields is declared. For each personal-data store the check requires ONE of: a TTL, expiry or prune mechanism inside its schema block (expireAfterSeconds, expires, expiresAt, @Index expire, Prunable, add_retention_policy…) or a retention annotation with a period in the block's comments; a document or configuration line (Markdown, text, YAML, TOML, JSON, SQL, .env, settings) that names the store and states a retention within three lines (a period, deleted/kept/purged after, retention, TTL); a code line naming the store with a retention setting (RETENTION_DAYS, retention:, ttl, lifecycle rule) within three lines; or a cleanup job (a function whose deleteMany/destroy/DELETE FROM/prune/purge statement names the store and bounds the rows by age). FAIL (HIGH, one finding per store with its line) when personal-data stores exist and the checkout declares no retention of any kind. Otherwise a store with no declaration naming it is MEDIUM (blocks only inside Extended suites), HIGH when the store holds special-category or financial data (health, biometric, religion, IBAN, card number, salary, tax id…) or is named like one (patients, payments, invoices, transactions). A document that keeps a store indefinitely (forever, never deleted, no expiration) without binding it to the account is HIGH (retention-unbounded). PASS when every store has a declaration naming it; the summary names each store with its declaration (file, line, signal). Not judged by this check (declared): whether the declared period is enforced at run time (a documented period with no job is accepted here; enforcement is the matter of retention-per-store), whether the period is lawful for the data category, and stores whose retention is bound to the account (accepted as declared; the deletion flow is the matter of real-deletion).
Type
deterministic
1.1.0: deterministic: the rule runs over the static analysis kit (tokens, function units, calls, imports, routes) with no AI engine; exact decisionRule and languages published; not-applicable with the reason when the checkout gives nothing to evaluate. Store-to-declaration mapping BY NAME (singular/plural, any case style, foreign-key suffixes excluded): TTL or prune in the store's own schema block or annotation, a doc or config line naming the store with a retention statement within 3 lines, a code line with a retention setting, or a cleanup job whose hard delete names the store and bounds rows by age; an undeclared store is MEDIUM, a special-category or financial store or nothing declared is HIGH, 'forever/never deleted' is HIGH. Not judged: enforcement (retention-per-store), lawfulness of the period, account-bound retention. (1.0.2: never analyses temporary files (*.tmp.*; spec.excludes follows AUDITOR_ANALYSIS_EXCLUDES). (1.0.1: never analyses test, spec, fixture and mock paths nor the auditor's own scripts (spec.excludes = AUDITOR_ANALYSIS_EXCLUDES); a fixture-looking secret (sk_test_, example, dummy) in real code is LOW, informative; every model call stays under the engine prompt cap (spec.promptMaxChars = AUDITOR_AI_PROMPT_MAX_CHARS): assess mode sends the candidate set in parts and aggregates the verdicts (a HIGH in any part is a FAIL, an unsatisfied part is a FAIL).))
// stores = model / table / collection blocks of the schema files with personal-data fields; each is looked up BY NAME (users, User, prisma.user, usersDao, support_tickets; not userId)
// declared = TTL/prune in the store's block | doc or config line naming the store + retention statement within 3 lines | code line naming it + retention setting | cleanup job: hard delete naming the store bounded by age
// undeclared store -> MEDIUM (special/financial -> HIGH; nothing declared anywhere -> HIGH); 'forever / never deleted' -> HIGH; no personal-data store -> not-applicable
The full script is disclosed on request in a read-only viewer (never published on GitHub); the attestation binds to this exact hash.