Configurable agreement staleness — selective inspection triggers (SE)

Objective

  • Introduce a company- and/or template-level control over when signed agreements are marked stale (isStale), so users like SE Region can keep agreements current for report lock without treating charge/service-only edits as invalidating a signature.
  • Primary focus: a selective inspection triggers mode — stale checks (or stale outcomes) apply when template/legal-relevant inspection facts change likely includes property/address, people, datetime, and always template body/version changes), but not for edits that are only charge/service line items.
  • Lower priority for this task: an additional mode where staleness tracks template version only (inspection edits never drive stale via the worker). Worth documenting as a follow-up if the same setting enum leaves room, but not the main deliverable.
  • Preserve today’s default for companies/templates that do not opt in (no surprise behavior change).

Background

  • SE (and similar operators) write agreements so that service selections are not meant to void the deal; today the product still marks agreements outdated whenever regenerated resolved text would differ from signedContentHash, which commonly happens after charges change because stale checks run from inspection and charge streams.
  • There is no “SE region” branch in code today — behavior is global for Attik-created jobs (jobCreationType === 'attik' in the stale worker). The ask is a configurable policy, not a hard-coded region exception.
  • How it works now (symptoms): queueAgreementStaleCheck debounces ~30s then agreementStaleWorker recomputes merged content and flips isStale when the hash diverges. Report unlock uses agreementLockSync: only non-stale active agreements count as signed for lock purposes, so all active rows stale leaves reports locked even if something is still “signed” on paper.
  • Transcript case: agreement signed → services changed → replacement signed → prior agreement archived → client/report access still wrong. That can align with stale-only lock math or timing; fixing charge-driven staleness for opted-in templates may reduce incidence, but any remaining archive/replace edge should be validated with repro data after policy work.
  • Original context and team thread: Message from ryan — Slack.

Scope

Backend

  • Stale queue sources: charge lifecycle in src/events/streamHandlers/chargeStream.ts (insert/update/delete paths that call queueAgreementStaleCheck), and inspection updates in src/events/streamHandlers/inspectionStream.ts (e.g. datetime reschedule, charges / charges.*, people, address/property — the latter two are already gated so they do not double-queue when hasChargesChange is true). Policy work will need a clear rule for which paths still enqueue a stale check under an opted-in template/company.
  • Stale computation: src/events/bullmq/agreementStaleWorker.ts (queueAgreementStaleCheck, processStaleCheck, merge + signedContentHash / computeSignedContentHash flow) and helpers such as mergeAgreementBlocksToContent and resolveAgreementTextForStaleCheck imported there — today the worker compares full resolved text; selective behavior may mean skipping the job, no-oping when the only pending change class is charge-only, or narrowing what invalidates, depending on the approach the implementer chooses.
  • Persistence: agreement shape and staleness fields live in src/models/agreementSchema.ts (isStale, staleAt, signedContentHash, template link via _agreementTemplateId). Template documents use src/models/agreementTemplateSchema.ts (timestamps exist for versioning-style signals; no dedicated “version” field beyond normal doc evolution — decision needed on how to represent policy and template identity).
  • Lock and inspection sync: src/util/functions/agreements/agreementLockSync.ts encodes the non-stale-only signed rule used by src/util/functions/inspection/checkReportAccess.ts and by src/events/streamHandlers/agreementStream.ts when syncing inspection.agreement.signed / present. If product changes when rows count as stale vs signed, keep portal and API behavior consistent with those rules.
  • Tests: existing coverage touches this area in tests/unit/agreementLockSync.test.ts and tests/integration/lockReport.test.ts — extend or add cases when behavior flags change.

Frontend

  • Portal report lock logic is intended to mirror backend: attik-frontend/src/util/functions/report/computeAgreementSignedForReportLock.ts (see cross-repo note in agreementLockSync.ts). Any change to what counts as signed for lock must stay aligned with backend and be regression-tested in the portal paths that consume the same semantics.

Mobile

  • No specific files were audited for this story; confirm if any mobile client duplicates agreement lock rules before shipping.

References

  • Message from ryan — Slack
  • Backend stale pipeline: src/events/bullmq/agreementStaleWorker.ts, src/events/streamHandlers/inspectionStream.ts, src/events/streamHandlers/chargeStream.ts
  • Lock math: src/util/functions/agreements/agreementLockSync.ts, src/util/functions/inspection/checkReportAccess.ts, src/events/streamHandlers/agreementStream.ts
  • Portal parity: attik-frontend/src/util/functions/report/computeAgreementSignedForReportLock.ts

Please authenticate to join the conversation.

Upvoters
Status

Canceled

Board
🏠

Main App

Date

26 days ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.