{
  "holes": [
    {
      "id": "ti-001",
      "code_ref": "packages/db/src/queries/accountant-export.ts:157",
      "fn": "setAccountantExportJobDone",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; setAccountantExportJobDone exported only — movement-file.ts:97-153 updates accountantExportJobs inline by jobId from createAccountantExportJob(tenantId) at accountant-exports.ts:99",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-002",
      "code_ref": "packages/db/src/queries/accountant-export.ts:169",
      "fn": "setAccountantExportJobError",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; setAccountantExportJobError exported only — same inline update path as ti-001",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-003",
      "code_ref": "packages/db/src/queries/activities.ts:65",
      "fn": "softDeleteCustomerActivity",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/customers/activity.ts:98-110 — getCustomerActivityNote(db,session.tid,customerId,activityId) tenant check before softDeleteCustomerActivity(db,activityId)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-004",
      "code_ref": "packages/db/src/queries/activities.ts:109",
      "fn": "softDeleteInvoiceActivity",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/invoices/activity.ts:112 — getInvoiceActivityNote with session.tid upstream before softDeleteInvoiceActivity",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-005",
      "code_ref": "packages/db/src/queries/activities.ts:153",
      "fn": "softDeleteProjectActivity",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/projects/activity.ts:112 — getProjectActivityNote with session.tid upstream before softDeleteProjectActivity",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-006",
      "code_ref": "packages/db/src/queries/activities.ts:197",
      "fn": "softDeleteVendorActivity",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/vendor-activity.ts:114 — getVendorActivityNote with session.tid upstream before softDeleteVendorActivity",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-007",
      "code_ref": "packages/db/src/queries/attachments.ts:206",
      "fn": "hardDeleteAttachmentRow",
      "class": "defense-in-depth-only",
      "caller_evidence": "No caller in apps/packages outside attachments.ts definition — helper unwired",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-008",
      "code_ref": "packages/db/src/queries/auth-reads.ts:80",
      "fn": "getRoleById",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/auth/invite.ts:72-74 + users/team.ts:139-141 — getRoleById then role.tenantId!==session.tid rejected",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-009",
      "code_ref": "packages/db/src/queries/bad-debt.ts:271",
      "fn": "performRecovery",
      "class": "defense-in-depth-only",
      "caller_evidence": "packages/db/src/queries/bad-debt.ts:263-277 — reclaim row from tenant+invoice scoped select inside performRecovery; apps/zync-api/src/routes/invoices/record-recovery.ts:50 passes session.tid",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-010",
      "code_ref": "packages/db/src/queries/bad-debt.ts:277",
      "fn": "performRecovery",
      "class": "defense-in-depth-only",
      "caller_evidence": "Same as ti-009 — second UPDATE on reclaim.id after tenant-scoped SELECT in performRecovery",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-011",
      "code_ref": "packages/db/src/queries/calendar.ts:230",
      "fn": "getCalendarConnectionById",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/calendar/sync-webhooks.ts:77 — POST webhook /webhooks/calendar/google/:connId; connId from URL, Google KV channel map gates access; cross-tenant lookup intentional",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-012",
      "code_ref": "packages/db/src/queries/calendar.ts:273",
      "fn": "updateCalendarConnectionTokens",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/calendar/sync-webhooks.ts:94,164 + cron-sync.ts:80 — token refresh after getCalendarConnectionById(conn.id) on webhook/cron path",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-013",
      "code_ref": "packages/db/src/queries/calendar.ts:285",
      "fn": "updateCalendarConnectionLastSynced",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/calendar/sync-webhooks.ts:109,179 + cron-sync.ts:125 — last-sync touch after webhook/cron connection resolve",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-014",
      "code_ref": "packages/db/src/queries/calendar.ts:342",
      "fn": "getSchedulingConnectionById",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; getSchedulingConnectionById only exported — scheduling uses getSchedulingConnectionByTenant at scheduling-webhooks.ts:65",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-015",
      "code_ref": "packages/db/src/queries/contract-signatories.ts:128",
      "fn": "bulkUpsertSignatories",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/contracts/index.ts:392-432 — getContractTenantScoped before bulkUpsertSignatories; deletes use ids from tenant-scoped existing list",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-016",
      "code_ref": "packages/db/src/queries/contract-signatories.ts:195",
      "fn": "markManualReminderSent",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/contracts/index.ts:452-504 — getContractTenantScoped+getSignatory(sigId,contractId,session.tid) before markManualReminderSent",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-017",
      "code_ref": "packages/db/src/queries/contract-signatories.ts:229",
      "fn": "resendSignatoryLink",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/contracts/index.ts:535-548 — getContractTenantScoped+getSignatory upstream before resendSignatoryLink",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-018",
      "code_ref": "packages/db/src/queries/contract-signatories.ts:260",
      "fn": "replaceSignatory",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/contracts/index.ts:584-621 — getContractTenantScoped+getSignatory upstream before replaceSignatory",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-019",
      "code_ref": "packages/db/src/queries/contract-signatories.ts:294",
      "fn": "markSignatoryReminderSent",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/cron/contract-signing-reminders.ts:113 — cron iterates due signatories; id from cron query not user HTTP param",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-020",
      "code_ref": "packages/db/src/queries/contracts.ts:80",
      "fn": "getContractPublic",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/contracts/index.ts:277-289 — public POST /:id/sign; contractId is signing token, no session",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-021",
      "code_ref": "packages/db/src/queries/customer-dedup.ts:374",
      "fn": "mergeCustomers",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/customers/dedup.ts:103-109 — mergeCustomers verifies suggestion tenantId at customer-dedup.ts:309-318 before suggestion update",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-022",
      "code_ref": "packages/db/src/queries/data-import.ts:188",
      "fn": "setImportJobProcessing",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/imports/router.ts:269-287 queues import.process; setImportJobProcessing unwired (no queue consumer) — intended queue path with getImportJob(session.tid,id) upstream",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-023",
      "code_ref": "packages/db/src/queries/expenses.ts:225",
      "fn": "getExpenseById",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/expenses/index.ts:329-331 — row.tenantId!==session.tid before getExpenseById; queue expense-process.ts:55-58 tenant match",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-024",
      "code_ref": "packages/db/src/queries/expenses.ts:431",
      "fn": "setExpenseStatus",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/expenses/index.ts:329-344 + queues/expense-process.ts:48-62 — tenant verified before setExpenseStatus",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-025",
      "code_ref": "packages/db/src/queries/marketing.ts:949",
      "fn": "getLeadWebhookById",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/marketing/public-form.ts:248 — public GET webhook verify; webhookId in path, secret token check follows",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-026",
      "code_ref": "packages/db/src/queries/marketing.ts:1012",
      "fn": "touchWebhookLastReceived",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/marketing/public-form.ts:271-299 — inbound POST webhook after HMAC verify",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-027",
      "code_ref": "packages/db/src/queries/oauth.ts:152",
      "fn": "markAuthorizationCodeUsed",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/oauth/token.ts:106-130 — markAuthorizationCodeUsed after lookupAuthorizationCode(code secret)",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-028",
      "code_ref": "packages/db/src/queries/oauth.ts:274",
      "fn": "rotateOAuthRefreshToken",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/oauth/token.ts:157-185 — rotateOAuthRefreshToken after lookupRefreshToken(hash)",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-029",
      "code_ref": "packages/db/src/queries/profitability.ts:121",
      "fn": "getProjectProfitability",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/reports/profitability.ts:79 — getProjectProfitability(db,session.tid,projectId) verifies project tenant at profitability.ts:58-62; customer id from that project row",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-030",
      "code_ref": "packages/db/src/queries/project-templates.ts:374",
      "fn": "updateTemplateTask",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/project-templates/index.ts:158-164 — updateTemplateTask after getTemplate(db,templateId,session.tid) at project-templates.ts:357",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-031",
      "code_ref": "packages/db/src/queries/project-templates.ts:396",
      "fn": "deleteTemplateTask",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/project-templates/index.ts:176-180 — deleteTemplateTask after getTemplate tenant check at project-templates.ts:396",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-032",
      "code_ref": "packages/db/src/queries/project-templates.ts:576",
      "fn": "instantiateTemplate",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/project-templates/index.ts:221 — instantiateTemplate calls getTemplate(input.template_id,tenantId) at project-templates.ts:452 before usageCount update",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-033",
      "code_ref": "packages/db/src/queries/proposal-expiry-cron.ts:154",
      "fn": "revertLeadToQualifiedIfNoLiveProposal",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/cron/proposal-expiry.ts:65-69 — cron passes tenantId; lead SELECT tenant-scoped at proposal-expiry-cron.ts:133-137 before lead UPDATE",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-034",
      "code_ref": "packages/db/src/queries/receipts.ts:538",
      "fn": "voidReceipt",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/receipts/index.ts:120 — voidReceipt selects receipt with tenantId at receipts.ts:522-525 before UPDATE by id only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-035",
      "code_ref": "packages/db/src/queries/receipts.ts:557",
      "fn": "voidReceipt",
      "class": "defense-in-depth-only",
      "caller_evidence": "voidReceipt same path — invoicePayments update uses paymentIds from receipt lines after tenant-scoped receipt SELECT",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-036",
      "code_ref": "packages/db/src/queries/recurring-invoices.ts:405",
      "fn": "advanceRecurringTemplate",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/invoices/recurring.ts:230-311 — getRecurringInvoiceTemplate(db,session.tid,id) before advanceRecurringTemplate(db,template)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-037",
      "code_ref": "packages/db/src/queries/recurring-tasks.ts:146",
      "fn": "touchRecurringGenerated",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/cron/recurring-task-generator.ts:84 — cron touchRecurringGenerated(db,recurring.id) from due recurring query",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-038",
      "code_ref": "packages/db/src/queries/report-schedules.ts:137",
      "fn": "getReportScheduleById",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/cron/scheduled-reports.ts:195 — getReportScheduleById from queue/cron scheduleId not user session",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-039",
      "code_ref": "packages/db/src/queries/report-schedules.ts:166",
      "fn": "touchScheduleRun",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/cron/scheduled-reports.ts:166 — touchScheduleRun from cron after schedule loaded",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-040",
      "code_ref": "packages/db/src/queries/report-schedules.ts:178",
      "fn": "deactivateSchedule",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/cron/scheduled-reports.ts:64 — deactivateSchedule from cron runner",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-041",
      "code_ref": "packages/db/src/queries/sessions.ts:62",
      "fn": "touchSession",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/middleware/session-guard.ts:55-79 — touchSession(db,sessionRow.id) where sessionRow from getSessionByTokenHash(cookie), not user-supplied id param",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-042",
      "code_ref": "packages/db/src/queries/tenant-export-jobs.ts:53",
      "fn": "getTenantExportJobById",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; getTenantExportJobById exported only (GDPR export unwired)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-043",
      "code_ref": "packages/db/src/queries/tenant-export-jobs.ts:73",
      "fn": "markTenantExportJobStatus",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; markTenantExportJobStatus exported only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-044",
      "code_ref": "packages/db/src/queries/time-approval.ts:208",
      "fn": "approveTimeEntries",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/time/approvals.ts:130 — approveTimeEntries SELECT request with tenantId at time-approval.ts:191-196 before UPDATE by requestId only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-045",
      "code_ref": "packages/db/src/queries/time-approval.ts:282",
      "fn": "rejectTimeEntries",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/time/approvals.ts:162 — rejectTimeEntries same upstream tenant SELECT at time-approval.ts:265-270",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-046",
      "code_ref": "packages/db/src/queries/time.ts:703",
      "fn": "stopStaleEntry",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/cron/time-cleanup.ts:40 — stopStaleEntry from findStaleRunningEntries cron",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-047",
      "code_ref": "packages/db/src/queries/time.ts:857",
      "fn": "getTaskProjectId",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/time.ts:165 — getTaskProjectId(db,tokenRow.taskId) on magic-link consume; taskId from token row after consumeTimerMagicLink not raw user id param",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-048",
      "code_ref": "packages/db/src/queries/time.ts:872",
      "fn": "getTaskTitle",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/time.ts:558 — POST /api/time/magic passes parsed.data.taskId (body) to getTaskTitle with NO task tenant ownership check; enables cross-tenant task title leak + foreign taskId stored in magic token",
      "recommended": "add predicate"
    },
    {
      "id": "ti-049",
      "code_ref": "packages/db/src/queries/uniform-export.ts:51",
      "fn": "setUniformExportJobRunning",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/queues/uniform-export.ts:70 — queue consumer jobId from QUEUE message enqueued at uniform-format.ts:104-128 after createUniformExportJob(tenantId)",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-050",
      "code_ref": "packages/db/src/queries/uniform-export.ts:66",
      "fn": "setUniformExportJobDone",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/queues/uniform-export.ts:111 — same trusted queue path as ti-049",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-051",
      "code_ref": "packages/db/src/queries/uniform-export.ts:79",
      "fn": "setUniformExportJobError",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/queues/uniform-export.ts:126 — handleUniformExportError queue path",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-052",
      "code_ref": "packages/db/src/queries/uniform-export.ts:90",
      "fn": "updateUniformExportJobDownloadExpiry",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/reports/uniform-format.ts:161-179 — getUniformExportJob(db,tenantId,jobId) before updateUniformExportJobDownloadExpiry",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-053",
      "code_ref": "packages/db/src/queries/webhooks.ts:290",
      "fn": "updateWebhookDelivery",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; updateWebhookDelivery exported only (webhook.deliver consumer not wired to this helper)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-054",
      "code_ref": "packages/db/src/queries/webhooks.ts:357",
      "fn": "updateApiKeyLastUsed",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-public-api/src/middleware/auth.ts:124-134 — updateApiKeyLastUsed(db,keyRow.id) after getApiKeyByHash; id from secret hash not client-supplied",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-055",
      "code_ref": "apps/zync-api/src/routes/tasks.ts:96",
      "fn": "POST / (createTask handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/tasks.ts:96 — POST /api/tasks passes body project_id/status_id/assignee_id to createTask with no FK tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-056",
      "code_ref": "apps/zync-api/src/routes/tasks.ts:138",
      "fn": "PATCH /:id (updateTask handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/tasks.ts:138 — PATCH /api/tasks/:id passes patch project_id/status_id/assignee_id to updateTask without FK tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-057",
      "code_ref": "packages/db/src/queries/tasks.ts:254",
      "fn": "createTask",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/tasks.ts:249-258 — createTask inserts foreign project_id/status_id/assignee_id without tenant ownership checks",
      "recommended": "add predicate"
    },
    {
      "id": "ti-058",
      "code_ref": "packages/db/src/queries/tasks.ts:309",
      "fn": "updateTask",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/tasks.ts:308-317 — updateTask applies patch project_id/status_id/assignee_id without FK tenant verify (task row is tenant-scoped)",
      "recommended": "add predicate"
    },
    {
      "id": "ti-059",
      "code_ref": "apps/zync-api/src/routes/support/index.ts:127",
      "fn": "POST / (createTicket handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/support/index.ts:127 — POST / passes customer_id/contact_id/category_id to createTicket without FK tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-060",
      "code_ref": "packages/db/src/queries/support.ts:288",
      "fn": "createTicket",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/support.ts:284-296 — createTicket inserts customerId/contactId/categoryId without tenant FK checks",
      "recommended": "add predicate"
    },
    {
      "id": "ti-061",
      "code_ref": "apps/zync-api/src/routes/support/index.ts:155",
      "fn": "PATCH /:id (updateTicket handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/support/index.ts:155 — PATCH /:id passes category_id/assignee_id without FK tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-062",
      "code_ref": "packages/db/src/queries/support.ts:319",
      "fn": "updateTicket",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/support.ts:319-320 — updateTicket sets categoryId/assigneeId from patch without FK tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-063",
      "code_ref": "apps/zync-api/src/routes/portal-files.ts:140",
      "fn": "POST /portal-files",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/portal-files.ts:140-143 — POST /portal-files accepts customer_id/project_id body without tenant ownership verify before createPortalFile",
      "recommended": "add predicate"
    },
    {
      "id": "ti-064",
      "code_ref": "packages/db/src/queries/portal-files.ts:209",
      "fn": "createPortalFile",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/portal-files.ts:205-210 — createPortalFile inserts customerId/projectId without FK tenant checks",
      "recommended": "add predicate"
    },
    {
      "id": "ti-065",
      "code_ref": "apps/zync-api/src/routes/projects/crud.ts:86",
      "fn": "POST / (createProject handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/projects/crud.ts:86 — POST / passes customer_id to createProject without customer tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-066",
      "code_ref": "packages/db/src/queries/projects.ts:288",
      "fn": "createProject",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/projects.ts:288 — createProject sets customerId from input without FK tenant check",
      "recommended": "add predicate"
    },
    {
      "id": "ti-067",
      "code_ref": "packages/db/src/queries/projects.ts:344",
      "fn": "updateProject",
      "class": "live-exploitable",
      "caller_evidence": "packages/db/src/queries/projects.ts:344 — updateProject sets customerId from patch without FK tenant check",
      "recommended": "add predicate"
    },
    {
      "id": "ti-068",
      "code_ref": "apps/zync-api/src/routes/projects/crud.ts:115",
      "fn": "PATCH /:id (updateProject handler)",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/projects/crud.ts:115-118 — PATCH /:id passes customer_id to updateProject without upstream customer verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-069",
      "code_ref": "apps/zync-api/src/routes/expenses/index.ts:98",
      "fn": "POST per-diem schema",
      "class": "live-exploitable",
      "caller_evidence": "apps/zync-api/src/routes/expenses/index.ts:245-251 — POST /per-diem optional project_id to createPerDiemExpense (per-diem.ts:64) without project tenant verify",
      "recommended": "add predicate"
    },
    {
      "id": "ti-070",
      "code_ref": "packages/db/src/queries/data-import.ts:251",
      "fn": "completeImportJob",
      "class": "defense-in-depth-only",
      "caller_evidence": "Intended import.process queue path; completeImportJob unwired — handler getImportJob(session.tid,id) at imports/router.ts:269",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-071",
      "code_ref": "packages/db/src/queries/invoice-payments.ts:342",
      "fn": "reverseInvoicePayment",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/invoices/payments.ts:109 — reverseInvoicePayment SELECT payment with tenantId+invoiceId at invoice-payments.ts:314-323 before DELETE by paymentId only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-072",
      "code_ref": "packages/db/src/queries/kb-versions.ts:309",
      "fn": "restoreKbArticleVersion",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/kb/versions.ts:191-196 — restoreArticleVersion SELECT article with tenantId at kb-versions.ts:258-266 before UPDATE by articleId only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-073",
      "code_ref": "packages/db/src/queries/customer-dedup.ts:381",
      "fn": "mergeCustomers",
      "class": "defense-in-depth-only",
      "caller_evidence": "mergeCustomers suggestion UPDATE at customer-dedup.ts:374-381 after tenant-scoped suggestion SELECT (same as ti-021)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-074",
      "code_ref": "packages/db/src/queries/roles.ts:251",
      "fn": "updateCustomRole",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/roles.ts:140 — updateCustomRole SELECT role with tenantId at roles.ts:231-235 before UPDATE by roleId only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-075",
      "code_ref": "packages/db/src/queries/roles.ts:344",
      "fn": "deleteCustomRole",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/roles.ts:179 — deleteCustomRole tenant-scoped existing SELECT at roles.ts:300-304 before DELETE by roleId at :344",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-076",
      "code_ref": "packages/db/src/queries/roles.ts:361",
      "fn": "deleteCustomRole",
      "class": "defense-in-depth-only",
      "caller_evidence": "deleteCustomRole second DELETE path at roles.ts:361 after same tenant-scoped existing SELECT",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-077",
      "code_ref": "packages/db/src/queries/webhooks.ts:299",
      "fn": "updateWebhookDelivery",
      "class": "defense-in-depth-only",
      "caller_evidence": "No app caller; updateWebhookDelivery second UPDATE site at webhooks.ts:299 — same unwired helper as ti-053",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-078",
      "code_ref": "packages/db/src/queries/tenant-export-jobs.ts:57",
      "fn": "getTenantExportJobById",
      "class": "defense-in-depth-only",
      "caller_evidence": "getTenantExportJobById at tenant-export-jobs.ts:57-61 — app-level row.tenantId!==tenantId returns null; no HTTP caller yet",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-079",
      "code_ref": "packages/db/src/queries/time.ts:260",
      "fn": "stopEntry",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/time.ts:406,454 — stopEntry SELECT entry with tenantId at time.ts:229-237 before UPDATE by entryId only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-080",
      "code_ref": "packages/db/src/queries/time.ts:327",
      "fn": "startEntry",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/time.ts:175,328 — startEntry stops running entry selected with tenantId+userId at time.ts:306-316 before UPDATE by running.id only",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-081",
      "code_ref": "packages/db/src/queries/proposals.ts:678",
      "fn": "rejectProposalByToken",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/proposals-public.ts:194-201 — rejectProposalByToken after getProposalByPublicToken(token)",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-082",
      "code_ref": "packages/db/src/queries/contractors.ts:906",
      "fn": "updatePayoutBill",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/contractors/router.ts:417-424 — updatePayoutBill calls getPayoutBillWithLines(tenantId,contractorId,billId) at contractors.ts:804 before line-total UPDATE at :906",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-083",
      "code_ref": "packages/db/src/queries/contract-signing.ts:123",
      "fn": "signContract",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/sign.ts:127-135 — signContractByToken after getSignatoryByToken(token); signatoryId from token lookup",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-084",
      "code_ref": "packages/db/src/queries/contract-signing.ts:147",
      "fn": "signContract",
      "class": "intentional-public-system",
      "caller_evidence": "signContract contract UPDATE at contract-signing.ts:147 after signatory token resolution in sign.ts",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-085",
      "code_ref": "packages/db/src/queries/contract-signing.ts:179",
      "fn": "declineContract",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/sign.ts:167-183 — declineContract after getSignatoryByToken; signatoryId from token row",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-086",
      "code_ref": "packages/db/src/queries/time-approval.ts:288",
      "fn": "rejectTimeEntries",
      "class": "defense-in-depth-only",
      "caller_evidence": "rejectTimeEntries UPDATE at time-approval.ts:288 — duplicate of ti-045; upstream tenant SELECT at :265-270",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-087",
      "code_ref": "packages/db/src/queries/recurring-invoices.ts:408",
      "fn": "advanceRecurringTemplate",
      "class": "defense-in-depth-only",
      "caller_evidence": "apps/zync-api/src/routes/cron/recurring-invoice-generator.ts:74,93,151 — advanceRecurringTemplate from listDueRecurringTemplates cron; manual path also tenant-scoped (ti-036)",
      "recommended": "add predicate (DiD)"
    },
    {
      "id": "ti-088",
      "code_ref": "packages/db/src/queries/recurring-expenses.ts:260",
      "fn": "generateDueExpenses",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/cron/recurring-expense-generator.ts:54 — generateDueExpenses(db,tenantId) iterates tenant-scoped due templates before template UPDATE",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-089",
      "code_ref": "packages/db/src/queries/proposals.ts:616",
      "fn": "recordProposalView",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/proposals-public.ts:78-92 — recordProposalView after getProposalByPublicToken(token); proposalId from token lookup",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-090",
      "code_ref": "packages/db/src/queries/proposals.ts:631",
      "fn": "recordProposalView",
      "class": "intentional-public-system",
      "caller_evidence": "recordProposalView UPDATE inside public GET /:token/public after token-resolved proposal",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-091",
      "code_ref": "packages/db/src/queries/proposals.ts:653",
      "fn": "acceptProposalByToken",
      "class": "intentional-public-system",
      "caller_evidence": "apps/zync-api/src/routes/proposals-public.ts:149-157 — acceptProposalByToken after getProposalByPublicToken",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-092",
      "code_ref": "packages/db/src/queries/proposals.ts:660",
      "fn": "acceptProposalByToken",
      "class": "intentional-public-system",
      "caller_evidence": "acceptProposalByToken UPDATE in public POST /:token/accept after token lookup",
      "recommended": "leave + annotate"
    },
    {
      "id": "ti-093",
      "code_ref": "packages/db/src/queries/proposals.ts:671",
      "fn": "rejectProposalByToken",
      "class": "intentional-public-system",
      "caller_evidence": "rejectProposalByToken SELECT in public flow at proposals-public.ts:194-201 after getProposalByPublicToken",
      "recommended": "leave + annotate"
    }
  ],
  "summary": {
    "live": 16,
    "did": 46,
    "intentional": 31,
    "total": 93
  }
}
