@happyvertical/smrt-timesheets 0.56.2 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/AGENTS.md +9 -3
  2. package/README.md +34 -5
  3. package/agents/period-rollup.md +13 -0
  4. package/dist/attendance/correction-service.d.ts +50 -9
  5. package/dist/attendance/correction-service.d.ts.map +1 -1
  6. package/dist/attendance/models.d.ts +6 -0
  7. package/dist/attendance/models.d.ts.map +1 -1
  8. package/dist/attendance/segments.d.ts.map +1 -1
  9. package/dist/attendance/service.d.ts +7 -1
  10. package/dist/attendance/service.d.ts.map +1 -1
  11. package/dist/attendance.js +2 -2
  12. package/dist/chunks/{attendance-mk6nqJhX.js → attendance-DAitZkht.js} +6 -6
  13. package/dist/chunks/attendance-DAitZkht.js.map +1 -0
  14. package/dist/chunks/{attendance-CDfhph1R.js → attendance-UY7sTXFx.js} +192 -38
  15. package/dist/chunks/attendance-UY7sTXFx.js.map +1 -0
  16. package/dist/chunks/{models-C1krjn40.js → models-BJcnFMOL.js} +14 -3
  17. package/dist/chunks/models-BJcnFMOL.js.map +1 -0
  18. package/dist/chunks/{rollup-C8p1PAXX.js → rollup-B-qdcGDs.js} +15 -7
  19. package/dist/chunks/rollup-B-qdcGDs.js.map +1 -0
  20. package/dist/chunks/service-time-entry-CWAVYIID.js +286 -0
  21. package/dist/chunks/service-time-entry-CWAVYIID.js.map +1 -0
  22. package/dist/chunks/{time-entry-actions-Bp--TxUD.js → time-entry-actions-BgaZCLL4.js} +2 -2
  23. package/dist/chunks/{time-entry-actions-Bp--TxUD.js.map → time-entry-actions-BgaZCLL4.js.map} +1 -1
  24. package/dist/index.js +5 -5
  25. package/dist/manifest.json +58 -5
  26. package/dist/rollup/attendance.d.ts +2 -2
  27. package/dist/rollup/attendance.d.ts.map +1 -1
  28. package/dist/rollup/calculation.d.ts +7 -1
  29. package/dist/rollup/calculation.d.ts.map +1 -1
  30. package/dist/rollup/service.d.ts.map +1 -1
  31. package/dist/rollup/types.d.ts +10 -0
  32. package/dist/rollup/types.d.ts.map +1 -1
  33. package/dist/rollup.d.ts +1 -1
  34. package/dist/rollup.d.ts.map +1 -1
  35. package/dist/rollup.js +1 -1
  36. package/dist/smrt-knowledge.json +25 -10
  37. package/package.json +7 -7
  38. package/dist/chunks/attendance-CDfhph1R.js.map +0 -1
  39. package/dist/chunks/attendance-mk6nqJhX.js.map +0 -1
  40. package/dist/chunks/models-C1krjn40.js.map +0 -1
  41. package/dist/chunks/rollup-C8p1PAXX.js.map +0 -1
  42. package/dist/chunks/service-time-entry-Cxh0pt0J.js +0 -286
  43. package/dist/chunks/service-time-entry-Cxh0pt0J.js.map +0 -1
@@ -3,14 +3,14 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-timesheets",
6
- "packageVersion": "0.56.2",
6
+ "packageVersion": "0.57.1",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "758a908da24db7967e18896d23983514e45a4c5ee3df46e83113176bed939839",
11
- "packageJson": "c349b624889d708bcfded901c47cb4fa444d32752994417f6809c27f4f4539b2",
12
- "agents": "67979245cd75faa81e381084a73ab20fd166fbd875345a8c01e3a3da2ec8ffaf",
13
- "moduleDoc:agents/period-rollup.md": "7e693235a49e8354a8910f480cde8a7aecdc4f28afaa6d778fd4bfaf67119b46"
10
+ "manifest": "4ce42ca297b4b05dc59144797b9526083b24191abc61ca1f917b3baaa289d587",
11
+ "packageJson": "09b722edca68309d93e96869b1678fc195096ad38c3333b8b3d7cb64af625b05",
12
+ "agents": "6771f7afb9dcff79c1d649519dd85538691c0f2753df321dbc3c1c9051c9a1bd",
13
+ "moduleDoc:agents/period-rollup.md": "68a7427e9d15815e04e8e8e77f52041224c96dd82f93712f741d471c81feada6"
14
14
  },
15
15
  "exports": [
16
16
  ".",
@@ -59,7 +59,7 @@
59
59
  "timesheets"
60
60
  ],
61
61
  "risks": [
62
- "cross-package-refs:15",
62
+ "cross-package-refs:16",
63
63
  "sensitive-fields-excluded"
64
64
  ],
65
65
  "objects": [
@@ -630,6 +630,13 @@
630
630
  "related": "AttendancePunch",
631
631
  "columnType": "UUID"
632
632
  },
633
+ {
634
+ "name": "profileId",
635
+ "type": "crossPackageRef",
636
+ "required": false,
637
+ "related": "@happyvertical/smrt-profiles:Profile",
638
+ "columnType": "UUID"
639
+ },
633
640
  {
634
641
  "name": "workRefType",
635
642
  "type": "text",
@@ -713,6 +720,13 @@
713
720
  "required": false,
714
721
  "related": "AttendancePunch",
715
722
  "columnType": "UUID"
723
+ },
724
+ {
725
+ "name": "profileId",
726
+ "type": "crossPackageRef",
727
+ "required": false,
728
+ "related": "@happyvertical/smrt-profiles:Profile",
729
+ "columnType": "UUID"
716
730
  }
717
731
  ],
718
732
  "methods": [
@@ -793,6 +807,7 @@
793
807
  }
794
808
  ],
795
809
  "relationshipFeatures": [
810
+ "crossPackageRef",
796
811
  "foreignKey",
797
812
  "uuidColumns"
798
813
  ],
@@ -2641,18 +2656,18 @@
2641
2656
  "prompts": [],
2642
2657
  "relationshipsV2": {
2643
2658
  "foreignKeyFields": 9,
2644
- "crossPackageRefFields": 15,
2659
+ "crossPackageRefFields": 16,
2645
2660
  "junctionCollections": 0,
2646
2661
  "hierarchicalObjects": 0,
2647
2662
  "polymorphicAssociations": 0,
2648
- "uuidColumns": 92
2663
+ "uuidColumns": 94
2649
2664
  },
2650
- "agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n`AttendanceSegment` (`attendance_segments`) is a running work interval inside a\npunch: queryable `workRef*`/`taskRef*`/`kind` columns, one open per punch\n(`openSlot`), closed by `startBreak`/`punchOut`/`autoClose`, never overlapping\nbreaks or each other, immutable once closed. It creates no `ServiceTimeEntry`.\nClosed punches, breaks and segments change only through\n`AttendanceCorrectionService` (`attendance/correction-service.ts`), which sets a\nper-instance capability (`attendance/write.ts`, not exported) that lifts the\nmodels' closed-interval checks for one save, validates the whole punch layout,\nrecomputes durations and appends an `AttendanceCorrection` (append-only ledger,\nno generated operations; only a first acknowledgement by the subject may update\na row). Never export `write.ts`. Punch `source`/`deviceRef*` and break `kind` are immutable except through corrections; `autoCloseReason` is write-once via `persistAutoClose`. A confirmed `PeriodTimecard` is honoured by the\nopt-in `confirmedTimecardGuard` (`rollup/correction-guard.ts`), which refuses\nwhile a card is submitted or confirmed. `PeriodRollupService.reopen()` returns\nsuch a card to `open` and appends a `TimecardReopening` (`timecard_reopenings`,\nappend-only, service-only via `rollup/write.ts`); `submit()` is the optional\nopen -> submitted step (`requireSubmission` makes it mandatory) (#3950).\nSegment transitions live in `attendance/segments.ts`; compose them with other\nattendance writes via `AttendanceService.withTransaction`. Any new attendance\ntable must be added to the `includeObjects` lists in the `*.optional.test.ts`\nfiles.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Generated write actions (#4001)\n\nGenerated CRUD writes stay closed. `ServiceTimeEntryCollection` hosts the only\ngenerated writes, `record` / `submit` / `approve` (`POST\n/servicetimeentries/<action>`, body `{ entryId }` for submit/approve), on REST\nonly; MCP and CLI stay read-only until per-operation MCP authorization (#4004).\n`services/time-entry-actions.ts` delegates each to `ServiceEvidenceService`, so\nthe trail and snapshots are set in one place. `services/action-principal.ts`\ntakes identity from the tenancy ALS, then the smrt-users session ALS, never the\nbody; authorizes `servicetimeentries.<action>` with `assertOperationPermission`\n(system context never bypasses; `approve` refuses super-admin bypass); and\nstamps the user's linked profile. Only the participant records/submits their\nown time unless the caller holds `approve`. `approve` prices with the host's\n`setServiceTimeEntryCommercialResolver()` — a factory called per request with\nthe collection's options, so a writing resolver shares the request executor;\nwithout one it is a 409 and writes nothing. The runtime REST transport gates\na collection-hosted action on the ITEM's `api.include`, SvelteKit on the\ncollection's, so both list the actions;\na consumer closing them overrides both registrations. The collection and both\nsnapshot collections carry their own `@smrt()` surface: undecorated, they\npublished generated create/update/delete MCP tools. `reject` waits for #3994.\n\n## Recipe\n\n`src/recipes.ts` declares one `SmrtRecipe`, `timesheets.time-entries`\n(label \"Time entries\", section `work`), emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`. It shows\n`ServiceTimeEntry` alone, with the generic work reference, evidence, agent\nand approval-trail fields hidden; attendance and rollup models stay out of\nit. `status` and its trail are hidden and locked, and the help never says to\nset them: `save()` accepts a bare transition, so submit and approve must go\nthrough `ServiceEvidenceService` (the recipe declares no surface for that). Help lives in `src/time-entries.recipe.md` (#3591); keep it and the recipe\nin plain language. See [core recipes](../core/agents/recipes.md).\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\nDisplay components keep the legacy one-decimal hours format by default. A\nconsumer that retains more precise source evidence supplies `hoursFormatter`;\ncard/list/queue detail snippets carry domain evidence without widening the base\nview. ApprovalQueue's optional action snippet replaces its callback controls so\nthe host can own a native form, request identity, authorization and retry flow.\nDisplay currency codes are open ISO strings and amounts remain integer minor\nunits; never put private rates into a view that should not expose them.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n\nReplay hashes the tap AS SENT (action, device `at`, paid, work, and `source`/`device`/`kind` only when present, so older receipts still match) so retries match;\n`replay(taps, { resolveAt, now, refusal })` only changes the stored time and\nreason. Never hash the resolved time. Work actions (`startWork`, `switchWork`,\n`stopWork`, `resumeWork`) run in the tap's transaction via `apply()`. Refusals store\n`code` from `AttendanceConflict.code`; give new conflicts a code.\n",
2665
+ "agentDoc": "# smrt-timesheets\n\nShared time entries: who (a `smrt-profiles` Profile, or an agent) worked how\nlong on what (a generic work reference), whether it was approved, and the\nimmutable charge / compensation snapshots taken at approval. Moved out of\n`smrt-projects` / `smrt-support` in #3288 so shops, builders, and service\ndesks share one model instead of re-implementing it.\n\nThis package contains **layer 1 (entry)**, **layer 2 (attendance)**, and\n**layer 3 (period rollup)**.\n`src/attendance/` owns punches, breaks, atomic transitions and durable offline\nreceipts; `./attendance` is the public subpath. Period rollup belongs in this\npackage too, never projects or support. See the README attendance contract.\nAttendance services accept trusted session actors, serialize mutations per\nactor, and bind every write to one executor. The new tables use non-null unique\nopen slots, not nullable unique keys. A linked entry is draft evidence, and\nrollups must deduplicate `AttendancePunch.serviceTimeEntryId`.\n`AttendanceSegment` (`attendance_segments`) is a running work interval inside a\npunch: queryable `workRef*`/`taskRef*`/`kind` columns, one open per punch\n(`openSlot`), closed by `startBreak`/`punchOut`/`autoClose`, never overlapping\nbreaks or each other, immutable once closed. It creates no `ServiceTimeEntry`.\nClosed punches, breaks and segments change only through\n`AttendanceCorrectionService` (`attendance/correction-service.ts`), which sets a\nper-instance capability (`attendance/write.ts`, not exported) that lifts the\nmodels' closed-interval checks for one save, validates the whole punch layout,\nrecomputes durations and appends an `AttendanceCorrection` (append-only ledger,\nno generated operations; only a first acknowledgement by the subject may update\na row). An open punch accepts only a `startedAt` correction, closed breaks/segments inside it, and `addPunch({ open: true })`; the open break/segment and closing stay `AttendanceService`'s. Never export `write.ts`. Punch `source`/`deviceRef*` and break `kind` are immutable except through corrections; `autoCloseReason` is write-once via `persistAutoClose`. A confirmed `PeriodTimecard` is honoured by the\nopt-in `confirmedTimecardGuard` (`rollup/correction-guard.ts`), which refuses\nwhile a card is submitted or confirmed. `PeriodRollupService.reopen()` returns\nsuch a card to `open` and appends a `TimecardReopening` (`timecard_reopenings`,\nappend-only, service-only via `rollup/write.ts`); `submit()` is the optional\nopen -> submitted step (`requireSubmission` makes it mandatory) (#3950).\n`PeriodRollupOptions.attribution` (`'clip'` default | `'start'`) decides crossing\nsources: `'start'` keeps a punch (breaks and segments included) or bounded entry\nwhole in the period containing its start, unclipped, and excludes it from the\nnext (`attributeSource` in `rollup/calculation.ts`) (#4028).\nSegment transitions live in `attendance/segments.ts`; compose them with other\nattendance writes via `AttendanceService.withTransaction`. Any new attendance\ntable must be added to the `includeObjects` lists in the `*.optional.test.ts`\nfiles.\n\n## Models\n\n| Model | Table | Notes |\n|---|---|---|\n| `ServiceTimeEntry` | `service_time_entries` | `workRefType` + `workRefId`, `participantKind` (`human` \\| `agent`), `participantProfileId` / `agentRef`, `source`, `startedAt` / `endedAt` / `durationSeconds`, status machine, `correctionOfId` |\n| `ServiceChargeSnapshot` | `service_charge_snapshots` | Client charge, integer minor units (#2401), one per entry |\n| `ServiceCompensationSnapshot` | `service_compensation_snapshots` | Provider earning, integer minor units, one per entry |\n\n`ServiceEvidenceService` records, submits, approves (writing both snapshots\nthrough a `ServiceCommercialResolver`), and corrects. `smrt-projects` supplies\n`SubscriptionServiceCommercialResolver` for #1925 pricing.\n\n## Generated write actions (#4001)\n\nGenerated CRUD writes stay closed. `ServiceTimeEntryCollection` hosts the only\ngenerated writes, `record` / `submit` / `approve` (`POST\n/servicetimeentries/<action>`, body `{ entryId }` for submit/approve), on REST\nonly; MCP and CLI stay read-only until per-operation MCP authorization (#4004).\n`services/time-entry-actions.ts` delegates each to `ServiceEvidenceService`, so\nthe trail and snapshots are set in one place. `services/action-principal.ts`\ntakes identity from the tenancy ALS, then the smrt-users session ALS, never the\nbody; authorizes `servicetimeentries.<action>` with `assertOperationPermission`\n(system context never bypasses; `approve` refuses super-admin bypass); and\nstamps the user's linked profile. Only the participant records/submits their\nown time unless the caller holds `approve`. `approve` prices with the host's\n`setServiceTimeEntryCommercialResolver()` — a factory called per request with\nthe collection's options, so a writing resolver shares the request executor;\nwithout one it is a 409 and writes nothing. The runtime REST transport gates\na collection-hosted action on the ITEM's `api.include`, SvelteKit on the\ncollection's, so both list the actions;\na consumer closing them overrides both registrations. The collection and both\nsnapshot collections carry their own `@smrt()` surface: undecorated, they\npublished generated create/update/delete MCP tools. `reject` waits for #3994.\n\n## Recipe\n\n`src/recipes.ts` declares one `SmrtRecipe`, `timesheets.time-entries`\n(label \"Time entries\", section `work`), emitted into the built\n`manifest.json` and `smrt-knowledge.json` `recipes`. It shows\n`ServiceTimeEntry` alone, with the generic work reference, evidence, agent\nand approval-trail fields hidden; attendance and rollup models stay out of\nit. `status` and its trail are hidden and locked, and the help never says to\nset them: `save()` accepts a bare transition, so submit and approve must go\nthrough `ServiceEvidenceService` (the recipe declares no surface for that). Help lives in `src/time-entries.recipe.md` (#3591); keep it and the recipe\nin plain language. See [core recipes](../core/agents/recipes.md).\n\n## Invariants\n\n- **No domain foreign key on the base.** Work context is the generic\n `workRefType` (qualified class name) + `workRefId`. A domain adds its own\n context by subclassing over the same table — smrt-support's\n `ServiceTimeEntry` adds `caseId` / `specialistId` and extends\n `frozenFieldNames()` so they freeze on approval.\n- **Entries carry no rate.** Money lives only on the snapshots, `= 0`\n integer initializers (INTEGER columns). Never `= 0.0`. Snapshot `save()`\n rejects an `amount` that is not a safe integer (`VALIDATION_INVALID_VALUE`),\n so SQLite cannot silently store `19.99`; fix a fractional resolver at its\n source rather than rounding here.\n- **Approved evidence never changes.** `save()` enforces\n `SERVICE_TIME_ENTRY_STATUS_TRANSITIONS` and compares the frozen fields with\n the stored row. A correction is a new row (`correctionOfId`), the original\n flips to `corrected`. Snapshots are immutable after their first save.\n- **Old qualified names are aliases.** Each model declares its pre-#3288\n `@happyvertical/smrt-projects:*` name in `previousQualifiedNames` (#3338;\n `packages/core/agents/model-moves.md`). smrt-projects must never list these\n classes in its manifests again (`CONFIG_QUALIFIED_NAME_ALIAS_COLLISION`),\n and no subtype (smrt-support's, or a consumer's closing subclass) may\n redeclare an alias (two claimants). Known gap: a same-named subtype that\n replaces a moved class drops that class's alias, and the replaced class's\n own `@happyvertical/smrt-timesheets:*` name stops resolving too — guidance\n for apps with smrt-support must name `@happyvertical/smrt-support:ServiceTimeEntry`.\n Support's vitest environment keeps both classes registered, so verify this\n against built packages, not package tests. `projects/src/__tests__/legacy-qualified-identity.test.ts` pins\n the projects-only contract.\n- **Table names are a compatibility contract.** `service_time_entries` and the\n two snapshot tables hold existing production rows from smrt-projects /\n smrt-support; renaming any of them, or adding a discriminator, is a data\n migration.\n\n## One table family\n\nClasses may share a table only as one inheritance chain. Core resolves\nsame-named subclasses by constructor ancestry at runtime and qualified manifest\nancestry at build registration; the deepest subtype wins across N levels.\nSibling or unrelated types cannot share the table. `smrt-projects` re-exports\nthe base; `smrt-support` subclasses it directly.\n\nConsumers closing package routes should call\n`ObjectRegistry.registerOverride(qualifiedName, { api: false, mcp: false,\ncli: false, tenancy: { mode: 'required' } })` after importing the model and before\nconstructing transports. This tightens the existing registration without a new\nsubtype. See core's README for startup order, static route generation, and reset\nsemantics. `src/__tests__/consumer-closed-surface.test.ts` also covers existing\nmanifest subclass consumers. The snapshots' explicit `time_entry_id` conflict\nkey is not rewritten when a consumer requires tenancy; widening it remains a\nconsumer schema change.\n\nSubclasses restate their fields: package-isolated manifest scanning does not\nread dependency sources, so an unrestated subclass manifest has no columns.\n\n## Svelte UI\n\n`./svelte` exports `TimeEntryCard`, `TimeEntryList`, `TimeSummary`,\n`DurationDisplay`, and `TimeEntryApprovalQueue` (props-driven, plain view\nobjects, `smrt-ui` primitives, `smrtRawPrimitives: \"strict\"`). smrt-projects\nand smrt-support re-export them from their own `./svelte` subpaths.\nCard/list/summary message keys keep their `projects.*` namespace: tenant\nlanguage overrides are stored by key, and renaming would orphan them. New\nkeys use `timesheets.*`.\n\nDisplay components keep the legacy one-decimal hours format by default. A\nconsumer that retains more precise source evidence supplies `hoursFormatter`;\ncard/list/queue detail snippets carry domain evidence without widening the base\nview. ApprovalQueue's optional action snippet replaces its callback controls so\nthe host can own a native form, request identity, authorization and retry flow.\nDisplay currency codes are open ISO strings and amounts remain integer minor\nunits; never put private rates into a view that should not expose them.\n\n## Validation\n\n```sh\npnpm --filter @happyvertical/smrt-timesheets test\npnpm --filter @happyvertical/smrt-timesheets test:postgres\npnpm --filter @happyvertical/smrt-timesheets typecheck\npnpm --filter @happyvertical/smrt-timesheets build\npnpm --filter @happyvertical/smrt-projects test\npnpm --filter @happyvertical/smrt-support test\n```\n\nSchema-affecting edits must keep `service-time-entry-compat.test.ts` (projects,\nsupport) and `service-time-entry-migration-plan.test.ts` (support) green: a\npre-move table must plan no DDL.\n\n## Period rollup\n\nLayer 3 lives under `src/rollup/` and the `./rollup` subpath. See\n[period-rollup.md](agents/period-rollup.md) for resolver, source clipping,\nconfirmation, attributed adjustments, isolation, and transaction contracts.\nTimecards and adjustments have closed generated surfaces and service-only\nmodel writes; never bypass the instance capability with generic persistence.\n\nReplay hashes the tap AS SENT (action, device `at`, paid, work, and `source`/`device`/`kind` only when present, so older receipts still match) so retries match;\n`replay(taps, { resolveAt, now, refusal })` only changes the stored time, reason and the\npunch's `recordedOffline`/`reviewRequired` (both default true and stay set across\nlater taps; false never clears an earlier flag). A reviewed correction clears\n`reviewRequired`. Never hash the resolved time. Work actions (`startWork`, `switchWork`,\n`stopWork`, `resumeWork`) run in the tap's transaction via `apply()`. Refusals store\n`code` from `AttendanceConflict.code`; give new conflicts a code.\n",
2651
2666
  "moduleDocs": [
2652
2667
  {
2653
2668
  "path": "agents/period-rollup.md",
2654
2669
  "module": "period-rollup",
2655
- "content": "# Period timecards\n\n`@happyvertical/smrt-timesheets/rollup` exports `PeriodRollupService`,\n`PeriodTimecard`, `TimecardAdjustment`, their collections, and resolver types.\nThe package root also exports them. SQLite and PostgreSQL are supported.\nCreate application tables through normal migrations; neither service provisions\nschema. The attendance option additionally requires the attendance tables.\n\nConstruct a service only after authenticating and authorizing its trusted\n`{ tenantId, profileId }` scope. A conflicting ambient tenant is rejected.\n`options.actorProfileId` records a separately authorized supervisor; it defaults\nto the subject profile for self confirmation/correction. Never accept this\ntrusted scope or attribution unchecked from request input. Profile authorization\nis consumer-owned; the service filters every source/card/adjustment read by its\ntrusted subject and tenant. Generic API, CLI and MCP surfaces are closed by\ndefault; consumer registry overrides may further restrict them (#3318).\n\n## Resolver contract\n\n`periodFor(at, actor)` supplies half-open UTC `startsAt`/`endsAt`, an IANA\n`timezone`, a nonempty policy `version`, and a JSON `rules` snapshot.\nThe consumer owns period length, calendar start day, timezone and DST, overtime\nthresholds, holidays and classification. The requested instant must be inside\nthe window. Overlapping windows for existing cards are rejected: introduce a new\ncalendar at a boundary after existing windows, and use adjustments for already\nconfirmed periods.\n\n`classify(period, sources, actor)` returns nonnegative safe integer\n`regularSeconds` and `overtimeSeconds`, whose sum must equal source seconds.\nInputs are detached so resolver mutation cannot rewrite evidence. Resolver\nexceptions, malformed windows, invalid timezone, fractional/unsafe totals and\nnonconserving classification abort the transaction. No Alberta or other\njurisdiction rule is assumed, and no pay rate or money is stored.\n\nA configurable UTC calendar example (illustrative policy, not payroll advice):\n\n```ts\nimport { PeriodRollupService, type PeriodRulesResolver } from\n '@happyvertical/smrt-timesheets/rollup';\n\nconst settings = {\n lengthDays: 14, startDay: 1, timezone: 'UTC', // 0 = Sunday\n anchor: '2026-01-05T00:00:00Z', // aligned Monday; anchors fortnight phase\n thresholdSeconds: 80 * 3600,\n holidays: ['2026-12-25'],\n};\nconst resolver: PeriodRulesResolver = {\n async periodFor(at) {\n const anchor = new Date(settings.anchor);\n if (anchor.getUTCDay() !== settings.startDay || settings.timezone !== 'UTC')\n throw new Error('Use a timezone-aware calendar resolver for local periods');\n const length = settings.lengthDays * 86400000;\n const start = anchor.getTime() +\n Math.floor((at.getTime() - anchor.getTime()) / length) * length;\n return {\n startsAt: new Date(start), endsAt: new Date(start + length),\n timezone: settings.timezone, version: 'shop-policy-v1', rules: settings,\n };\n },\n async classify(period, sources) {\n const rules = period.rules as typeof settings;\n // This example assigns a contribution to its clipped start's UTC date.\n // Local daily rules should split contributions at their own local midnight.\n let regular = 0;\n let holiday = 0;\n for (const source of sources) {\n if (rules.holidays.includes(source.startsAt.slice(0, 10))) holiday += source.seconds;\n else regular += source.seconds;\n }\n return {\n regularSeconds: Math.min(regular, rules.thresholdSeconds),\n overtimeSeconds: holiday + Math.max(0, regular - rules.thresholdSeconds),\n };\n },\n};\nconst rollups = new PeriodRollupService(db, authorizedSubject, resolver,\n { attendance: true, actorProfileId: authenticatedSupervisorProfileId });\nconst card = await rollups.rollup(new Date());\nawait rollups.confirm(card.id!);\nawait rollups.adjust(card.id!, {\n operationId: requestId, regularSeconds: 900, overtimeSeconds: 0,\n reason: 'Approved correction after confirmation',\n});\nconst effectiveSeconds = await rollups.totals(card.id!);\n```\n\nA timezone-aware resolver must compute each local boundary separately: a local\nday crossing DST is not necessarily 86400 seconds. Resolvers can use an app's\nexisting calendar library; no new timezone dependency or implicit default is\nintroduced here.\n\n## Evidence and immutability\n\nDecimal-hours-only entries have no measured integer seconds. The seconds-only\nrollup rejects these sources explicitly when their approval instant falls in the\nrequested period; it never rounds them or counts null as\nzero. Use an hours-aware consumer policy for that source representation.\n\nOnly approved human `ServiceTimeEntry` rows for the subject count; corrected\nancestors do not. Correction chains must remain within that subject/tenant and\npoint to corrected parents. Multiple approved descendants of one root are\nrejected as ambiguous rather than double-counted. Entries with both `startedAt`\nand `endedAt` allocate their declared seconds\nproportionally across the period using exact integer prefix division, preserving\nseconds over adjacent periods. Unbounded entries count at `startedAt`, or, when\nabsent, `approvedAt`; an end-only timestamp does not create an interval. Missing\nboth attribution timestamps is an error. This is time attribution, not a\nclaim that a manual entry has a continuous wall-clock duration.\n\nWith `attendance: true`, closed punches without `reviewRequired` count, excluding\nunpaid breaks and retaining paid breaks. Open/review-required punches do not\ncount. Linked attendance is authoritative: its evidence entry and any approved\ncorrection descendants are excluded, including when the punch is still under\nreview or outside the requested period. Unlinked approved work entries and\nattendance both count; consumers must link overlapping evidence explicitly.\nSource snapshots preserve each paid attendance segment and entry identity.\n\nRepeated rollup updates one open card per tenant/person/start; a confirmed card\ncannot be recomputed. Confirmation is attributed and idempotent. Model save,\ncollection create/update/upsert and object/collection delete cannot bypass the\nservice. Model mutation capabilities are per-instance and not exported.\nDirect SQL is a trusted administrative boundary, not an exposed application API.\nProfile/card foreign keys restrict deletion to preserve history.\n\nAdjustments contain signed integer regular/overtime deltas, reason, operation\nkey and actor attribution. A retry reuses its row only for the exact same\npayload and actor. Effective totals may never become negative or unsafe.\nAdjustments never alter the confirmed row or source snapshot. Undo an adjustment\nwith a separately attributed compensating adjustment, never deletion.\n\nAll service mutations use a root transaction, transaction-bound collections,\nembedded writer serialization or a PostgreSQL tenant/person advisory lock.\nOutputs are reloaded on the root executor after commit. Rollup reads are an\napplication snapshot: subsequent source approvals/corrections appear on the\nnext open rollup, and confirmed cards require explicit adjustments.\n\n## Lifecycle: submit, confirm, reopen (#3950)\n\n`open` (draft) -> `submitted` (optional) -> `confirmed`. `submit(id)` freezes an\nopen card for review; `confirm(id)` accepts an open card too unless the service\nis built with `{ requireSubmission: true }`. Submitted and confirmed cards are\nnot re-rolled.\n\n`reopen(id, { operationId, reason })` returns a submitted or confirmed card to\n`open` (clearing its submission/confirmation attribution) and appends a\n`TimecardReopening`: actor, reason, prior status and a JSON snapshot of the\nprevious base, adjustment and effective seconds (`getPrevious()`). It is\nidempotent per `operationId` and refuses a blank reason. `reopenings(id)` lists\nthe history. The consumer authorizes the actor; give reopening its own\npermission. Existing adjustments stay and keep counting in `totals()`: after a\nre-roll, offset ones the correction made redundant with a new adjustment.\n`confirmedTimecardGuard` refuses corrections while a card is submitted or\nconfirmed and allows them once it is reopened. The new `timecard_reopenings`\ntable, and nullable `submitted_at`/`submitted_by_profile_id` columns on\n`period_timecards`, need a migration.\n"
2670
+ "content": "# Period timecards\n\n`@happyvertical/smrt-timesheets/rollup` exports `PeriodRollupService`,\n`PeriodTimecard`, `TimecardAdjustment`, their collections, and resolver types.\nThe package root also exports them. SQLite and PostgreSQL are supported.\nCreate application tables through normal migrations; neither service provisions\nschema. The attendance option additionally requires the attendance tables.\n\nConstruct a service only after authenticating and authorizing its trusted\n`{ tenantId, profileId }` scope. A conflicting ambient tenant is rejected.\n`options.actorProfileId` records a separately authorized supervisor; it defaults\nto the subject profile for self confirmation/correction. Never accept this\ntrusted scope or attribution unchecked from request input. Profile authorization\nis consumer-owned; the service filters every source/card/adjustment read by its\ntrusted subject and tenant. Generic API, CLI and MCP surfaces are closed by\ndefault; consumer registry overrides may further restrict them (#3318).\n\n## Resolver contract\n\n`periodFor(at, actor)` supplies half-open UTC `startsAt`/`endsAt`, an IANA\n`timezone`, a nonempty policy `version`, and a JSON `rules` snapshot.\nThe consumer owns period length, calendar start day, timezone and DST, overtime\nthresholds, holidays and classification. The requested instant must be inside\nthe window. Overlapping windows for existing cards are rejected: introduce a new\ncalendar at a boundary after existing windows, and use adjustments for already\nconfirmed periods.\n\n`classify(period, sources, actor)` returns nonnegative safe integer\n`regularSeconds` and `overtimeSeconds`, whose sum must equal source seconds.\nInputs are detached so resolver mutation cannot rewrite evidence. Resolver\nexceptions, malformed windows, invalid timezone, fractional/unsafe totals and\nnonconserving classification abort the transaction. No Alberta or other\njurisdiction rule is assumed, and no pay rate or money is stored.\n\nA configurable UTC calendar example (illustrative policy, not payroll advice):\n\n```ts\nimport { PeriodRollupService, type PeriodRulesResolver } from\n '@happyvertical/smrt-timesheets/rollup';\n\nconst settings = {\n lengthDays: 14, startDay: 1, timezone: 'UTC', // 0 = Sunday\n anchor: '2026-01-05T00:00:00Z', // aligned Monday; anchors fortnight phase\n thresholdSeconds: 80 * 3600,\n holidays: ['2026-12-25'],\n};\nconst resolver: PeriodRulesResolver = {\n async periodFor(at) {\n const anchor = new Date(settings.anchor);\n if (anchor.getUTCDay() !== settings.startDay || settings.timezone !== 'UTC')\n throw new Error('Use a timezone-aware calendar resolver for local periods');\n const length = settings.lengthDays * 86400000;\n const start = anchor.getTime() +\n Math.floor((at.getTime() - anchor.getTime()) / length) * length;\n return {\n startsAt: new Date(start), endsAt: new Date(start + length),\n timezone: settings.timezone, version: 'shop-policy-v1', rules: settings,\n };\n },\n async classify(period, sources) {\n const rules = period.rules as typeof settings;\n // This example assigns a contribution to its clipped start's UTC date.\n // Local daily rules should split contributions at their own local midnight.\n let regular = 0;\n let holiday = 0;\n for (const source of sources) {\n if (rules.holidays.includes(source.startsAt.slice(0, 10))) holiday += source.seconds;\n else regular += source.seconds;\n }\n return {\n regularSeconds: Math.min(regular, rules.thresholdSeconds),\n overtimeSeconds: holiday + Math.max(0, regular - rules.thresholdSeconds),\n };\n },\n};\nconst rollups = new PeriodRollupService(db, authorizedSubject, resolver,\n { attendance: true, actorProfileId: authenticatedSupervisorProfileId });\nconst card = await rollups.rollup(new Date());\nawait rollups.confirm(card.id!);\nawait rollups.adjust(card.id!, {\n operationId: requestId, regularSeconds: 900, overtimeSeconds: 0,\n reason: 'Approved correction after confirmation',\n});\nconst effectiveSeconds = await rollups.totals(card.id!);\n```\n\nA timezone-aware resolver must compute each local boundary separately: a local\nday crossing DST is not necessarily 86400 seconds. Resolvers can use an app's\nexisting calendar library; no new timezone dependency or implicit default is\nintroduced here.\n\n## Evidence and immutability\n\nDecimal-hours-only entries have no measured integer seconds. The seconds-only\nrollup rejects these sources explicitly when their approval instant falls in the\nrequested period; it never rounds them or counts null as\nzero. Use an hours-aware consumer policy for that source representation.\n\nOnly approved human `ServiceTimeEntry` rows for the subject count; corrected\nancestors do not. Correction chains must remain within that subject/tenant and\npoint to corrected parents. Multiple approved descendants of one root are\nrejected as ambiguous rather than double-counted. Entries with both `startedAt`\nand `endedAt` allocate their declared seconds\nproportionally across the period using exact integer prefix division, preserving\nseconds over adjacent periods. Unbounded entries count at `startedAt`, or, when\nabsent, `approvedAt`; an end-only timestamp does not create an interval. Missing\nboth attribution timestamps is an error. This is time attribution, not a\nclaim that a manual entry has a continuous wall-clock duration.\n\nWith `attendance: true`, closed punches without `reviewRequired` count, excluding\nunpaid breaks and retaining paid breaks. Open/review-required punches do not\ncount. Linked attendance is authoritative: its evidence entry and any approved\ncorrection descendants are excluded, including when the punch is still under\nreview or outside the requested period. Unlinked approved work entries and\nattendance both count; consumers must link overlapping evidence explicitly.\nSource snapshots preserve each paid attendance segment and entry identity.\n\nRepeated rollup updates one open card per tenant/person/start; a confirmed card\ncannot be recomputed. Confirmation is attributed and idempotent. Model save,\ncollection create/update/upsert and object/collection delete cannot bypass the\nservice. Model mutation capabilities are per-instance and not exported.\nDirect SQL is a trusted administrative boundary, not an exposed application API.\nProfile/card foreign keys restrict deletion to preserve history.\n\nAdjustments contain signed integer regular/overtime deltas, reason, operation\nkey and actor attribution. A retry reuses its row only for the exact same\npayload and actor. Effective totals may never become negative or unsafe.\nAdjustments never alter the confirmed row or source snapshot. Undo an adjustment\nwith a separately attributed compensating adjustment, never deletion.\n\nAll service mutations use a root transaction, transaction-bound collections,\nembedded writer serialization or a PostgreSQL tenant/person advisory lock.\nOutputs are reloaded on the root executor after commit. Rollup reads are an\napplication snapshot: subsequent source approvals/corrections appear on the\nnext open rollup, and confirmed cards require explicit adjustments.\n\n## Boundary attribution (#4028)\n\nBy default (`attribution: 'clip'`) a source crossing a period boundary is\nprorated across every period it overlaps. Pass `{ attribution: 'start' }` for a\npay rule where a shift counts wholly in the period it started: an attendance\npunch (its breaks and segments follow it) or a bounded evidence entry belongs,\nunclipped, to the period containing its start and is excluded from the period\nit spills into. A Sunday 22:00 to Monday 06:00 shift with a Monday 00:00\nboundary rolls up as 8 h / 0 h under `'start'` and 2 h / 6 h under `'clip'`.\nSource `endsAt` may then lie past the period end. Use one attribution per\nperson: switching it for periods already confirmed does not re-roll them, and\nconfirmed or submitted cards behave as before (reopen, then roll up again).\n\n## Lifecycle: submit, confirm, reopen (#3950)\n\n`open` (draft) -> `submitted` (optional) -> `confirmed`. `submit(id)` freezes an\nopen card for review; `confirm(id)` accepts an open card too unless the service\nis built with `{ requireSubmission: true }`. Submitted and confirmed cards are\nnot re-rolled.\n\n`reopen(id, { operationId, reason })` returns a submitted or confirmed card to\n`open` (clearing its submission/confirmation attribution) and appends a\n`TimecardReopening`: actor, reason, prior status and a JSON snapshot of the\nprevious base, adjustment and effective seconds (`getPrevious()`). It is\nidempotent per `operationId` and refuses a blank reason. `reopenings(id)` lists\nthe history. The consumer authorizes the actor; give reopening its own\npermission. Existing adjustments stay and keep counting in `totals()`: after a\nre-roll, offset ones the correction made redundant with a new adjustment.\n`confirmedTimecardGuard` refuses corrections while a card is submitted or\nconfirmed and allows them once it is reopened. The new `timecard_reopenings`\ntable, and nullable `submitted_at`/`submitted_by_profile_id` columns on\n`period_timecards`, need a migration.\n"
2656
2671
  }
2657
2672
  ],
2658
2673
  "recipes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-timesheets",
3
- "version": "0.56.2",
3
+ "version": "0.57.1",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Shared time entries for the SMRT framework: who worked how long on what, approval and correction, with immutable charge and compensation snapshots",
6
6
  "type": "module",
@@ -39,11 +39,11 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@happyvertical/smrt-core": "0.56.2",
43
- "@happyvertical/smrt-tenancy": "0.56.2",
44
- "@happyvertical/smrt-types": "0.56.2",
45
- "@happyvertical/smrt-ui": "0.56.2",
46
- "@happyvertical/smrt-users": "0.56.2",
42
+ "@happyvertical/smrt-core": "0.57.1",
43
+ "@happyvertical/smrt-tenancy": "0.57.1",
44
+ "@happyvertical/smrt-types": "0.57.1",
45
+ "@happyvertical/smrt-ui": "0.57.1",
46
+ "@happyvertical/smrt-users": "0.57.1",
47
47
  "@happyvertical/sql": "^0.103.7"
48
48
  },
49
49
  "peerDependencies": {
@@ -55,7 +55,7 @@
55
55
  }
56
56
  },
57
57
  "devDependencies": {
58
- "@happyvertical/smrt-vitest": "0.56.2",
58
+ "@happyvertical/smrt-vitest": "0.57.1",
59
59
  "@sveltejs/package": "^2.5.8",
60
60
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
61
61
  "@types/node": "24.13.6",
@@ -1 +0,0 @@
1
- {"version":3,"file":"attendance-CDfhph1R.js","names":["unpaidMilliseconds","outcome"],"sources":["../../src/attendance/correction-models.ts","../../src/attendance/context.ts","../../src/attendance/segments.ts","../../src/attendance/service.ts","../../src/attendance/correction-service.ts"],"sourcesContent":["import {\n crossPackageRef,\n field,\n foreignKey,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { parseTimesheetJson } from '../models/json.js';\nimport { AttendancePunch } from './models.js';\nimport { isCorrectionWrite } from './write.js';\n\n/** What a correction did. Punch kinds target the punch itself. */\nexport const ATTENDANCE_CORRECTION_KINDS = [\n 'punch.corrected',\n 'punch.added',\n 'break.added',\n 'break.changed',\n 'break.removed',\n 'segment.added',\n 'segment.changed',\n 'segment.removed',\n] as const;\nexport type AttendanceCorrectionKind =\n (typeof ATTENDANCE_CORRECTION_KINDS)[number];\n\nconst IMMUTABLE_COLUMNS = [\n 'tenant_id',\n 'subject_profile_id',\n 'actor_profile_id',\n 'kind',\n 'punch_id',\n 'target_id',\n 'before_state',\n 'after_state',\n 'reason',\n] as const;\n\n/**\n * One append-only change to closed attendance: who changed whose time, why, and\n * the before/after values as JSON (`null` for an addition's before or a\n * removal's after). Written only by `AttendanceCorrectionService`, in the same\n * transaction as the change. After insert only the acknowledgement may be set,\n * once, by the subject; rows are never deleted.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_corrections',\n sensitive: true,\n indexes: [\n {\n name: 'attendance_corrections_punch_idx',\n columns: ['tenantId', 'punchId'],\n },\n {\n name: 'attendance_corrections_subject_idx',\n columns: ['tenantId', 'subjectProfileId'],\n },\n ],\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class AttendanceCorrection extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n /** Whose time was corrected. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n subjectProfileId = '';\n /** Who made the correction. */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n actorProfileId = '';\n @field({ type: 'text' }) kind: AttendanceCorrectionKind = 'punch.corrected';\n @foreignKey(AttendancePunch) punchId = '';\n /** The break or segment id for those kinds; the punch id for punch kinds. */\n targetId = '';\n @field({ type: 'text' }) beforeState = 'null';\n @field({ type: 'text' }) afterState = 'null';\n @field({ type: 'text' }) reason = '';\n acknowledgementRequired = false;\n acknowledgedAt: Date | null = null;\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { nullable: true })\n acknowledgedByProfileId: string | null = null;\n\n /** The values before the change, or null for an addition. */\n getBefore<T = Record<string, unknown>>(): T | null {\n return parseTimesheetJson<T | null>(this.beforeState, null);\n }\n /** The values after the change, or null for a removal. */\n getAfter<T = Record<string, unknown>>(): T | null {\n return parseTimesheetJson<T | null>(this.afterState, null);\n }\n\n override async save(): Promise<this> {\n // Insert and acknowledgement are both service-only: a generic save() or\n // collection create() must not fabricate or countersign ledger history.\n if (!isCorrectionWrite(this))\n throw new Error(\n 'Attendance corrections are writable only through AttendanceCorrectionService.',\n );\n if (\n !this.tenantId ||\n !this.subjectProfileId ||\n !this.actorProfileId ||\n !this.punchId ||\n !this.targetId ||\n !ATTENDANCE_CORRECTION_KINDS.includes(this.kind) ||\n typeof this.reason !== 'string' ||\n !this.reason.trim() ||\n typeof this.acknowledgementRequired !== 'boolean'\n )\n throw new Error('Invalid attendance correction.');\n for (const json of [this.beforeState, this.afterState]) JSON.parse(json);\n if (!!this.acknowledgedAt !== !!this.acknowledgedByProfileId)\n throw new Error('Acknowledgement needs both a time and a profile.');\n if (this.acknowledgedAt) {\n if (!this.acknowledgementRequired)\n throw new Error('This correction does not require acknowledgement.');\n if (this.acknowledgedByProfileId !== this.subjectProfileId)\n throw new Error('Only the subject can acknowledge a correction.');\n }\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if (prior) {\n const row = this as unknown as Record<string, unknown>;\n const camel = (column: string) =>\n column.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n if (\n IMMUTABLE_COLUMNS.some(\n (column) => prior[column] !== row[camel(column)],\n ) ||\n Boolean(prior.acknowledgement_required) !==\n this.acknowledgementRequired ||\n (prior.acknowledged_at &&\n (new Date(prior.acknowledged_at as string).getTime() !==\n this.acknowledgedAt?.getTime() ||\n prior.acknowledged_by_profile_id !== this.acknowledgedByProfileId))\n )\n throw new Error(\n 'Attendance corrections are append-only; only a first acknowledgement may change.',\n );\n }\n this.id ??= crypto.randomUUID();\n return super.save();\n }\n\n override async delete(): Promise<void> {\n throw new Error('Attendance correction history cannot be deleted.');\n }\n}\n\n/** The correction ledger. */\nexport class AttendanceCorrectionCollection extends SmrtCollection<AttendanceCorrection> {\n static readonly _itemClass = AttendanceCorrection;\n}\n","import { isPostgresDatabase } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport type {\n AttendanceBreakCollection,\n AttendancePunchCollection,\n AttendanceReplayCollection,\n AttendanceSegmentCollection,\n} from './models.js';\n\n/** Resolve these identifiers from the authenticated session, never from tap input. */\nexport interface AttendanceActor {\n tenantId: string;\n profileId: string;\n}\n/**\n * Explicit work attribution for a closed punch's draft evidence entry or for a\n * running work segment. `taskRefType`/`taskRefId` (both or neither) name an\n * optional second reference such as an operation; `kind` is an optional short\n * consumer lane label.\n */\nexport interface AttendanceWork {\n workRefType: string;\n workRefId: string;\n description: string;\n taskRefType?: string;\n taskRefId?: string;\n kind?: string;\n}\n\n/**\n * Where a punch was captured: a consumer-owned type (for example a class name\n * such as a station or tablet) and that device's id. Both or neither.\n */\nexport interface AttendanceDeviceRef {\n type: string;\n id: string;\n}\n\n/** Optional provenance recorded when a punch opens. */\nexport interface AttendancePunchOptions {\n /** Short consumer-owned label such as `device`, `web`, `import`, `correction`. */\n source?: string;\n /** The device that captured the punch. */\n device?: AttendanceDeviceRef;\n}\n\n/** Optional detail recorded when a break starts. */\nexport interface AttendanceBreakOptions {\n /** Short consumer-owned label such as `meal` or `rest`. */\n kind?: string;\n}\n\nconst MAX_LABEL = 64;\n\n/** Validate an optional short label (`source`, `kind`, device type); returns the stored form. */\nexport function attendanceLabel(value: unknown, name: string): string {\n if (value === undefined || value === null) return '';\n if (\n typeof value !== 'string' ||\n value !== value.trim() ||\n value.length > MAX_LABEL\n )\n throw new AttendanceConflict(\n `${name} must be a trimmed string of at most ${MAX_LABEL} characters.`,\n 'invalid',\n );\n return value;\n}\n\n/** Validate punch provenance; returns the stored column values. */\nexport function attendanceProvenance(options?: AttendancePunchOptions): {\n source: string;\n deviceRefType: string;\n deviceRefId: string;\n} {\n const source = attendanceLabel(options?.source, 'source');\n const device = options?.device;\n if (device !== undefined && (!device || typeof device !== 'object'))\n throw new AttendanceConflict('device must be { type, id }.', 'invalid');\n const deviceRefType = attendanceLabel(device?.type, 'device.type');\n const deviceRefId = attendanceLabel(device?.id, 'device.id');\n if (!!deviceRefType !== !!deviceRefId)\n throw new AttendanceConflict(\n 'device.type and device.id must be provided together.',\n 'invalid',\n );\n return { source, deviceRefType, deviceRefId };\n}\n\n/** Expected state conflict, distinct from retryable database failures. */\nexport class AttendanceConflict extends Error {\n /**\n * Stable machine-readable reason (see {@link AttendanceRefusalCode}); stored\n * on a refused replay outcome so a device screen can say why.\n */\n readonly code: AttendanceRefusalCode;\n constructor(message: string, code: AttendanceRefusalCode = 'conflict') {\n super(message);\n this.code = code;\n }\n}\n\n/**\n * Built-in refusal codes. Consumers may store any other string through the\n * `refusalCode` replay hook.\n */\nexport type AttendanceRefusalCode =\n | 'conflict'\n | 'invalid'\n | 'predates_committed'\n | 'punch_open'\n | 'no_open_punch'\n | 'break_open'\n | 'no_open_break'\n | 'during_break'\n | 'work_open'\n | 'no_open_work'\n | 'no_resumable_work'\n | (string & {});\n\n/**\n * Collections bound to one transaction executor. Handed to\n * `AttendanceService.withTransaction` callbacks and to the segment helpers so\n * corrections and replay can compose several mutations atomically.\n */\nexport type AttendanceContext = {\n db: DatabaseInterface;\n punches: AttendancePunchCollection;\n breaks: AttendanceBreakCollection;\n segments: AttendanceSegmentCollection;\n replays: AttendanceReplayCollection;\n};\n\n/** Validate a work reference; throws {@link AttendanceConflict}. */\nexport function validateAttendanceWork(work: AttendanceWork): void {\n if (\n !work ||\n typeof work.workRefType !== 'string' ||\n !work.workRefType ||\n typeof work.workRefId !== 'string' ||\n !work.workRefId ||\n typeof work.description !== 'string'\n )\n throw new AttendanceConflict(\n 'A complete work reference and description are required.',\n );\n for (const key of ['taskRefType', 'taskRefId', 'kind'] as const)\n if (work[key] !== undefined && typeof work[key] !== 'string')\n throw new AttendanceConflict(`${key} must be a string.`);\n if (!!work.taskRefType !== !!work.taskRefId)\n throw new AttendanceConflict(\n 'taskRefType and taskRefId must be provided together.',\n );\n}\n\n/** Canonicalize a tenant/profile id: PostgreSQL requires UUIDs, other adapters take it verbatim. */\nexport function attendanceIdentity(\n db: DatabaseInterface,\n value: string,\n): string {\n if (!isPostgresDatabase(db)) return value;\n const hex = value.replace(/[{}-]/g, '').toLowerCase();\n if (!/^[0-9a-f]{32}$/.test(hex))\n throw new Error('PostgreSQL attendance identities must be UUIDs.');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import {\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceWork,\n validateAttendanceWork,\n} from './context.js';\nimport type {\n AttendancePunch,\n AttendanceSegment,\n AttendanceSegmentCloseReason,\n} from './models.js';\n\n/*\n * Segment transitions on an already-open punch, run on the caller's transaction\n * context. `AttendanceService` composes them; corrections and replay call them\n * inside `AttendanceService.withTransaction`. Callers own the actor lock and\n * punch lookup; these helpers own break and one-open-segment rules.\n */\n\n/** The open segment of a punch, or null. */\nexport async function openSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n): Promise<AttendanceSegment | null> {\n const [segment] = await tx.segments.list({\n where: { tenantId: punch.tenantId, punchId: punch.id, endedAt: null },\n limit: 1,\n });\n return segment ?? null;\n}\n\n/** All segments of a punch, oldest first. */\nexport async function listSegments(\n tx: AttendanceContext,\n punch: AttendancePunch,\n): Promise<AttendanceSegment[]> {\n return tx.segments.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n orderBy: 'started_at ASC',\n });\n}\n\n/** Close the punch's open segment, if any. Returns it, or null when none was open. */\nexport async function closeOpenSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n reason: Exclude<AttendanceSegmentCloseReason, ''>,\n): Promise<AttendanceSegment | null> {\n const segment = await openSegment(tx, punch);\n if (!segment) return null;\n if (at < segment.startedAt)\n throw new AttendanceConflict('Segment cannot end before it starts.');\n segment.endedAt = at;\n segment.closedReason = reason;\n await segment.save();\n return segment;\n}\n\n/** Open a segment; refuses while a break or another segment is open. */\nexport async function beginSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n work: AttendanceWork,\n): Promise<AttendanceSegment> {\n validateAttendanceWork(work);\n if (punch.endedAt) throw new AttendanceConflict('Punch is closed.');\n const breaks = await tx.breaks.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n });\n if (breaks.some((b) => !b.endedAt))\n throw new AttendanceConflict(\n 'Cannot work during an open break.',\n 'during_break',\n );\n if (await openSegment(tx, punch))\n throw new AttendanceConflict(\n 'A work segment is already open.',\n 'work_open',\n );\n return tx.segments.create({\n tenantId: punch.tenantId,\n punchId: punch.id ?? undefined,\n workRefType: work.workRefType,\n workRefId: work.workRefId,\n taskRefType: work.taskRefType ?? '',\n taskRefId: work.taskRefId ?? '',\n kind: work.kind ?? '',\n description: work.description,\n startedAt: at,\n _insertOnly: true,\n });\n}\n\n/** Atomically close the open segment and open the next at the same instant. */\nexport async function replaceSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n work: AttendanceWork,\n): Promise<AttendanceSegment> {\n validateAttendanceWork(work);\n if (!(await closeOpenSegment(tx, punch, at, 'switch')))\n throw new AttendanceConflict(\n 'No open work segment to switch.',\n 'no_open_work',\n );\n return beginSegment(tx, punch, at, work);\n}\n\n/** Close the open segment (`stop`); refuses when none is open. */\nexport async function endSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n): Promise<AttendanceSegment> {\n const segment = await closeOpenSegment(tx, punch, at, 'stop');\n if (!segment)\n throw new AttendanceConflict('No open work segment.', 'no_open_work');\n return segment;\n}\n\n/**\n * Reopen the work a break interrupted: a new segment with the same references\n * as the latest segment, which must have been closed by `startBreak`.\n */\nexport async function continueSegment(\n tx: AttendanceContext,\n punch: AttendancePunch,\n at: Date,\n): Promise<AttendanceSegment> {\n const all = await listSegments(tx, punch);\n const last = all[all.length - 1];\n // Only the latest break may be the one that interrupted the work: a later,\n // idle break (taken after the work was left unresumed) must not reopen it.\n const breaks = await tx.breaks.list({\n where: { tenantId: punch.tenantId, punchId: punch.id },\n orderBy: 'started_at DESC',\n limit: 1,\n });\n if (\n last?.closedReason !== 'break' ||\n !last.endedAt ||\n last.endedAt.getTime() !== breaks[0]?.startedAt.getTime()\n )\n throw new AttendanceConflict(\n 'No work interrupted by a break to resume.',\n 'no_resumable_work',\n );\n return beginSegment(tx, punch, at, last);\n}\n","import {\n isEmbeddedDatabase,\n isPostgresDatabase,\n withEmbeddedWriteTransaction,\n} from '@happyvertical/smrt-core';\nimport { getTenantId, withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { parseTimesheetJson } from '../models/json.js';\nimport { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport {\n type AttendanceActor,\n type AttendanceBreakOptions,\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceDeviceRef,\n type AttendancePunchOptions,\n type AttendanceRefusalCode,\n type AttendanceWork,\n attendanceIdentity,\n attendanceLabel,\n attendanceProvenance,\n validateAttendanceWork,\n} from './context.js';\nimport {\n AttendanceBreakCollection,\n AttendancePunch,\n AttendancePunchCollection,\n AttendanceReplayCollection,\n type AttendanceReplayOutcome,\n AttendanceSegment,\n AttendanceSegmentCollection,\n} from './models.js';\nimport {\n beginSegment,\n closeOpenSegment,\n continueSegment,\n endSegment,\n listSegments,\n openSegment,\n replaceSegment,\n} from './segments.js';\nimport { persistAutoClose } from './write.js';\n\n/** Whether stored evidence metadata carries this work's task reference and kind. */\nfunction sameAttribution(metadata: string, work: AttendanceWork): boolean {\n const stored = parseTimesheetJson<Record<string, unknown>>(metadata, {});\n return (\n (stored.taskRefType ?? '') === (work.taskRefType ?? '') &&\n (stored.taskRefId ?? '') === (work.taskRefId ?? '') &&\n (stored.kind ?? '') === (work.kind ?? '')\n );\n}\n\nexport {\n type AttendanceActor,\n type AttendanceBreakOptions,\n AttendanceConflict,\n type AttendanceContext,\n type AttendanceDeviceRef,\n type AttendancePunchOptions,\n type AttendanceRefusalCode,\n type AttendanceWork,\n validateAttendanceWork,\n} from './context.js';\nexport type { AttendanceReplayOutcome } from './models.js';\nexport * from './segments.js';\n\n/** Offline command with a durable client-generated identifier and device timestamp. */\nexport interface AttendanceTap {\n clientId: string;\n at: string;\n action: AttendanceTapAction;\n paid?: boolean;\n /** Required by `startWork` and `switchWork`; also links evidence on `punchOut`. */\n work?: AttendanceWork;\n /** Recorded on the punch by `punchIn`; hashed with the tap. */\n source?: string;\n /** Recorded on the punch by `punchIn`; hashed with the tap. */\n device?: AttendanceDeviceRef;\n /** Recorded on the break by `startBreak`; hashed with the tap. */\n kind?: string;\n}\n\n/** Punch and break actions plus the work-segment actions (`stopWork` is \"pause\"). */\nexport type AttendanceTapAction =\n | 'punchIn'\n | 'punchOut'\n | 'startBreak'\n | 'endBreak'\n | 'startWork'\n | 'switchWork'\n | 'stopWork'\n | 'resumeWork';\n\nconst TAP_ACTIONS: readonly string[] = [\n 'punchIn',\n 'punchOut',\n 'startBreak',\n 'endBreak',\n 'startWork',\n 'switchWork',\n 'stopWork',\n 'resumeWork',\n];\n\n/** What a time policy sees for one tap that has not been replayed yet. */\nexport interface AttendanceTimeInput {\n /** The tap exactly as sent. */\n tap: AttendanceTap;\n /** The tap's device time. */\n deviceAt: Date;\n /** When the server received the batch. */\n serverNow: Date;\n /** `serverNow - deviceAt` in milliseconds: how long the tap waited. */\n waitedMs: number;\n /** The trusted actor the tap replays for. */\n actor: AttendanceActor;\n}\n\n/** The effective time a policy chose, and whether a person should look at it. */\nexport interface AttendanceResolvedTime {\n at: Date;\n /** Flags the resulting work segment (punches are always flagged offline). */\n reviewRequired?: boolean;\n}\n\n/** Why a consumer refuses or rewrites the stored reason of a refused tap. */\nexport interface AttendanceRefusal {\n code: AttendanceRefusalCode;\n /** Replaces the default message when given. */\n message?: string;\n}\n\n/** A resolved auto-close deadline and, optionally, why the punch closed then. */\nexport interface AttendanceAutoCloseDecision {\n deadline: Date;\n /** Recorded as `autoCloseReason` (1 to 200 characters); defaults to `deadline`. */\n reason?: string;\n}\n\n/**\n * Fixed maximum open seconds, or a per-punch resolver returning the deadline\n * (a Date or a decision) or null to leave the punch open.\n */\nexport type AttendanceAutoCloseDeadline =\n | number\n | ((\n punch: AttendancePunch,\n ) =>\n | Date\n | AttendanceAutoCloseDecision\n | null\n | undefined\n | Promise<Date | AttendanceAutoCloseDecision | null | undefined>);\n\nexport interface AttendanceReplayOptions {\n /**\n * Choose each tap's effective time (for example the device time inside a\n * window, else the server arrival time). The receipt still hashes the tap as\n * sent, so retries with the same clientId match; the resolved time is what is\n * stored. Called once per tap that has no receipt yet, inside its transaction.\n */\n resolveAt?: (\n input: AttendanceTimeInput,\n ) => AttendanceResolvedTime | Promise<AttendanceResolvedTime>;\n /** Clock for `serverNow`; defaults to `new Date()` per tap. */\n now?: () => Date;\n /**\n * Name the stored code (and optionally message) of a refused tap. Without it,\n * the refusal's own `AttendanceConflict.code` is stored.\n */\n refusal?: (\n tap: AttendanceTap,\n error: AttendanceConflict,\n ) => AttendanceRefusal | undefined | void;\n}\n\ntype Context = AttendanceContext;\n\n/**\n * Attendance mutations bound to a trusted tenant/profile. Applications authorize\n * the actor before constructing this service. Each transition is atomic; all\n * participating collections use the transaction executor. PostgreSQL serializes\n * this actor with an advisory lock; embedded adapters serialize writers.\n */\nexport class AttendanceService {\n constructor(\n private readonly db: DatabaseInterface,\n private readonly actor: AttendanceActor,\n ) {\n if (!actor.tenantId || !actor.profileId)\n throw new Error('Attendance requires a trusted tenant/profile actor.');\n this.actor = {\n tenantId: this.identity(actor.tenantId),\n profileId: this.identity(actor.profileId),\n };\n }\n\n private identity(value: string): string {\n return attendanceIdentity(this.db, value);\n }\n\n /**\n * Run `operation` in the actor's serialized attendance transaction. The\n * context's collections share the executor, so corrections and replay can\n * compose several attendance mutations (with the segment helpers) atomically.\n * Results that are punches or segments are re-read after commit.\n */\n async withTransaction<T>(\n operation: (context: AttendanceContext) => Promise<T>,\n ): Promise<T> {\n return this.transact(operation);\n }\n\n private async transact<T>(\n operation: (context: Context) => Promise<T>,\n ): Promise<T> {\n const current = getTenantId();\n if (current && this.identity(current) !== this.actor.tenantId)\n throw new Error('Attendance actor differs from active tenant.');\n return withTenant({ tenantId: this.actor.tenantId }, async () => {\n const result = await withEmbeddedWriteTransaction(\n this.db,\n isEmbeddedDatabase(this.db),\n async (db) => {\n if (isPostgresDatabase(db))\n await db.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1, 0))',\n JSON.stringify([\n 'smrt.attendance',\n this.actor.tenantId,\n this.actor.profileId,\n ]),\n );\n return operation({\n db,\n punches: await AttendancePunchCollection.create({ db }),\n breaks: await AttendanceBreakCollection.create({ db }),\n segments: await AttendanceSegmentCollection.create({ db }),\n replays: await AttendanceReplayCollection.create({ db }),\n });\n },\n true,\n );\n if (result instanceof AttendancePunch) {\n const punches = await AttendancePunchCollection.create({ db: this.db });\n const [fresh] = await punches.list({\n where: { ...this.actor, id: result.id },\n limit: 1,\n });\n if (!fresh) throw new Error('Committed attendance punch disappeared.');\n return fresh as T;\n }\n if (result instanceof AttendanceSegment) {\n const segments = await AttendanceSegmentCollection.create({\n db: this.db,\n });\n const [fresh] = await segments.list({\n where: { tenantId: this.actor.tenantId, id: result.id },\n limit: 1,\n });\n if (!fresh)\n throw new Error('Committed attendance segment disappeared.');\n return fresh as T;\n }\n return result;\n });\n }\n\n private date(at: Date): Date {\n if (!(at instanceof Date) || !Number.isFinite(at.getTime()))\n throw new AttendanceConflict(\n 'Attendance timestamps must be valid dates.',\n );\n return at;\n }\n\n private async open(tx: Context): Promise<AttendancePunch> {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('No open punch.', 'no_open_punch');\n return punch;\n }\n\n private async chronological(tx: Context, at: Date): Promise<void> {\n const open = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n const rows = open.length\n ? open\n : await tx.punches.list({\n where: { ...this.actor },\n orderBy: 'ended_at DESC',\n limit: 1,\n });\n if (!rows.length) return;\n const punch = rows[0];\n const breaks = await tx.breaks.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const segments = await tx.segments.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const latest = Math.max(\n punch.startedAt.getTime(),\n ...segments.flatMap((g) => [\n g.startedAt.getTime(),\n g.endedAt?.getTime() ?? 0,\n ]),\n punch.endedAt?.getTime() ?? 0,\n ...breaks.flatMap((b) => [\n b.startedAt.getTime(),\n b.endedAt?.getTime() ?? 0,\n ]),\n );\n if (at.getTime() < latest)\n throw new AttendanceConflict(\n 'Tap predates committed attendance; manual review required.',\n 'predates_committed',\n );\n }\n\n /** Open one punch; the database unique slot also protects direct model writers. */\n async punchIn(\n at = new Date(),\n options?: AttendancePunchOptions,\n ): Promise<AttendancePunch> {\n const meta = attendanceProvenance(options);\n return this.transact((tx) =>\n this.transition(tx, 'punchIn', this.date(at), false, undefined, false, {\n ...meta,\n kind: '',\n }),\n );\n }\n /** Close the punch and any active break; optionally create linked draft evidence. */\n async punchOut(at: Date, work?: AttendanceWork): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(tx, 'punchOut', this.date(at), false, work),\n );\n }\n /** Begin a break; paid defaults false and must be explicitly configured by the caller. */\n async startBreak(\n at: Date,\n paid = false,\n options?: AttendanceBreakOptions,\n ): Promise<AttendancePunch> {\n const kind = attendanceLabel(options?.kind, 'kind');\n return this.transact((tx) =>\n this.transition(tx, 'startBreak', this.date(at), paid, undefined, false, {\n ...attendanceProvenance(),\n kind,\n }),\n );\n }\n /**\n * End the current break. With `{ resumeWork: true }` the work interrupted by\n * `startBreak` resumes at the same instant, atomically with the break end.\n */\n async endBreak(\n at: Date,\n options?: { resumeWork?: boolean },\n ): Promise<AttendancePunch> {\n return this.transact((tx) =>\n this.transition(\n tx,\n 'endBreak',\n this.date(at),\n false,\n undefined,\n options?.resumeWork,\n ),\n );\n }\n\n /** Open a work segment in the open punch; refuses during a break or while one is open. */\n async startWork(at: Date, work: AttendanceWork): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n validateAttendanceWork(work);\n await this.chronological(tx, this.date(at));\n return beginSegment(tx, await this.open(tx), at, work);\n });\n }\n /** Atomically close the open segment and open the next one at `at`. */\n async switchWork(at: Date, work: AttendanceWork): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n validateAttendanceWork(work);\n await this.chronological(tx, this.date(at));\n return replaceSegment(tx, await this.open(tx), at, work);\n });\n }\n /** Close the open segment without starting another. */\n async stopWork(at: Date): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n await this.chronological(tx, this.date(at));\n return endSegment(tx, await this.open(tx), at);\n });\n }\n /** Reopen the work a break interrupted (the latest segment must have closed on `startBreak`). */\n async resumeWork(at: Date): Promise<AttendanceSegment> {\n return this.transact(async (tx) => {\n await this.chronological(tx, this.date(at));\n return continueSegment(tx, await this.open(tx), at);\n });\n }\n /** The open work segment of the actor's open punch, or null. */\n async currentWork(): Promise<AttendanceSegment | null> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n return punch ? openSegment(tx, punch) : null;\n });\n }\n /** Work segments of one of the actor's punches, oldest first. */\n async segments(punchId: string): Promise<AttendanceSegment[]> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: punchId },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for actor.');\n return listSegments(tx, punch);\n });\n }\n\n private async transition(\n tx: Context,\n action: AttendanceTap['action'],\n at: Date,\n paid = false,\n work?: AttendanceWork,\n resumeWork = false,\n meta: ReturnType<typeof attendanceProvenance> & { kind: string } = {\n ...attendanceProvenance(),\n kind: '',\n },\n ): Promise<AttendancePunch> {\n await this.chronological(tx, at);\n if (action === 'punchIn') {\n if (\n (\n await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n })\n ).length\n )\n throw new AttendanceConflict('A punch is already open.', 'punch_open');\n return tx.punches.create({\n ...this.actor,\n startedAt: at,\n source: meta.source,\n deviceRefType: meta.deviceRefType,\n deviceRefId: meta.deviceRefId,\n _insertOnly: true,\n });\n }\n const punch = await this.open(tx);\n const breaks = await tx.breaks.list({\n where: { tenantId: this.actor.tenantId, punchId: punch.id },\n });\n const active = breaks.find((b) => !b.endedAt);\n if (action === 'startBreak') {\n if (active)\n throw new AttendanceConflict('A break is already open.', 'break_open');\n if (typeof paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n await closeOpenSegment(tx, punch, at, 'break');\n await tx.breaks.create({\n tenantId: this.actor.tenantId,\n punchId: punch.id ?? undefined,\n startedAt: at,\n paid,\n kind: meta.kind,\n _insertOnly: true,\n });\n return punch;\n }\n if (action !== 'endBreak' && action !== 'punchOut')\n throw new AttendanceConflict('Unknown attendance action.');\n if (action === 'endBreak' && !active)\n throw new AttendanceConflict('No open break.', 'no_open_break');\n if (action === 'punchOut' && work) {\n this.validateWork(work);\n const unpaidMilliseconds = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + (b.endedAt ?? at).getTime() - b.startedAt.getTime(),\n 0,\n );\n if (\n Math.round(\n (at.getTime() - punch.startedAt.getTime() - unpaidMilliseconds) /\n 1000,\n ) <= 0\n )\n throw new AttendanceConflict(\n 'Only closed punches with positive duration can produce service evidence.',\n );\n }\n if (active) {\n active.endedAt = at;\n await active.save();\n }\n if (action === 'endBreak') {\n if (resumeWork) await continueSegment(tx, punch, at);\n return punch;\n }\n await closeOpenSegment(tx, punch, at, 'punchOut');\n punch.endedAt = at;\n const unpaidMilliseconds = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + b.endedAt!.getTime() - b.startedAt.getTime(),\n 0,\n );\n punch.unpaidBreakSeconds = Math.round(unpaidMilliseconds / 1000);\n punch.durationSeconds = Math.round(\n (at.getTime() - punch.startedAt.getTime() - unpaidMilliseconds) / 1000,\n );\n await punch.save();\n if (work) await this.link(tx, punch, work);\n return punch;\n }\n\n /** Run one replayed tap on `tx`; work actions share the punch changes' transaction. */\n private async apply(\n tx: Context,\n tap: AttendanceTap,\n at: Date,\n ): Promise<{ punch: AttendancePunch; segment?: AttendanceSegment }> {\n const work = (): AttendanceWork => {\n if (!tap.work)\n throw new AttendanceConflict('Work is required.', 'invalid');\n return tap.work;\n };\n let segment: AttendanceSegment;\n switch (tap.action) {\n case 'startWork':\n await this.chronological(tx, at);\n segment = await beginSegment(tx, await this.open(tx), at, work());\n break;\n case 'switchWork':\n await this.chronological(tx, at);\n segment = await replaceSegment(tx, await this.open(tx), at, work());\n break;\n case 'stopWork':\n await this.chronological(tx, at);\n segment = await endSegment(tx, await this.open(tx), at);\n break;\n case 'resumeWork':\n await this.chronological(tx, at);\n segment = await continueSegment(tx, await this.open(tx), at);\n break;\n default:\n return {\n punch: await this.transition(\n tx,\n tap.action,\n at,\n tap.paid,\n tap.work,\n false,\n {\n ...attendanceProvenance({\n source: tap.source,\n device: tap.device,\n }),\n kind: attendanceLabel(tap.kind, 'kind'),\n },\n ),\n };\n }\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: segment.punchId },\n limit: 1,\n });\n return { punch, segment };\n }\n\n private validateWork(work: AttendanceWork): void {\n validateAttendanceWork(work);\n }\n\n private async link(\n tx: Context,\n punch: AttendancePunch,\n work: AttendanceWork,\n ): Promise<void> {\n this.validateWork(work);\n if (!punch.endedAt || punch.durationSeconds <= 0)\n throw new AttendanceConflict(\n 'Only closed punches with positive duration can produce service evidence.',\n );\n const entries = await ServiceTimeEntryCollection.create({ db: tx.db });\n if (punch.serviceTimeEntryId) {\n const [existing] = await entries.list({\n where: { id: punch.serviceTimeEntryId, tenantId: this.actor.tenantId },\n limit: 1,\n });\n if (\n !existing ||\n existing.workRefType !== work.workRefType ||\n existing.workRefId !== work.workRefId ||\n existing.description !== work.description ||\n !sameAttribution(existing.metadata, work)\n )\n throw new AttendanceConflict('Punch already linked to different work.');\n return;\n }\n const entry = await entries.create({\n tenantId: this.actor.tenantId,\n participantKind: 'human',\n participantProfileId: this.actor.profileId,\n workRefType: work.workRefType,\n workRefId: work.workRefId,\n description: work.description,\n source: 'timer',\n startedAt: punch.startedAt,\n endedAt: punch.endedAt,\n durationSeconds: punch.durationSeconds,\n status: 'draft',\n metadata: JSON.stringify({\n attendancePunchId: punch.id,\n ...(work.taskRefType\n ? { taskRefType: work.taskRefType, taskRefId: work.taskRefId }\n : {}),\n ...(work.kind ? { kind: work.kind } : {}),\n }),\n _insertOnly: true,\n });\n if (!entry.id) throw new Error('Saved service entry has no id.');\n punch.serviceTimeEntryId = entry.id;\n await punch.save();\n }\n\n /** Idempotently link a closed, owned punch to draft service evidence. */\n async linkServiceTimeEntry(\n punchId: string,\n work: AttendanceWork,\n ): Promise<AttendancePunch> {\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, id: punchId },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for actor.');\n await this.link(tx, punch, work);\n return punch;\n });\n }\n\n /**\n * Close the open punch once its deadline has passed, flagging it for review.\n * `deadline` is either a fixed maximum open duration in seconds (closing at\n * `startedAt + seconds`, reason `maxDuration`) or a resolver called with the\n * open punch that returns its deadline (a `Date`, or `{ deadline, reason }`),\n * or null to leave it open. The consumer owns time zones and shop-day rules.\n * The open work segment and break close at the deadline; returns the closed\n * punch, or null when nothing was due.\n */\n async autoClose(\n now: Date,\n deadline: AttendanceAutoCloseDeadline,\n ): Promise<AttendancePunch | null> {\n this.date(now);\n if (\n typeof deadline !== 'function' &&\n (!Number.isSafeInteger(deadline) || deadline <= 0)\n )\n throw new AttendanceConflict(\n 'maxOpenSeconds must be a positive integer.',\n );\n return this.transact(async (tx) => {\n const [punch] = await tx.punches.list({\n where: { ...this.actor, endedAt: null },\n limit: 1,\n });\n if (!punch) return null;\n let at: Date;\n let reason = 'maxDuration';\n if (typeof deadline === 'function') {\n const resolved = await deadline(punch);\n if (resolved === null || resolved === undefined) return null;\n const decision =\n resolved instanceof Date ? { deadline: resolved } : resolved;\n at = decision.deadline;\n if (!(at instanceof Date) || !Number.isFinite(at.getTime()))\n throw new AttendanceConflict('Auto-close deadline is invalid.');\n if (at < punch.startedAt)\n throw new AttendanceConflict(\n 'Auto-close deadline predates the punch.',\n );\n reason = decision.reason ?? 'deadline';\n if (typeof reason !== 'string' || !reason.trim() || reason.length > 200)\n throw new AttendanceConflict(\n 'Auto-close reason must be 1 to 200 characters.',\n );\n } else {\n at = new Date(punch.startedAt.getTime() + deadline * 1000);\n }\n if (at > now) return null;\n // Do not truncate a later committed break. The conflict requires manual review.\n const closed = await this.transition(tx, 'punchOut', at);\n closed.reviewRequired = true;\n closed.autoCloseReason = reason;\n await persistAutoClose(closed);\n return closed;\n });\n }\n\n /**\n * Replay device timestamps in order (ties use clientId). Committed attendance\n * wins: stale/state-conflicting taps get durable refusals; identical retries\n * return the same outcome, changed payloads using a key are rejected. A\n * database failure rolls back its tap and receipt and remains retryable.\n */\n async replay(\n taps: readonly AttendanceTap[],\n options: AttendanceReplayOptions = {},\n ): Promise<AttendanceReplayOutcome[]> {\n for (const tap of taps) {\n if (\n !tap ||\n typeof tap.at !== 'string' ||\n typeof tap.clientId !== 'string' ||\n !tap.clientId ||\n !TAP_ACTIONS.includes(tap.action)\n )\n throw new AttendanceConflict('Malformed attendance tap.');\n this.date(new Date(tap.at));\n if (tap.work !== undefined) this.validateWork(tap.work);\n if (\n (tap.action === 'startWork' || tap.action === 'switchWork') &&\n tap.work === undefined\n )\n throw new AttendanceConflict('Malformed attendance tap.');\n if (tap.paid !== undefined && typeof tap.paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n attendanceProvenance({ source: tap.source, device: tap.device });\n attendanceLabel(tap.kind, 'kind');\n }\n const ordered = [...taps].sort(\n (a, b) =>\n Date.parse(a.at) - Date.parse(b.at) ||\n (a.clientId < b.clientId ? -1 : a.clientId > b.clientId ? 1 : 0),\n );\n const outcomes: AttendanceReplayOutcome[] = [];\n for (const tap of ordered)\n outcomes.push(\n await this.transact(async (tx) => {\n const replayKey = JSON.stringify([\n this.actor.tenantId,\n this.actor.profileId,\n tap.clientId,\n ]);\n const content = JSON.stringify([\n tap.action,\n new Date(tap.at).toISOString(),\n tap.paid ?? false,\n tap.work?.workRefType ?? null,\n tap.work?.workRefId ?? null,\n tap.work?.description ?? null,\n ...(tap.work?.taskRefType || tap.work?.kind\n ? [\n tap.work.taskRefType ?? null,\n tap.work.taskRefId ?? null,\n tap.work.kind ?? null,\n ]\n : []),\n // Provenance joins the hash only when present, so receipts of taps\n // that carry none are unchanged.\n ...(tap.source || tap.device || tap.kind\n ? [\n {\n source: tap.source ?? '',\n deviceType: tap.device?.type ?? '',\n deviceId: tap.device?.id ?? '',\n kind: tap.kind ?? '',\n },\n ]\n : []),\n ]);\n const [receipt] = await tx.replays.list({\n where: { tenantId: this.actor.tenantId, replayKey },\n limit: 1,\n });\n if (receipt) {\n if (receipt.content !== content)\n throw new AttendanceConflict(\n 'Client id reused with different content.',\n );\n const outcome = receipt.getOutcome();\n if (outcome.clientId !== tap.clientId)\n throw new Error(\n 'Stored attendance receipt belongs to another client id.',\n );\n return outcome;\n }\n const deviceAt = new Date(tap.at);\n let at = deviceAt;\n let review = false;\n if (options.resolveAt) {\n const serverNow = this.date(options.now?.() ?? new Date());\n const resolved = await options.resolveAt({\n tap,\n deviceAt,\n serverNow,\n waitedMs: serverNow.getTime() - deviceAt.getTime(),\n actor: { ...this.actor },\n });\n if (\n !resolved ||\n !(resolved.at instanceof Date) ||\n !Number.isFinite(resolved.at.getTime())\n )\n throw new Error('resolveAt must return a valid date.');\n at = resolved.at;\n review = resolved.reviewRequired === true;\n }\n let outcome: AttendanceReplayOutcome;\n try {\n // Domain refusals are validated before the first write. Adapters with\n // savepoints isolate the tap; every failure remains in the outer atomic scope.\n // Any database failure escapes and rolls back the entire transaction.\n outcome = await withEmbeddedWriteTransaction(\n tx.db,\n isEmbeddedDatabase(tx.db),\n async (db) => {\n const inner = {\n db,\n punches: await AttendancePunchCollection.create({ db }),\n breaks: await AttendanceBreakCollection.create({ db }),\n segments: await AttendanceSegmentCollection.create({ db }),\n replays: tx.replays,\n };\n const { punch, segment } = await this.apply(inner, tap, at);\n punch.recordedOffline = true;\n punch.reviewRequired = true;\n await punch.save();\n if (segment && review) {\n segment.reviewRequired = true;\n await segment.save();\n }\n return {\n clientId: tap.clientId,\n punchId: punch.id!,\n ...(segment ? { segmentId: segment.id! } : {}),\n ...(at.getTime() !== deviceAt.getTime()\n ? { resolvedAt: at.toISOString() }\n : {}),\n };\n },\n true,\n );\n } catch (error) {\n if (!(error instanceof AttendanceConflict)) throw error;\n const custom = options.refusal?.(tap, error) || undefined;\n outcome = {\n clientId: tap.clientId,\n error: custom?.message || error.message,\n code: custom?.code || error.code,\n };\n }\n await tx.replays.create({\n tenantId: this.actor.tenantId,\n replayKey,\n content,\n outcome: JSON.stringify(outcome),\n _insertOnly: true,\n });\n return outcome;\n }),\n );\n return outcomes;\n }\n}\n","import type { DatabaseInterface } from '@happyvertical/sql';\nimport { ServiceTimeEntryCollection } from '../models/service-time-entry.js';\nimport {\n type AttendanceActor,\n AttendanceConflict,\n type AttendanceContext,\n type AttendancePunchOptions,\n type AttendanceWork,\n attendanceIdentity,\n attendanceLabel,\n attendanceProvenance,\n validateAttendanceWork,\n} from './context.js';\nimport {\n AttendanceCorrection,\n AttendanceCorrectionCollection,\n type AttendanceCorrectionKind,\n} from './correction-models.js';\nimport {\n AttendanceBreak,\n type AttendancePunch,\n AttendanceSegment,\n} from './models.js';\nimport { AttendanceService } from './service.js';\nimport { deleteCorrection, persistCorrection } from './write.js';\n\n/** A closed time range a correction touches, for period-lock checks. */\nexport interface AttendanceCorrectionInterval {\n startsAt: Date;\n endsAt: Date;\n}\n\n/**\n * Optional period lock, run on the correction's transaction before any write.\n * Return a message to refuse the correction (for example \"overlaps a confirmed\n * timecard\"), or null to allow it. `confirmedTimecardGuard` in `./rollup` is the\n * ready-made one.\n */\nexport type AttendanceCorrectionGuard = (\n tx: AttendanceContext,\n subject: AttendanceActor,\n intervals: readonly AttendanceCorrectionInterval[],\n) => Promise<string | null>;\n\nexport interface AttendanceCorrectionOptions {\n guard?: AttendanceCorrectionGuard;\n}\n\n/** Fields every correction carries. */\nexport interface AttendanceCorrectionRequest {\n /** Whose time is being corrected. */\n subjectProfileId: string;\n /** Required, non-blank. Stored verbatim (trimmed) on the ledger row. */\n reason: string;\n /** When true the subject must `acknowledge()` the correction. */\n acknowledgementRequired?: boolean;\n}\n\nexport interface CorrectPunchInput extends AttendanceCorrectionRequest {\n punchId: string;\n startedAt?: Date;\n endedAt?: Date;\n}\nexport interface AddPunchInput\n extends AttendanceCorrectionRequest,\n AttendancePunchOptions {\n startedAt: Date;\n endedAt: Date;\n}\n/** Adds a break when `breakId` is absent; otherwise changes that break. */\nexport interface SaveBreakInput extends AttendanceCorrectionRequest {\n punchId: string;\n breakId?: string;\n startedAt?: Date;\n endedAt?: Date;\n paid?: boolean;\n /** Short consumer-owned label (for example `meal`); omit to keep, `''` to clear. */\n kind?: string;\n}\nexport interface RemoveBreakInput extends AttendanceCorrectionRequest {\n breakId: string;\n}\n/**\n * Adds a segment when `segmentId` is absent (`startedAt`, `endedAt` and `work`\n * are then required); otherwise changes that segment, keeping what is omitted.\n */\nexport interface SaveSegmentInput extends AttendanceCorrectionRequest {\n punchId: string;\n segmentId?: string;\n startedAt?: Date;\n endedAt?: Date;\n work?: AttendanceWork;\n}\nexport interface RemoveSegmentInput extends AttendanceCorrectionRequest {\n segmentId: string;\n}\n\ninterface Layout {\n startsAt: number;\n endsAt: number;\n breaks: { startedAt: Date; endedAt: Date; paid: boolean }[];\n segments: { startedAt: Date; endedAt: Date }[];\n}\n\nconst iso = (d: Date | null) => (d ? d.toISOString() : null);\nconst punchState = (p: AttendancePunch) => ({\n startedAt: iso(p.startedAt),\n endedAt: iso(p.endedAt),\n durationSeconds: p.durationSeconds,\n unpaidBreakSeconds: p.unpaidBreakSeconds,\n reviewRequired: p.reviewRequired,\n ...(p.source ? { source: p.source } : {}),\n ...(p.deviceRefType\n ? { deviceRefType: p.deviceRefType, deviceRefId: p.deviceRefId }\n : {}),\n});\nconst breakState = (b: AttendanceBreak) => ({\n startedAt: iso(b.startedAt),\n endedAt: iso(b.endedAt),\n paid: b.paid,\n ...(b.kind ? { kind: b.kind } : {}),\n});\nconst segmentState = (s: AttendanceSegment) => ({\n startedAt: iso(s.startedAt),\n endedAt: iso(s.endedAt),\n workRefType: s.workRefType,\n workRefId: s.workRefId,\n taskRefType: s.taskRefType,\n taskRefId: s.taskRefId,\n kind: s.kind,\n description: s.description,\n durationSeconds: s.durationSeconds,\n closedReason: s.closedReason,\n});\n\nfunction instant(value: Date | undefined, name: string): Date {\n if (!(value instanceof Date) || !Number.isFinite(value.getTime()))\n throw new AttendanceConflict(`${name} must be a valid date.`);\n return value;\n}\n\nfunction overlapping(\n a: { start: number; end: number },\n b: { start: number; end: number },\n): boolean {\n return a.start < b.end && b.start < a.end;\n}\n\nfunction validateLayout(layout: Layout): void {\n const inside = (r: { startedAt: Date; endedAt: Date }) =>\n r.endedAt.getTime() >= r.startedAt.getTime() &&\n r.startedAt.getTime() >= layout.startsAt &&\n r.endedAt.getTime() <= layout.endsAt;\n if (!layout.breaks.every(inside))\n throw new AttendanceConflict(\n 'A break must be an interval inside its punch.',\n );\n if (!layout.segments.every(inside))\n throw new AttendanceConflict(\n 'A work segment must be an interval inside its punch.',\n );\n const span = (r: { startedAt: Date; endedAt: Date }) => ({\n start: r.startedAt.getTime(),\n end: r.endedAt.getTime(),\n });\n const sets = [layout.breaks, layout.segments];\n for (const set of sets)\n for (let i = 0; i < set.length; i++)\n for (let j = i + 1; j < set.length; j++)\n if (overlapping(span(set[i]), span(set[j])))\n throw new AttendanceConflict(\n set === layout.breaks\n ? 'Breaks must not overlap.'\n : 'Work segments must not overlap.',\n );\n for (const b of layout.breaks)\n for (const s of layout.segments)\n if (overlapping(span(b), span(s)))\n throw new AttendanceConflict(\n 'A work segment must not overlap a break.',\n );\n}\n\n/**\n * Corrections to closed attendance, with a paper trail.\n *\n * `actor` is who makes the change; each request names the `subjectProfileId`\n * whose time it is. The application authorizes the actor (and decides whether\n * they may correct this subject) before constructing the service, exactly as\n * for `AttendanceService`. This service is the only path that may change a\n * closed punch, break or segment: each method validates the resulting layout\n * (everything inside its punch, no overlapping breaks, segments or punches,\n * no segment across a break), recomputes `durationSeconds` and\n * `unpaidBreakSeconds`, keeps a still-draft linked service entry in step, and\n * appends an `AttendanceCorrection` row, all in one transaction serialized with\n * the subject's own taps. Open punches are not corrected here: use\n * `AttendanceService`.\n */\nexport class AttendanceCorrectionService {\n private readonly actor: AttendanceActor;\n constructor(\n private readonly db: DatabaseInterface,\n actor: AttendanceActor,\n private readonly options: AttendanceCorrectionOptions = {},\n ) {\n if (!actor?.tenantId || !actor.profileId)\n throw new Error('Corrections require a trusted tenant/profile actor.');\n this.actor = {\n tenantId: attendanceIdentity(db, actor.tenantId),\n profileId: attendanceIdentity(db, actor.profileId),\n };\n }\n\n /** Change a closed punch's start and/or end. A reviewed punch no longer needs review. */\n async correctPunch(input: CorrectPunchInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const before = punchState(punch);\n const startedAt =\n input.startedAt === undefined\n ? punch.startedAt\n : instant(input.startedAt, 'startedAt');\n const endedAt =\n input.endedAt === undefined\n ? punch.endedAt!\n : instant(input.endedAt, 'endedAt');\n if (endedAt <= startedAt)\n throw new AttendanceConflict('A punch must end after it starts.');\n const children = await this.children(tx, punch);\n validateLayout(this.layout(startedAt, endedAt, children));\n for (const other of await tx.punches.list({\n where: { tenantId: subject.tenantId, profileId: subject.profileId },\n }))\n if (\n other.id !== punch.id &&\n overlapping(\n { start: startedAt.getTime(), end: endedAt.getTime() },\n {\n start: other.startedAt.getTime(),\n end: other.endedAt?.getTime() ?? Number.POSITIVE_INFINITY,\n },\n )\n )\n throw new AttendanceConflict('Punches must not overlap.');\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n { startsAt: startedAt, endsAt: endedAt },\n ]);\n const entry = await this.editableEntry(tx, punch);\n punch.startedAt = startedAt;\n punch.endedAt = endedAt;\n punch.reviewRequired = false;\n await this.settle(tx, punch, children.breaks, entry);\n return record(\n 'punch.corrected',\n punch,\n punch.id!,\n before,\n punchState(punch),\n );\n });\n }\n\n /** Record a missed punch retroactively, as an already-closed interval. */\n async addPunch(input: AddPunchInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const startedAt = instant(input.startedAt, 'startedAt');\n const endedAt = instant(input.endedAt, 'endedAt');\n const meta = attendanceProvenance(input);\n if (endedAt <= startedAt)\n throw new AttendanceConflict('A punch must end after it starts.');\n for (const other of await tx.punches.list({\n where: { tenantId: subject.tenantId, profileId: subject.profileId },\n }))\n if (\n overlapping(\n { start: startedAt.getTime(), end: endedAt.getTime() },\n {\n start: other.startedAt.getTime(),\n end: other.endedAt?.getTime() ?? Number.POSITIVE_INFINITY,\n },\n )\n )\n throw new AttendanceConflict('Punches must not overlap.');\n await this.guard(tx, subject, [{ startsAt: startedAt, endsAt: endedAt }]);\n const punch = await tx.punches.create({\n tenantId: subject.tenantId,\n profileId: subject.profileId,\n startedAt,\n endedAt,\n durationSeconds: Math.round(\n (endedAt.getTime() - startedAt.getTime()) / 1000,\n ),\n ...meta,\n _insertOnly: true,\n });\n return record('punch.added', punch, punch.id!, null, punchState(punch));\n });\n }\n\n /** Add a break to a closed punch, or change one (times and/or paid). */\n async saveBreak(input: SaveBreakInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const children = await this.children(tx, punch);\n const existing = input.breakId\n ? children.breaks.find((b) => b.id === input.breakId)\n : undefined;\n if (input.breakId && !existing)\n throw new AttendanceConflict('Break not found in that punch.');\n if (\n !existing &&\n (input.startedAt === undefined || input.endedAt === undefined)\n )\n throw new AttendanceConflict(\n 'A new break needs startedAt and endedAt.',\n );\n if (input.paid !== undefined && typeof input.paid !== 'boolean')\n throw new AttendanceConflict('paid must be boolean.');\n const next = {\n startedAt: instant(input.startedAt ?? existing?.startedAt, 'startedAt'),\n endedAt: instant(\n input.endedAt ?? existing?.endedAt ?? undefined,\n 'endedAt',\n ),\n paid: input.paid ?? existing?.paid ?? false,\n kind:\n input.kind === undefined\n ? (existing?.kind ?? '')\n : attendanceLabel(input.kind, 'kind'),\n };\n const rest = children.breaks.filter((b) => b !== existing);\n validateLayout(\n this.layout(punch.startedAt, punch.endedAt!, {\n breaks: [...rest, next as unknown as AttendanceBreak],\n segments: children.segments,\n }),\n );\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const entry = await this.editableEntry(tx, punch);\n const before = existing ? breakState(existing) : null;\n let target: AttendanceBreak;\n if (existing) {\n existing.startedAt = next.startedAt;\n existing.endedAt = next.endedAt;\n existing.paid = next.paid;\n existing.kind = next.kind;\n await persistCorrection(existing);\n target = existing;\n } else {\n target = await new AttendanceBreak({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(target, {\n tenantId: subject.tenantId,\n punchId: punch.id ?? undefined,\n ...next,\n });\n await persistCorrection(target);\n }\n await this.settle(tx, punch, [...rest, target], entry);\n return record(\n existing ? 'break.changed' : 'break.added',\n punch,\n target.id!,\n before,\n breakState(target),\n );\n });\n }\n\n /** Remove a break from a closed punch. */\n async removeBreak(input: RemoveBreakInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const [target] = await tx.breaks.list({\n where: { tenantId: subject.tenantId, id: input.breakId },\n limit: 1,\n });\n if (!target) throw new AttendanceConflict('Break not found.');\n const punch = await this.closedPunch(tx, subject, target.punchId);\n const children = await this.children(tx, punch);\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const entry = await this.editableEntry(tx, punch);\n const before = breakState(target);\n await deleteCorrection(target);\n await this.settle(\n tx,\n punch,\n children.breaks.filter((b) => b.id !== target.id),\n entry,\n );\n return record('break.removed', punch, target.id!, before, null);\n });\n }\n\n /** Add a work segment to a closed punch, or change one (times and/or work attribution). */\n async saveSegment(input: SaveSegmentInput): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const punch = await this.closedPunch(tx, subject, input.punchId);\n const children = await this.children(tx, punch);\n const existing = input.segmentId\n ? children.segments.find((s) => s.id === input.segmentId)\n : undefined;\n if (input.segmentId && !existing)\n throw new AttendanceConflict('Work segment not found in that punch.');\n if (\n !existing &&\n (input.startedAt === undefined ||\n input.endedAt === undefined ||\n !input.work)\n )\n throw new AttendanceConflict(\n 'A new work segment needs startedAt, endedAt and work.',\n );\n if (input.work) validateAttendanceWork(input.work);\n const next = {\n startedAt: instant(input.startedAt ?? existing?.startedAt, 'startedAt'),\n endedAt: instant(\n input.endedAt ?? existing?.endedAt ?? undefined,\n 'endedAt',\n ),\n };\n const rest = children.segments.filter((s) => s !== existing);\n validateLayout(\n this.layout(punch.startedAt, punch.endedAt!, {\n breaks: children.breaks,\n segments: [...rest, next as unknown as AttendanceSegment],\n }),\n );\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const before = existing ? segmentState(existing) : null;\n let target: AttendanceSegment;\n if (existing) {\n existing.startedAt = next.startedAt;\n existing.endedAt = next.endedAt;\n if (input.work) this.attribute(existing, input.work);\n await persistCorrection(existing);\n target = existing;\n } else {\n target = await new AttendanceSegment({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(target, {\n tenantId: subject.tenantId,\n punchId: punch.id ?? undefined,\n workRefType: input.work!.workRefType,\n workRefId: input.work!.workRefId,\n taskRefType: input.work!.taskRefType ?? '',\n taskRefId: input.work!.taskRefId ?? '',\n kind: input.work!.kind ?? '',\n description: input.work!.description,\n ...next,\n closedReason: 'stop',\n });\n await persistCorrection(target);\n }\n return record(\n existing ? 'segment.changed' : 'segment.added',\n punch,\n target.id!,\n before,\n segmentState(target),\n );\n });\n }\n\n /** Remove a work segment from a closed punch. */\n async removeSegment(\n input: RemoveSegmentInput,\n ): Promise<AttendanceCorrection> {\n return this.run(input, async (tx, subject, record) => {\n const [target] = await tx.segments.list({\n where: { tenantId: subject.tenantId, id: input.segmentId },\n limit: 1,\n });\n if (!target) throw new AttendanceConflict('Work segment not found.');\n const punch = await this.closedPunch(tx, subject, target.punchId);\n await this.guard(tx, subject, [\n { startsAt: punch.startedAt, endsAt: punch.endedAt! },\n ]);\n const before = segmentState(target);\n await deleteCorrection(target);\n return record('segment.removed', punch, target.id!, before, null);\n });\n }\n\n /**\n * The subject confirms they have seen a correction that required it. Only\n * the subject (this service's actor) may; it can be done once.\n */\n async acknowledge(\n correctionId: string,\n at: Date = new Date(),\n ): Promise<AttendanceCorrection> {\n instant(at, 'at');\n const id = await this.attendance(this.actor.profileId).withTransaction(\n async (tx) => {\n const ledger = await AttendanceCorrectionCollection.create({\n db: tx.db,\n });\n const [row] = await ledger.list({\n where: { tenantId: this.actor.tenantId, id: correctionId },\n limit: 1,\n });\n if (!row) throw new AttendanceConflict('Correction not found.');\n if (row.subjectProfileId !== this.actor.profileId)\n throw new AttendanceConflict(\n 'Only the subject can acknowledge a correction.',\n );\n if (!row.acknowledgementRequired)\n throw new AttendanceConflict(\n 'This correction does not require acknowledgement.',\n );\n if (row.acknowledgedAt)\n throw new AttendanceConflict('Correction already acknowledged.');\n row.acknowledgedAt = at;\n row.acknowledgedByProfileId = this.actor.profileId;\n await persistCorrection(row);\n return row.id!;\n },\n );\n return this.read(id);\n }\n\n /** Corrections to one punch, oldest first. */\n async listForPunch(punchId: string): Promise<AttendanceCorrection[]> {\n return this.attendance(this.actor.profileId).withTransaction(\n async (tx) =>\n await (await AttendanceCorrectionCollection.create({ db: tx.db })).list(\n {\n where: { tenantId: this.actor.tenantId, punchId },\n orderBy: 'created_at ASC',\n },\n ),\n );\n }\n\n /** Corrections of the actor's own time that still await their acknowledgement. */\n async pendingAcknowledgements(): Promise<AttendanceCorrection[]> {\n return this.attendance(this.actor.profileId).withTransaction(async (tx) =>\n (\n await (\n await AttendanceCorrectionCollection.create({ db: tx.db })\n ).list({\n where: {\n tenantId: this.actor.tenantId,\n subjectProfileId: this.actor.profileId,\n },\n orderBy: 'created_at ASC',\n })\n ).filter((row) => row.acknowledgementRequired && !row.acknowledgedAt),\n );\n }\n\n private attendance(subjectProfileId: string): AttendanceService {\n return new AttendanceService(this.db, {\n tenantId: this.actor.tenantId,\n profileId: subjectProfileId,\n });\n }\n\n private async read(id: string): Promise<AttendanceCorrection> {\n return this.attendance(this.actor.profileId).withTransaction(async (tx) => {\n const [row] = await (\n await AttendanceCorrectionCollection.create({ db: tx.db })\n ).list({ where: { tenantId: this.actor.tenantId, id }, limit: 1 });\n if (!row) throw new Error('Committed attendance correction disappeared.');\n return row;\n });\n }\n\n private async run(\n request: AttendanceCorrectionRequest,\n operation: (\n tx: AttendanceContext,\n subject: AttendanceActor,\n record: (\n kind: AttendanceCorrectionKind,\n punch: AttendancePunch,\n targetId: string,\n before: object | null,\n after: object | null,\n ) => Promise<string>,\n ) => Promise<string>,\n ): Promise<AttendanceCorrection> {\n const reason =\n typeof request.reason === 'string' ? request.reason.trim() : '';\n if (!reason) throw new AttendanceConflict('A correction needs a reason.');\n if (\n request.acknowledgementRequired !== undefined &&\n typeof request.acknowledgementRequired !== 'boolean'\n )\n throw new AttendanceConflict('acknowledgementRequired must be boolean.');\n if (!request.subjectProfileId)\n throw new AttendanceConflict('A correction needs a subject.');\n const subject: AttendanceActor = {\n tenantId: this.actor.tenantId,\n profileId: attendanceIdentity(this.db, request.subjectProfileId),\n };\n const id = await this.attendance(subject.profileId).withTransaction(\n async (tx) => {\n // Initialise the ledger table binding on this executor before writing.\n await AttendanceCorrectionCollection.create({ db: tx.db });\n return operation(\n tx,\n subject,\n async (kind, punch, targetId, before, after) => {\n const row = await new AttendanceCorrection({\n db: tx.db,\n id: crypto.randomUUID(),\n _skipLoad: true,\n }).initialize();\n Object.assign(row, {\n tenantId: subject.tenantId,\n subjectProfileId: subject.profileId,\n actorProfileId: this.actor.profileId,\n kind,\n punchId: punch.id ?? undefined,\n targetId,\n beforeState: JSON.stringify(before),\n afterState: JSON.stringify(after),\n reason,\n acknowledgementRequired: request.acknowledgementRequired ?? false,\n });\n await persistCorrection(row);\n return row.id!;\n },\n );\n },\n );\n return this.read(id);\n }\n\n private async closedPunch(\n tx: AttendanceContext,\n subject: AttendanceActor,\n punchId: string,\n ): Promise<AttendancePunch> {\n const [punch] = await tx.punches.list({\n where: {\n tenantId: subject.tenantId,\n profileId: subject.profileId,\n id: punchId,\n },\n limit: 1,\n });\n if (!punch) throw new AttendanceConflict('Punch not found for subject.');\n if (!punch.endedAt)\n throw new AttendanceConflict(\n 'Only closed punches are corrected; use AttendanceService for an open one.',\n );\n return punch;\n }\n\n private async children(tx: AttendanceContext, punch: AttendancePunch) {\n const where = { tenantId: punch.tenantId, punchId: punch.id };\n const breaks = await tx.breaks.list({ where, orderBy: 'started_at ASC' });\n const segments = await tx.segments.list({\n where,\n orderBy: 'started_at ASC',\n });\n if (breaks.some((b) => !b.endedAt) || segments.some((s) => !s.endedAt))\n throw new AttendanceConflict(\n 'Closed punch has an open break or segment.',\n );\n return { breaks, segments };\n }\n\n private layout(\n startedAt: Date,\n endedAt: Date,\n children: {\n breaks: readonly AttendanceBreak[];\n segments: readonly AttendanceSegment[];\n },\n ): Layout {\n return {\n startsAt: startedAt.getTime(),\n endsAt: endedAt.getTime(),\n breaks: children.breaks.map((b) => ({\n startedAt: b.startedAt,\n endedAt: b.endedAt!,\n paid: b.paid,\n })),\n segments: children.segments.map((s) => ({\n startedAt: s.startedAt,\n endedAt: s.endedAt!,\n })),\n };\n }\n\n private attribute(segment: AttendanceSegment, work: AttendanceWork): void {\n segment.workRefType = work.workRefType;\n segment.workRefId = work.workRefId;\n segment.taskRefType = work.taskRefType ?? '';\n segment.taskRefId = work.taskRefId ?? '';\n segment.kind = work.kind ?? '';\n segment.description = work.description;\n }\n\n private async guard(\n tx: AttendanceContext,\n subject: AttendanceActor,\n intervals: readonly AttendanceCorrectionInterval[],\n ): Promise<void> {\n const refusal = await this.options.guard?.(tx, subject, intervals);\n if (refusal) throw new AttendanceConflict(refusal);\n }\n\n /** The punch's still-draft evidence entry, refusing when it is no longer editable. */\n private async editableEntry(tx: AttendanceContext, punch: AttendancePunch) {\n if (!punch.serviceTimeEntryId) return null;\n const entries = await ServiceTimeEntryCollection.create({ db: tx.db });\n const [entry] = await entries.list({\n where: { id: punch.serviceTimeEntryId, tenantId: punch.tenantId },\n limit: 1,\n });\n if (!entry) return null;\n if (entry.status !== 'draft')\n throw new AttendanceConflict(\n `Linked service evidence is ${entry.status}; correct it with ServiceEvidenceService first.`,\n );\n return entry;\n }\n\n /** Recompute the punch's durations from its breaks and persist it and its draft entry. */\n private async settle(\n tx: AttendanceContext,\n punch: AttendancePunch,\n breaks: readonly AttendanceBreak[],\n entry: Awaited<ReturnType<AttendanceCorrectionService['editableEntry']>>,\n ): Promise<void> {\n const unpaidMs = breaks\n .filter((b) => !b.paid)\n .reduce(\n (sum, b) => sum + b.endedAt!.getTime() - b.startedAt.getTime(),\n 0,\n );\n punch.unpaidBreakSeconds = Math.round(unpaidMs / 1000);\n punch.durationSeconds = Math.round(\n (punch.endedAt!.getTime() - punch.startedAt.getTime() - unpaidMs) / 1000,\n );\n await persistCorrection(punch);\n if (entry) {\n entry.startedAt = punch.startedAt;\n entry.endedAt = punch.endedAt;\n entry.durationSeconds = punch.durationSeconds;\n await entry.save();\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"attendance-mk6nqJhX.js","names":[],"sources":["../../src/rollup/attendance.ts"],"sourcesContent":["import type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n AttendanceBreakCollection,\n AttendancePunchCollection,\n} from '../attendance/models.js';\nimport { clipSource, instant, integer } from './calculation.js';\nimport type { TimecardActor, TimecardPeriod, TimecardSource } from './types.js';\n\n/** Optional attendance reader. Called on the rollup's transaction executor. */\nexport async function attendanceSources(\n db: DatabaseInterface,\n actor: TimecardActor,\n period: TimecardPeriod,\n): Promise<{ sources: TimecardSource[]; linkedEntryIds: Set<string> }> {\n const punches = await AttendancePunchCollection.create({ db });\n const breaks = await AttendanceBreakCollection.create({ db });\n const sources: TimecardSource[] = [];\n const linkedEntryIds = new Set<string>();\n for (const punch of await punches.list({ where: { ...actor } })) {\n // A linked punch is authoritative even when pending review: never count its\n // evidence entry behind the review gate, or outside the punch's period.\n if (punch.serviceTimeEntryId) linkedEntryIds.add(punch.serviceTimeEntryId);\n if (!punch.endedAt || punch.reviewRequired) continue;\n const start = instant(punch.startedAt);\n const end = instant(punch.endedAt);\n if (end < start) throw new Error('Invalid attendance interval.');\n let cursor = start;\n let unpaidMs = 0;\n const spans: [number, number][] = [];\n for (const pause of await breaks.list({\n where: { tenantId: actor.tenantId, punchId: punch.id },\n orderBy: 'started_at ASC',\n })) {\n if (!pause.endedAt)\n throw new Error('Closed attendance has an open break.');\n const left = instant(pause.startedAt);\n const right = instant(pause.endedAt);\n if (left < cursor || right < left || right > end)\n throw new Error('Invalid or overlapping attendance breaks.');\n if (!pause.paid) {\n spans.push([cursor, left]);\n unpaidMs += right - left;\n } else {\n spans.push([cursor, right]);\n }\n cursor = right;\n }\n spans.push([cursor, end]);\n const net = Math.round((end - start - unpaidMs) / 1000);\n if (integer(punch.durationSeconds) !== net)\n throw new Error('Attendance duration does not match its breaks.');\n // Round elapsed paid prefixes once, preserving conservation across breaks.\n let paidMs = 0;\n for (const [left, right] of spans) {\n const seconds =\n Math.round((paidMs + right - left) / 1000) - Math.round(paidMs / 1000);\n paidMs += right - left;\n const source = clipSource(\n {\n kind: 'attendance',\n id: String(punch.id),\n startsAt: new Date(left).toISOString(),\n endsAt: new Date(right).toISOString(),\n seconds,\n },\n period,\n );\n if (source?.seconds) sources.push(source);\n }\n }\n return { sources, linkedEntryIds };\n}\n"],"mappings":";;;AASA,eAAsB,kBACpB,IACA,OACA,QACqE;CACrE,MAAM,UAAU,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;CAC7D,MAAM,SAAS,MAAM,0BAA0B,OAAO,EAAE,GAAG,CAAC;CAC5D,MAAM,UAA4B,CAAC;CACnC,MAAM,iCAAiB,IAAI,IAAY;CACvC,KAAA,MAAW,SAAS,MAAM,QAAQ,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAC,GAAG;EAG/D,IAAI,MAAM,oBAAoB,eAAe,IAAI,MAAM,kBAAkB;EACzE,IAAI,CAAC,MAAM,WAAW,MAAM,gBAAgB;EAC5C,MAAM,QAAQ,QAAQ,MAAM,SAAS;EACrC,MAAM,MAAM,QAAQ,MAAM,OAAO;EACjC,IAAI,MAAM,OAAO,MAAM,IAAI,MAAM,8BAA8B;EAC/D,IAAI,SAAS;EACb,IAAI,WAAW;EACf,MAAM,QAA4B,CAAC;EACnC,KAAA,MAAW,SAAS,MAAM,OAAO,KAAK;GACpC,OAAO;IAAE,UAAU,MAAM;IAAU,SAAS,MAAM;GAAG;GACrD,SAAS;EACX,CAAC,GAAG;GACF,IAAI,CAAC,MAAM,SACT,MAAM,IAAI,MAAM,sCAAsC;GACxD,MAAM,OAAO,QAAQ,MAAM,SAAS;GACpC,MAAM,QAAQ,QAAQ,MAAM,OAAO;GACnC,IAAI,OAAO,UAAU,QAAQ,QAAQ,QAAQ,KAC3C,MAAM,IAAI,MAAM,2CAA2C;GAC7D,IAAI,CAAC,MAAM,MAAM;IACf,MAAM,KAAK,CAAC,QAAQ,IAAI,CAAC;IACzB,YAAY,QAAQ;GACtB,OACE,MAAM,KAAK,CAAC,QAAQ,KAAK,CAAC;GAE5B,SAAS;EACX;EACA,MAAM,KAAK,CAAC,QAAQ,GAAG,CAAC;EACxB,MAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,YAAY,GAAI;EACtD,IAAI,QAAQ,MAAM,eAAe,MAAM,KACrC,MAAM,IAAI,MAAM,gDAAgD;EAElE,IAAI,SAAS;EACb,KAAA,MAAW,CAAC,MAAM,UAAU,OAAO;GACjC,MAAM,UACJ,KAAK,OAAO,SAAS,QAAQ,QAAQ,GAAI,IAAI,KAAK,MAAM,SAAS,GAAI;GACvE,UAAU,QAAQ;GAClB,MAAM,SAAS,WACb;IACE,MAAM;IACN,IAAI,OAAO,MAAM,EAAE;IACnB,UAAU,IAAI,KAAK,IAAI,CAAA,CAAE,YAAY;IACrC,QAAQ,IAAI,KAAK,KAAK,CAAA,CAAE,YAAY;IACpC;GACF,GACA,MACF;GACA,IAAI,QAAQ,SAAS,QAAQ,KAAK,MAAM;EAC1C;CACF;CACA,OAAO;EAAE;EAAS;CAAe;AACnC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"models-C1krjn40.js","names":[],"sources":["../../src/attendance/write.ts","../../src/attendance/models.ts"],"sourcesContent":["import type { SmrtObject } from '@happyvertical/smrt-core';\n\n// Per-instance capability, deliberately absent from the package export surface:\n// the only code that can lift closed-interval immutability is the correction\n// service, and only for the single save it is performing.\nconst permitted = new WeakSet<SmrtObject>();\n\n/** True while `object` is being saved by `AttendanceCorrectionService`. */\nexport function isCorrectionWrite(object: SmrtObject): boolean {\n return permitted.has(object);\n}\n\n/** Save `object` with closed-interval immutability lifted for this one write. */\nexport async function persistCorrection<T extends SmrtObject>(\n object: T,\n): Promise<T> {\n permitted.add(object);\n try {\n await object.save();\n return object;\n } finally {\n permitted.delete(object);\n }\n}\n\n/** Delete a closed break or segment through the correction capability. */\nexport async function deleteCorrection<T extends SmrtObject>(\n object: T,\n): Promise<void> {\n permitted.add(object);\n try {\n await object.delete();\n } finally {\n permitted.delete(object);\n }\n}\n\n// Same pattern for the auto-close note: the reason a punch was closed by the\n// scheduler is evidence, writable only by `AttendanceService.autoClose`.\nconst autoClosing = new WeakSet<SmrtObject>();\n\n/** True while `AttendanceService.autoClose` is saving `object`. */\nexport function isAutoCloseWrite(object: SmrtObject): boolean {\n return autoClosing.has(object);\n}\n\n/** Save `object` with its auto-close reason writable for this one write. */\nexport async function persistAutoClose<T extends SmrtObject>(\n object: T,\n): Promise<T> {\n autoClosing.add(object);\n try {\n await object.save();\n return object;\n } finally {\n autoClosing.delete(object);\n }\n}\n","import {\n crossPackageRef,\n field,\n foreignKey,\n isEmbeddedDatabase,\n isPostgresDatabase,\n SmrtCollection,\n SmrtObject,\n smrt,\n withEmbeddedWriteTransaction,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport { parseTimesheetJson } from '../models/json.js';\nimport { isAutoCloseWrite, isCorrectionWrite } from './write.js';\n\n// Punch closes and break writes share one atomic parent boundary, including direct ORM saves.\nasync function saveWithPunchLock<T extends SmrtObject>(\n model: T,\n punchId: string | null | undefined,\n tenant: string,\n save: () => Promise<T>,\n): Promise<T> {\n return withEmbeddedWriteTransaction(\n model.db,\n isEmbeddedDatabase(model.db),\n (db) =>\n model.withDatabase(db, async () => {\n if (punchId && isPostgresDatabase(db))\n await db.query(\n 'SELECT id FROM attendance_punches WHERE id = $1 AND tenant_id = $2 FOR UPDATE',\n punchId,\n tenant,\n );\n return save();\n }),\n true,\n );\n}\n\n/** A tenant-scoped interval of attendance, independent of billable work. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_punches',\n indexes: [\n {\n name: 'attendance_punches_open_owner_key',\n columns: ['tenantId', 'profileId', 'openSlot'],\n unique: true,\n },\n { name: 'attendance_punches_source_idx', columns: ['tenantId', 'source'] },\n {\n name: 'attendance_punches_device_idx',\n columns: ['tenantId', 'deviceRefType', 'deviceRefId'],\n },\n ],\n api: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n})\nexport class AttendancePunch extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile') profileId = '';\n startedAt: Date = new Date();\n endedAt: Date | null = null;\n durationSeconds = 0;\n unpaidBreakSeconds = 0;\n reviewRequired = false;\n /** Why the scheduler closed this punch; empty unless `autoClose` did. Write-once. */\n autoCloseReason = '';\n recordedOffline = false;\n /** Optional short consumer-owned capture label (`device`, `web`, `import`, ...); `''` when unknown. */\n source = '';\n /** Qualified type of the device that captured the punch; both device columns or neither. */\n deviceRefType = '';\n deviceRefId = '';\n // Resolve the active table family, including smrt-support's same-named subtype.\n @foreignKey('ServiceTimeEntry', { nullable: true }) serviceTimeEntryId:\n | string\n | null = null;\n /** Unique while open; closed rows retain independent non-null slots on every dialect. */\n @field({ required: true }) openSlot = '';\n\n override async save(): Promise<this> {\n return saveWithPunchLock(this, this.id, this.tenantId, () =>\n this.saveConsistent(),\n );\n }\n\n private async saveConsistent(): Promise<this> {\n if (!this.tenantId || !this.profileId)\n throw new Error('Attendance requires tenantId and profileId.');\n if (\n !Number.isFinite(this.startedAt?.getTime()) ||\n (this.endedAt &&\n (!Number.isFinite(this.endedAt.getTime()) ||\n this.endedAt < this.startedAt))\n )\n throw new Error('Invalid attendance interval.');\n if (\n ![this.durationSeconds, this.unpaidBreakSeconds].every(\n (value) => Number.isSafeInteger(value) && value >= 0,\n )\n )\n throw new Error(\n 'Attendance durations must be nonnegative safe integer seconds.',\n );\n // Rows that predate the additive columns may load NULL for them.\n this.source ??= '';\n this.deviceRefType ??= '';\n this.deviceRefId ??= '';\n this.autoCloseReason ??= '';\n if (\n typeof this.source !== 'string' ||\n this.source.length > 64 ||\n this.source !== this.source.trim() ||\n typeof this.deviceRefType !== 'string' ||\n this.deviceRefType.length > 64 ||\n this.deviceRefType !== this.deviceRefType.trim() ||\n typeof this.deviceRefId !== 'string' ||\n this.deviceRefId.length > 64 ||\n this.deviceRefId !== this.deviceRefId.trim() ||\n !!this.deviceRefType !== !!this.deviceRefId\n )\n throw new Error('Invalid attendance punch source or device.');\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n const corrected = isCorrectionWrite(this);\n if (\n typeof this.autoCloseReason !== 'string' ||\n this.autoCloseReason.length > 200\n )\n throw new Error(\n 'autoCloseReason must be a string of at most 200 characters.',\n );\n if (\n this.autoCloseReason !== ((prior?.auto_close_reason as string) ?? '') &&\n (!isAutoCloseWrite(this) || (prior && prior.auto_close_reason))\n )\n throw new Error('autoCloseReason is written only by autoClose, once.');\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.profile_id !== this.profileId ||\n (!corrected &&\n (new Date(prior.started_at as string).getTime() !==\n this.startedAt.getTime() ||\n (prior.source ?? '') !== this.source ||\n (prior.device_ref_type ?? '') !== this.deviceRefType ||\n (prior.device_ref_id ?? '') !== this.deviceRefId)))\n )\n throw new Error('Attendance ownership and start are immutable.');\n if (\n !corrected &&\n prior?.ended_at &&\n (new Date(prior.ended_at as string).getTime() !==\n this.endedAt?.getTime() ||\n Number(prior.duration_seconds) !== this.durationSeconds ||\n Number(prior.unpaid_break_seconds) !== this.unpaidBreakSeconds)\n )\n throw new Error('Closed attendance evidence is immutable.');\n if (this.id && this.endedAt) {\n const segments = await this.db.list('attendance_segments', {\n tenant_id: this.tenantId,\n punch_id: this.id,\n });\n if (\n segments.some(\n (item) =>\n !item.ended_at ||\n new Date(item.started_at as string).getTime() <\n this.startedAt.getTime() ||\n new Date(item.ended_at as string).getTime() >\n this.endedAt!.getTime(),\n )\n )\n throw new Error('Work segment must be inside its tenant-owned punch.');\n const breaks = await this.db.list('attendance_breaks', {\n tenant_id: this.tenantId,\n punch_id: this.id,\n });\n if (\n breaks.some(\n (item) =>\n !item.ended_at ||\n new Date(item.started_at as string).getTime() <\n this.startedAt.getTime() ||\n new Date(item.ended_at as string).getTime() >\n this.endedAt!.getTime(),\n )\n )\n throw new Error('Break must be inside its tenant-owned punch.');\n }\n this.id ??= crypto.randomUUID();\n this.openSlot = this.endedAt ? JSON.stringify(['closed', this.id]) : 'open';\n return super.save();\n }\n}\n\n/** A paid or unpaid break inside one attendance punch. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_breaks',\n indexes: [\n {\n name: 'attendance_breaks_open_punch_key',\n columns: ['tenantId', 'punchId', 'openSlot'],\n unique: true,\n },\n ],\n api: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n})\nexport class AttendanceBreak extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @foreignKey(AttendancePunch) punchId = '';\n startedAt: Date = new Date();\n endedAt: Date | null = null;\n paid = false;\n /** Optional short consumer-owned label (`meal`, `rest`, ...); `''` when unspecified. */\n kind = '';\n @field({ required: true }) openSlot = '';\n\n override async save(): Promise<this> {\n return saveWithPunchLock(this, this.punchId, this.tenantId, () =>\n this.saveConsistent(),\n );\n }\n\n private async saveConsistent(): Promise<this> {\n this.kind ??= '';\n if (\n !this.tenantId ||\n !this.punchId ||\n typeof this.paid !== 'boolean' ||\n typeof this.kind !== 'string' ||\n this.kind.length > 64 ||\n this.kind !== this.kind.trim()\n )\n throw new Error('Invalid attendance break.');\n if (\n !Number.isFinite(this.startedAt?.getTime()) ||\n (this.endedAt &&\n (!Number.isFinite(this.endedAt.getTime()) ||\n this.endedAt < this.startedAt))\n )\n throw new Error('Invalid attendance break interval.');\n const parent = await this.db.get('attendance_punches', {\n id: this.punchId,\n tenant_id: this.tenantId,\n });\n if (\n !parent ||\n this.startedAt.getTime() <\n new Date(parent.started_at as string).getTime() ||\n (parent.ended_at &&\n (!this.endedAt ||\n this.endedAt.getTime() >\n new Date(parent.ended_at as string).getTime()))\n )\n throw new Error('Break must be inside its tenant-owned punch.');\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n const corrected = isCorrectionWrite(this);\n if (!prior && parent.ended_at && !corrected)\n throw new Error(\n 'A closed punch changes only through AttendanceCorrectionService.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.punch_id !== this.punchId ||\n (!corrected &&\n (new Date(prior.started_at as string).getTime() !==\n this.startedAt.getTime() ||\n Boolean(prior.paid) !== this.paid ||\n (prior.kind ?? '') !== this.kind)))\n )\n throw new Error('Break ownership, start and pay policy are immutable.');\n if (\n !corrected &&\n prior?.ended_at &&\n new Date(prior.ended_at as string).getTime() !== this.endedAt?.getTime()\n )\n throw new Error('Closed breaks are immutable.');\n const brStart = this.startedAt.getTime();\n const brEnd = this.endedAt?.getTime() ?? Number.POSITIVE_INFINITY;\n if (\n (\n await this.db.list('attendance_segments', {\n tenant_id: this.tenantId,\n punch_id: this.punchId,\n })\n ).some(\n (row) =>\n brStart <\n (row.ended_at\n ? new Date(row.ended_at as string).getTime()\n : Number.POSITIVE_INFINITY) &&\n brEnd > new Date(row.started_at as string).getTime(),\n )\n )\n throw new Error('Break must not overlap a work segment.');\n this.id ??= crypto.randomUUID();\n this.openSlot = this.endedAt ? JSON.stringify(['closed', this.id]) : 'open';\n return super.save();\n }\n\n override async delete(): Promise<void> {\n // Judge the persisted row, not the mutable instance, and do it under the\n // parent punch lock so a concurrent punchOut cannot close the row between\n // the check and the delete.\n await saveWithPunchLock(this, this.punchId, this.tenantId, async () => {\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if ((this.endedAt || prior?.ended_at) && !isCorrectionWrite(this))\n throw new Error(\n 'Closed breaks are immutable; remove one through AttendanceCorrectionService.',\n );\n await super.delete();\n return this;\n });\n }\n}\n\nconst SEGMENT_CLOSE_REASONS = ['stop', 'switch', 'break', 'punchOut'] as const;\n/** Why a work segment ended; `''` while it is open. */\nexport type AttendanceSegmentCloseReason =\n | (typeof SEGMENT_CLOSE_REASONS)[number]\n | '';\n\n/**\n * A running unit of work inside one attendance punch: a work reference plus an\n * optional second (task) reference as queryable columns, so job costing can\n * index and join them. At most one segment is open per punch (`openSlot`). A\n * segment never spans a break; `startBreak` and `punchOut` close it. Segments\n * are attendance evidence and are not themselves service time entries.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_segments',\n indexes: [\n {\n name: 'attendance_segments_open_punch_key',\n columns: ['tenantId', 'punchId', 'openSlot'],\n unique: true,\n },\n {\n name: 'attendance_segments_work_idx',\n columns: ['tenantId', 'workRefType', 'workRefId'],\n },\n {\n name: 'attendance_segments_task_idx',\n columns: ['tenantId', 'taskRefType', 'taskRefId'],\n },\n ],\n api: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n})\nexport class AttendanceSegment extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @foreignKey(AttendancePunch) punchId = '';\n /** Qualified class name of the work context (for example a work order). */\n workRefType = '';\n workRefId = '';\n /** Optional second reference (for example an operation); both or neither. */\n taskRefType = '';\n taskRefId = '';\n /** Optional short consumer lane label. */\n kind = '';\n description = '';\n startedAt: Date = new Date();\n endedAt: Date | null = null;\n /** Elapsed whole seconds, set when the segment closes. */\n durationSeconds = 0;\n /** `stop`, `switch`, `break` or `punchOut` once closed; `''` while open. */\n closedReason: AttendanceSegmentCloseReason = '';\n /** Set when a replay time policy asked for review of this segment's boundary. */\n reviewRequired = false;\n @field({ required: true }) openSlot = '';\n\n override async save(): Promise<this> {\n return saveWithPunchLock(this, this.punchId, this.tenantId, () =>\n this.saveConsistent(),\n );\n }\n\n private async saveConsistent(): Promise<this> {\n if (\n !this.tenantId ||\n !this.punchId ||\n !this.workRefType ||\n !this.workRefId ||\n !!this.taskRefType !== !!this.taskRefId\n )\n throw new Error('Invalid attendance work segment.');\n if (\n !Number.isFinite(this.startedAt?.getTime()) ||\n (this.endedAt &&\n (!Number.isFinite(this.endedAt.getTime()) ||\n this.endedAt < this.startedAt))\n )\n throw new Error('Invalid attendance segment interval.');\n if (!this.endedAt !== !this.closedReason)\n throw new Error(\n 'A segment has a close reason exactly when it is closed.',\n );\n if (this.closedReason && !SEGMENT_CLOSE_REASONS.includes(this.closedReason))\n throw new Error('Invalid segment close reason.');\n const parent = await this.db.get('attendance_punches', {\n id: this.punchId,\n tenant_id: this.tenantId,\n });\n if (\n !parent ||\n this.startedAt.getTime() <\n new Date(parent.started_at as string).getTime() ||\n (parent.ended_at &&\n (!this.endedAt ||\n this.endedAt.getTime() >\n new Date(parent.ended_at as string).getTime()))\n )\n throw new Error('Segment must be inside its tenant-owned punch.');\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n const corrected = isCorrectionWrite(this);\n if (!prior && parent.ended_at && !corrected)\n throw new Error(\n 'A closed punch changes only through AttendanceCorrectionService.',\n );\n if (\n prior &&\n (prior.tenant_id !== this.tenantId ||\n prior.punch_id !== this.punchId ||\n (!corrected &&\n (prior.work_ref_type !== this.workRefType ||\n prior.work_ref_id !== this.workRefId ||\n prior.task_ref_type !== this.taskRefType ||\n prior.task_ref_id !== this.taskRefId ||\n prior.kind !== this.kind ||\n prior.description !== this.description ||\n new Date(prior.started_at as string).getTime() !==\n this.startedAt.getTime())))\n )\n throw new Error('Segment ownership, references and start are immutable.');\n if (\n !corrected &&\n prior?.ended_at &&\n (new Date(prior.ended_at as string).getTime() !==\n this.endedAt?.getTime() ||\n prior.closed_reason !== this.closedReason)\n )\n throw new Error('Closed segments are immutable.');\n const start = this.startedAt.getTime();\n const end = this.endedAt?.getTime() ?? Number.POSITIVE_INFINITY;\n const overlaps = (row: Record<string, unknown>) =>\n start <\n (row.ended_at\n ? new Date(row.ended_at as string).getTime()\n : Number.POSITIVE_INFINITY) &&\n end > new Date(row.started_at as string).getTime();\n const where = { tenant_id: this.tenantId, punch_id: this.punchId };\n if ((await this.db.list('attendance_breaks', where)).some(overlaps))\n throw new Error('Segment must not overlap a break.');\n if (\n (await this.db.list(this.tableName, where)).some(\n (row) => row.id !== this.id && overlaps(row),\n )\n )\n throw new Error('Segments must not overlap.');\n this.durationSeconds = this.endedAt ? Math.round((end - start) / 1000) : 0;\n this.id ??= crypto.randomUUID();\n this.openSlot = this.endedAt ? JSON.stringify(['closed', this.id]) : 'open';\n return super.save();\n }\n\n override async delete(): Promise<void> {\n // Judge the persisted row, not the mutable instance, and do it under the\n // parent punch lock so a concurrent punchOut cannot close the row between\n // the check and the delete.\n await saveWithPunchLock(this, this.punchId, this.tenantId, async () => {\n const prior = this.id\n ? await this.db.get(this.tableName, { id: this.id })\n : null;\n if ((this.endedAt || prior?.ended_at) && !isCorrectionWrite(this))\n throw new Error(\n 'Closed segments are immutable; remove one through AttendanceCorrectionService.',\n );\n await super.delete();\n return this;\n });\n }\n}\n\n/** Durable success or conflict; conflicts do not mutate attendance. */\nexport interface AttendanceReplayOutcome {\n clientId: string;\n punchId?: string;\n /** Work segment a replayed work action started, switched to, stopped or resumed. */\n segmentId?: string;\n /** Effective time stored when a `resolveAt` policy changed it (ISO 8601). */\n resolvedAt?: string;\n /** Refusal message (with `code`); present exactly when the tap was refused. */\n error?: string;\n /** Typed refusal reason; see `AttendanceRefusalCode`. */\n code?: string;\n}\n\nfunction validReplayOutcome(value: unknown): value is AttendanceReplayOutcome {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const result = value as Record<string, unknown>;\n return (\n typeof result.clientId === 'string' &&\n result.clientId.length > 0 &&\n (result.code === undefined ||\n (typeof result.code === 'string' &&\n result.code.length > 0 &&\n result.error !== undefined)) &&\n (result.segmentId === undefined || typeof result.segmentId === 'string') &&\n (result.resolvedAt === undefined ||\n typeof result.resolvedAt === 'string') &&\n ((typeof result.punchId === 'string' &&\n result.punchId.length > 0 &&\n result.error === undefined) ||\n (typeof result.error === 'string' &&\n result.error.length > 0 &&\n result.punchId === undefined))\n );\n}\n\n/** Durable offline-tap outcome; never exposed through generated operations. */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'attendance_replays',\n sensitive: true,\n api: { include: [] },\n cli: { include: [] },\n mcp: { include: [] },\n})\nexport class AttendanceReplay extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-timesheets\";\n @tenantId() tenantId = '';\n @field({ required: true, unique: true }) replayKey = '';\n @field({ type: 'text' }) content = '';\n @field({ type: 'text' }) outcome = '{}';\n\n /** Read a durable receipt, failing closed rather than re-executing a corrupt tap. */\n getOutcome(): AttendanceReplayOutcome {\n const value = parseTimesheetJson<unknown>(this.outcome, null);\n if (!validReplayOutcome(value))\n throw new Error('Invalid stored attendance replay outcome.');\n return value;\n }\n\n /** Store the JSON representation of a validated success or refusal. */\n setOutcome(value: AttendanceReplayOutcome): void {\n if (!validReplayOutcome(value))\n throw new Error('Invalid attendance replay outcome.');\n this.outcome = JSON.stringify(value);\n }\n\n override async save(): Promise<this> {\n this.getOutcome();\n return super.save();\n }\n}\n\n/** Punches for attendance and period rollup. */\nexport class AttendancePunchCollection extends SmrtCollection<AttendancePunch> {\n static readonly _itemClass = AttendancePunch;\n}\n/** Break intervals for net duration and period clipping. */\nexport class AttendanceBreakCollection extends SmrtCollection<AttendanceBreak> {\n static readonly _itemClass = AttendanceBreak;\n}\n/** Work segments for job costing and attendance review. */\nexport class AttendanceSegmentCollection extends SmrtCollection<AttendanceSegment> {\n static readonly _itemClass = AttendanceSegment;\n}\n/** Internal durable replay ledger. */\nexport class AttendanceReplayCollection extends SmrtCollection<AttendanceReplay> {\n static readonly _itemClass = AttendanceReplay;\n}\n"],"mappings":";;;;AAKA,IAAM,4BAAY,IAAI,QAAoB;AAGnC,SAAS,kBAAkB,QAA6B;CAC7D,OAAO,UAAU,IAAI,MAAM;AAC7B;AAGA,eAAsB,kBACpB,QACY;CACZ,UAAU,IAAI,MAAM;CACpB,IAAI;EACF,MAAM,OAAO,KAAK;EAClB,OAAO;CACT,UAAE;EACA,UAAU,OAAO,MAAM;CACzB;AACF;AAGA,eAAsB,iBACpB,QACe;CACf,UAAU,IAAI,MAAM;CACpB,IAAI;EACF,MAAM,OAAO,OAAO;CACtB,UAAE;EACA,UAAU,OAAO,MAAM;CACzB;AACF;AAIA,IAAM,8BAAc,IAAI,QAAoB;AAGrC,SAAS,iBAAiB,QAA6B;CAC5D,OAAO,YAAY,IAAI,MAAM;AAC/B;AAGA,eAAsB,iBACpB,QACY;CACZ,YAAY,IAAI,MAAM;CACtB,IAAI;EACF,MAAM,OAAO,KAAK;EAClB,OAAO;CACT,UAAE;EACA,YAAY,OAAO,MAAM;CAC3B;AACF;;;;;;;;;;;;;;;;;;ACzCA,eAAe,kBACb,OACA,SACA,QACA,MACY;CACZ,OAAO,6BACL,MAAM,IACN,mBAAmB,MAAM,EAAE,IAC1B,OACC,MAAM,aAAa,IAAI,YAAY;EACjC,IAAI,WAAW,mBAAmB,EAAE,GAClC,MAAM,GAAG,MACP,iFACA,SACA,MACF;EACF,OAAO,KAAK;CACd,CAAC,GACH,IACF;AACF;AAsBO,IAAM,kBAAN,cAA8B,WAAW;CAClC,WAAW;CACkC,YAAY;CACrE,4BAAkB,IAAI,KAAK;CAC3B,UAAuB;CACvB,kBAAkB;CAClB,qBAAqB;CACrB,iBAAiB;;CAEjB,kBAAkB;CAClB,kBAAkB;;CAElB,SAAS;;CAET,gBAAgB;CAChB,cAAc;CAEsC,qBAEzC;CAEgB,WAAW;CAEtC,MAAe,OAAsB;EACnC,OAAO,kBAAkB,MAAM,KAAK,IAAI,KAAK,gBAC3C,KAAK,eAAe,CACtB;CACF;CAEA,MAAc,iBAAgC;EAC5C,IAAI,CAAC,KAAK,YAAY,CAAC,KAAK,WAC1B,MAAM,IAAI,MAAM,6CAA6C;EAC/D,IACE,CAAC,OAAO,SAAS,KAAK,WAAW,QAAQ,CAAC,KACzC,KAAK,YACH,CAAC,OAAO,SAAS,KAAK,QAAQ,QAAQ,CAAC,KACtC,KAAK,UAAU,KAAK,YAExB,MAAM,IAAI,MAAM,8BAA8B;EAChD,IACE,CAAC,CAAC,KAAK,iBAAiB,KAAK,kBAAkB,CAAA,CAAE,OAC9C,UAAU,OAAO,cAAc,KAAK,KAAK,SAAS,CACrD,GAEA,MAAM,IAAI,MACR,gEACF;EAEF,KAAK,WAAW;EAChB,KAAK,kBAAkB;EACvB,KAAK,gBAAgB;EACrB,KAAK,oBAAoB;EACzB,IACE,OAAO,KAAK,WAAW,YACvB,KAAK,OAAO,SAAS,MACrB,KAAK,WAAW,KAAK,OAAO,KAAK,KACjC,OAAO,KAAK,kBAAkB,YAC9B,KAAK,cAAc,SAAS,MAC5B,KAAK,kBAAkB,KAAK,cAAc,KAAK,KAC/C,OAAO,KAAK,gBAAgB,YAC5B,KAAK,YAAY,SAAS,MAC1B,KAAK,gBAAgB,KAAK,YAAY,KAAK,KAC3C,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,KAAK,aAEhC,MAAM,IAAI,MAAM,4CAA4C;EAC9D,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;EACJ,MAAM,YAAY,kBAAkB,IAAI;EACxC,IACE,OAAO,KAAK,oBAAoB,YAChC,KAAK,gBAAgB,SAAS,KAE9B,MAAM,IAAI,MACR,6DACF;EACF,IACE,KAAK,qBAAsB,OAAO,qBAAgC,QACjE,CAAC,iBAAiB,IAAI,KAAM,SAAS,MAAM,oBAE5C,MAAM,IAAI,MAAM,qDAAqD;EACvE,IACE,UACC,MAAM,cAAc,KAAK,YACxB,MAAM,eAAe,KAAK,aACzB,CAAC,cACC,IAAI,KAAK,MAAM,UAAoB,CAAA,CAAE,QAAQ,MAC5C,KAAK,UAAU,QAAQ,MACtB,MAAM,UAAU,QAAQ,KAAK,WAC7B,MAAM,mBAAmB,QAAQ,KAAK,kBACtC,MAAM,iBAAiB,QAAQ,KAAK,eAE3C,MAAM,IAAI,MAAM,+CAA+C;EACjE,IACE,CAAC,aACD,OAAO,aACN,IAAI,KAAK,MAAM,QAAkB,CAAA,CAAE,QAAQ,MAC1C,KAAK,SAAS,QAAQ,KACtB,OAAO,MAAM,gBAAgB,MAAM,KAAK,mBACxC,OAAO,MAAM,oBAAoB,MAAM,KAAK,qBAE9C,MAAM,IAAI,MAAM,0CAA0C;EAC5D,IAAI,KAAK,MAAM,KAAK,SAAS;GAK3B,KACE,MALqB,KAAK,GAAG,KAAK,uBAAuB;IACzD,WAAW,KAAK;IAChB,UAAU,KAAK;GACjB,CAAC,EAAA,CAEU,MACN,SACC,CAAC,KAAK,YACN,IAAI,KAAK,KAAK,UAAoB,CAAA,CAAE,QAAQ,IAC1C,KAAK,UAAU,QAAQ,KACzB,IAAI,KAAK,KAAK,QAAkB,CAAA,CAAE,QAAQ,IACxC,KAAK,QAAS,QAAQ,CAC5B,GAEA,MAAM,IAAI,MAAM,qDAAqD;GAKvE,KACE,MALmB,KAAK,GAAG,KAAK,qBAAqB;IACrD,WAAW,KAAK;IAChB,UAAU,KAAK;GACjB,CAAC,EAAA,CAEQ,MACJ,SACC,CAAC,KAAK,YACN,IAAI,KAAK,KAAK,UAAoB,CAAA,CAAE,QAAQ,IAC1C,KAAK,UAAU,QAAQ,KACzB,IAAI,KAAK,KAAK,QAAkB,CAAA,CAAE,QAAQ,IACxC,KAAK,QAAS,QAAQ,CAC5B,GAEA,MAAM,IAAI,MAAM,8CAA8C;EAClE;EACA,KAAK,OAAO,OAAO,WAAW;EAC9B,KAAK,WAAW,KAAK,UAAU,KAAK,UAAU,CAAC,UAAU,KAAK,EAAE,CAAC,IAAI;EACrE,OAAO,MAAM,KAAK;CACpB;AACF;AA1IkD,cAArC,iBAA4C,mBAAkB,gCAAA;AAC7D,gBAAA,CAAX,SAAS,CAAA,GADC,gBACC,WAAA,YAAA,CAAA;AAC6C,gBAAA,CAAxD,gBAAgB,sCAAsC,CAAA,GAF5C,gBAE8C,WAAA,aAAA,CAAA;AAeL,gBAAA,CAAnD,WAAW,oBAAoB,EAAE,UAAU,KAAK,CAAC,CAAA,GAjBvC,gBAiByC,WAAA,sBAAA,CAAA;AAIzB,gBAAA,CAA1B,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GArBd,gBAqBgB,WAAA,YAAA,CAAA;AArBhB,kBAAN,gBAAA,CAnBN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,SAAS;EACP;GACE,MAAM;GACN,SAAS;IAAC;IAAY;IAAa;GAAU;GAC7C,QAAQ;EACV;EACA;GAAE,MAAM;GAAiC,SAAS,CAAC,YAAY,QAAQ;EAAE;EACzE;GACE,MAAM;GACN,SAAS;IAAC;IAAY;IAAiB;GAAa;EACtD;CACF;CACA,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;AAClC,CAAC,CAAA,GACY,eAAA;AA2JN,IAAM,kBAAN,cAA8B,WAAW;CAClC,WAAW;CACM,UAAU;CACvC,4BAAkB,IAAI,KAAK;CAC3B,UAAuB;CACvB,OAAO;;CAEP,OAAO;CACoB,WAAW;CAEtC,MAAe,OAAsB;EACnC,OAAO,kBAAkB,MAAM,KAAK,SAAS,KAAK,gBAChD,KAAK,eAAe,CACtB;CACF;CAEA,MAAc,iBAAgC;EAC5C,KAAK,SAAS;EACd,IACE,CAAC,KAAK,YACN,CAAC,KAAK,WACN,OAAO,KAAK,SAAS,aACrB,OAAO,KAAK,SAAS,YACrB,KAAK,KAAK,SAAS,MACnB,KAAK,SAAS,KAAK,KAAK,KAAK,GAE7B,MAAM,IAAI,MAAM,2BAA2B;EAC7C,IACE,CAAC,OAAO,SAAS,KAAK,WAAW,QAAQ,CAAC,KACzC,KAAK,YACH,CAAC,OAAO,SAAS,KAAK,QAAQ,QAAQ,CAAC,KACtC,KAAK,UAAU,KAAK,YAExB,MAAM,IAAI,MAAM,oCAAoC;EACtD,MAAM,SAAS,MAAM,KAAK,GAAG,IAAI,sBAAsB;GACrD,IAAI,KAAK;GACT,WAAW,KAAK;EAClB,CAAC;EACD,IACE,CAAC,UACD,KAAK,UAAU,QAAQ,IACrB,IAAI,KAAK,OAAO,UAAoB,CAAA,CAAE,QAAQ,KAC/C,OAAO,aACL,CAAC,KAAK,WACL,KAAK,QAAQ,QAAQ,IACnB,IAAI,KAAK,OAAO,QAAkB,CAAA,CAAE,QAAQ,IAElD,MAAM,IAAI,MAAM,8CAA8C;EAChE,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;EACJ,MAAM,YAAY,kBAAkB,IAAI;EACxC,IAAI,CAAC,SAAS,OAAO,YAAY,CAAC,WAChC,MAAM,IAAI,MACR,kEACF;EACF,IACE,UACC,MAAM,cAAc,KAAK,YACxB,MAAM,aAAa,KAAK,WACvB,CAAC,cACC,IAAI,KAAK,MAAM,UAAoB,CAAA,CAAE,QAAQ,MAC5C,KAAK,UAAU,QAAQ,KACvB,QAAQ,MAAM,IAAI,MAAM,KAAK,SAC5B,MAAM,QAAQ,QAAQ,KAAK,QAElC,MAAM,IAAI,MAAM,sDAAsD;EACxE,IACE,CAAC,aACD,OAAO,YACP,IAAI,KAAK,MAAM,QAAkB,CAAA,CAAE,QAAQ,MAAM,KAAK,SAAS,QAAQ,GAEvE,MAAM,IAAI,MAAM,8BAA8B;EAChD,MAAM,UAAU,KAAK,UAAU,QAAQ;EACvC,MAAM,QAAQ,KAAK,SAAS,QAAQ,KAAK,OAAO;EAChD,KAEI,MAAM,KAAK,GAAG,KAAK,uBAAuB;GACxC,WAAW,KAAK;GAChB,UAAU,KAAK;EACjB,CAAC,EAAA,CACD,MACC,QACC,WACG,IAAI,WACD,IAAI,KAAK,IAAI,QAAkB,CAAA,CAAE,QAAQ,IACzC,OAAO,sBACb,QAAQ,IAAI,KAAK,IAAI,UAAoB,CAAA,CAAE,QAAQ,CACvD,GAEA,MAAM,IAAI,MAAM,wCAAwC;EAC1D,KAAK,OAAO,OAAO,WAAW;EAC9B,KAAK,WAAW,KAAK,UAAU,KAAK,UAAU,CAAC,UAAU,KAAK,EAAE,CAAC,IAAI;EACrE,OAAO,MAAM,KAAK;CACpB;CAEA,MAAe,SAAwB;EAIrC,MAAM,kBAAkB,MAAM,KAAK,SAAS,KAAK,UAAU,YAAY;GACrE,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;GACJ,KAAK,KAAK,WAAW,OAAO,aAAa,CAAC,kBAAkB,IAAI,GAC9D,MAAM,IAAI,MACR,8EACF;GACF,MAAM,MAAM,OAAO;GACnB,OAAO;EACT,CAAC;CACH;AACF;AAhHkD,cAArC,iBAA4C,mBAAkB,gCAAA;AAC7D,gBAAA,CAAX,SAAS,CAAA,GADC,gBACC,WAAA,YAAA,CAAA;AACiB,gBAAA,CAA5B,WAAW,eAAe,CAAA,GAFhB,gBAEkB,WAAA,WAAA,CAAA;AAMF,gBAAA,CAA1B,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GARd,gBAQgB,WAAA,YAAA,CAAA;AARhB,kBAAN,gBAAA,CAdN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,SAAS,CACP;EACE,MAAM;EACN,SAAS;GAAC;GAAY;GAAW;EAAU;EAC3C,QAAQ;CACV,CACF;CACA,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;AAClC,CAAC,CAAA,GACY,eAAA;AAkHb,IAAM,wBAAwB;CAAC;CAAQ;CAAU;CAAS;AAAU;AAmC7D,IAAM,oBAAN,cAAgC,WAAW;CACpC,WAAW;CACM,UAAU;;CAEvC,cAAc;CACd,YAAY;;CAEZ,cAAc;CACd,YAAY;;CAEZ,OAAO;CACP,cAAc;CACd,4BAAkB,IAAI,KAAK;CAC3B,UAAuB;;CAEvB,kBAAkB;;CAElB,eAA6C;;CAE7C,iBAAiB;CACU,WAAW;CAEtC,MAAe,OAAsB;EACnC,OAAO,kBAAkB,MAAM,KAAK,SAAS,KAAK,gBAChD,KAAK,eAAe,CACtB;CACF;CAEA,MAAc,iBAAgC;EAC5C,IACE,CAAC,KAAK,YACN,CAAC,KAAK,WACN,CAAC,KAAK,eACN,CAAC,KAAK,aACN,CAAC,CAAC,KAAK,gBAAgB,CAAC,CAAC,KAAK,WAE9B,MAAM,IAAI,MAAM,kCAAkC;EACpD,IACE,CAAC,OAAO,SAAS,KAAK,WAAW,QAAQ,CAAC,KACzC,KAAK,YACH,CAAC,OAAO,SAAS,KAAK,QAAQ,QAAQ,CAAC,KACtC,KAAK,UAAU,KAAK,YAExB,MAAM,IAAI,MAAM,sCAAsC;EACxD,IAAI,CAAC,KAAK,YAAY,CAAC,KAAK,cAC1B,MAAM,IAAI,MACR,yDACF;EACF,IAAI,KAAK,gBAAgB,CAAC,sBAAsB,SAAS,KAAK,YAAY,GACxE,MAAM,IAAI,MAAM,+BAA+B;EACjD,MAAM,SAAS,MAAM,KAAK,GAAG,IAAI,sBAAsB;GACrD,IAAI,KAAK;GACT,WAAW,KAAK;EAClB,CAAC;EACD,IACE,CAAC,UACD,KAAK,UAAU,QAAQ,IACrB,IAAI,KAAK,OAAO,UAAoB,CAAA,CAAE,QAAQ,KAC/C,OAAO,aACL,CAAC,KAAK,WACL,KAAK,QAAQ,QAAQ,IACnB,IAAI,KAAK,OAAO,QAAkB,CAAA,CAAE,QAAQ,IAElD,MAAM,IAAI,MAAM,gDAAgD;EAClE,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;EACJ,MAAM,YAAY,kBAAkB,IAAI;EACxC,IAAI,CAAC,SAAS,OAAO,YAAY,CAAC,WAChC,MAAM,IAAI,MACR,kEACF;EACF,IACE,UACC,MAAM,cAAc,KAAK,YACxB,MAAM,aAAa,KAAK,WACvB,CAAC,cACC,MAAM,kBAAkB,KAAK,eAC5B,MAAM,gBAAgB,KAAK,aAC3B,MAAM,kBAAkB,KAAK,eAC7B,MAAM,gBAAgB,KAAK,aAC3B,MAAM,SAAS,KAAK,QACpB,MAAM,gBAAgB,KAAK,eAC3B,IAAI,KAAK,MAAM,UAAoB,CAAA,CAAE,QAAQ,MAC3C,KAAK,UAAU,QAAQ,KAE/B,MAAM,IAAI,MAAM,wDAAwD;EAC1E,IACE,CAAC,aACD,OAAO,aACN,IAAI,KAAK,MAAM,QAAkB,CAAA,CAAE,QAAQ,MAC1C,KAAK,SAAS,QAAQ,KACtB,MAAM,kBAAkB,KAAK,eAE/B,MAAM,IAAI,MAAM,gCAAgC;EAClD,MAAM,QAAQ,KAAK,UAAU,QAAQ;EACrC,MAAM,MAAM,KAAK,SAAS,QAAQ,KAAK,OAAO;EAC9C,MAAM,YAAY,QAChB,SACG,IAAI,WACD,IAAI,KAAK,IAAI,QAAkB,CAAA,CAAE,QAAQ,IACzC,OAAO,sBACb,MAAM,IAAI,KAAK,IAAI,UAAoB,CAAA,CAAE,QAAQ;EACnD,MAAM,QAAQ;GAAE,WAAW,KAAK;GAAU,UAAU,KAAK;EAAQ;EACjE,KAAK,MAAM,KAAK,GAAG,KAAK,qBAAqB,KAAK,EAAA,CAAG,KAAK,QAAQ,GAChE,MAAM,IAAI,MAAM,mCAAmC;EACrD,KACG,MAAM,KAAK,GAAG,KAAK,KAAK,WAAW,KAAK,EAAA,CAAG,MACzC,QAAQ,IAAI,OAAO,KAAK,MAAM,SAAS,GAAG,CAC7C,GAEA,MAAM,IAAI,MAAM,4BAA4B;EAC9C,KAAK,kBAAkB,KAAK,UAAU,KAAK,OAAO,MAAM,SAAS,GAAI,IAAI;EACzE,KAAK,OAAO,OAAO,WAAW;EAC9B,KAAK,WAAW,KAAK,UAAU,KAAK,UAAU,CAAC,UAAU,KAAK,EAAE,CAAC,IAAI;EACrE,OAAO,MAAM,KAAK;CACpB;CAEA,MAAe,SAAwB;EAIrC,MAAM,kBAAkB,MAAM,KAAK,SAAS,KAAK,UAAU,YAAY;GACrE,MAAM,QAAQ,KAAK,KACf,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC,IACjD;GACJ,KAAK,KAAK,WAAW,OAAO,aAAa,CAAC,kBAAkB,IAAI,GAC9D,MAAM,IAAI,MACR,gFACF;GACF,MAAM,MAAM,OAAO;GACnB,OAAO;EACT,CAAC;CACH;AACF;AAtIoD,cAAvC,mBAA8C,mBAAkB,gCAAA;AAC/D,gBAAA,CAAX,SAAS,CAAA,GADC,kBACC,WAAA,YAAA,CAAA;AACiB,gBAAA,CAA5B,WAAW,eAAe,CAAA,GAFhB,kBAEkB,WAAA,WAAA,CAAA;AAkBF,gBAAA,CAA1B,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GApBd,kBAoBgB,WAAA,YAAA,CAAA;AApBhB,oBAAN,gBAAA,CAtBN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,SAAS;EACP;GACE,MAAM;GACN,SAAS;IAAC;IAAY;IAAW;GAAU;GAC3C,QAAQ;EACV;EACA;GACE,MAAM;GACN,SAAS;IAAC;IAAY;IAAe;GAAW;EAClD;EACA;GACE,MAAM;GACN,SAAS;IAAC;IAAY;IAAe;GAAW;EAClD;CACF;CACA,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;AAClC,CAAC,CAAA,GACY,iBAAA;AAsJb,SAAS,mBAAmB,OAAkD;CAC5E,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACxE,MAAM,SAAS;CACf,OACE,OAAO,OAAO,aAAa,YAC3B,OAAO,SAAS,SAAS,MACxB,OAAO,SAAS,KAAA,KACd,OAAO,OAAO,SAAS,YACtB,OAAO,KAAK,SAAS,KACrB,OAAO,UAAU,KAAA,OACpB,OAAO,cAAc,KAAA,KAAa,OAAO,OAAO,cAAc,cAC9D,OAAO,eAAe,KAAA,KACrB,OAAO,OAAO,eAAe,cAC7B,OAAO,OAAO,YAAY,YAC1B,OAAO,QAAQ,SAAS,KACxB,OAAO,UAAU,KAAA,KAChB,OAAO,OAAO,UAAU,YACvB,OAAO,MAAM,SAAS,KACtB,OAAO,YAAY,KAAA;AAE3B;AAWO,IAAM,mBAAN,cAA+B,WAAW;CACnC,WAAW;CACkB,YAAY;CAC5B,UAAU;CACV,UAAU;;CAGnC,aAAsC;EACpC,MAAM,QAAQ,mBAA4B,KAAK,SAAS,IAAI;EAC5D,IAAI,CAAC,mBAAmB,KAAK,GAC3B,MAAM,IAAI,MAAM,2CAA2C;EAC7D,OAAO;CACT;;CAGA,WAAW,OAAsC;EAC/C,IAAI,CAAC,mBAAmB,KAAK,GAC3B,MAAM,IAAI,MAAM,oCAAoC;EACtD,KAAK,UAAU,KAAK,UAAU,KAAK;CACrC;CAEA,MAAe,OAAsB;EACnC,KAAK,WAAW;EAChB,OAAO,MAAM,KAAK;CACpB;AACF;AAzBmD,cAAtC,kBAA6C,mBAAkB,gCAAA;AAC9D,gBAAA,CAAX,SAAS,CAAA,GADC,iBACC,WAAA,YAAA,CAAA;AAC6B,gBAAA,CAAxC,MAAM;CAAE,UAAU;CAAM,QAAQ;AAAK,CAAC,CAAA,GAF5B,iBAE8B,WAAA,aAAA,CAAA;AAChB,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAHZ,iBAGc,WAAA,WAAA,CAAA;AACA,gBAAA,CAAxB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAA,GAJZ,iBAIc,WAAA,WAAA,CAAA;AAJd,mBAAN,gBAAA,CARN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,WAAW;CACX,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;CACnB,KAAK,EAAE,SAAS,CAAC,EAAE;AACrB,CAAC,CAAA,GACY,gBAAA;AA4BN,IAAM,4BAAN,cAAwC,eAAgC;CAC7E,OAAgB,aAAa;AAC/B;AAEO,IAAM,4BAAN,cAAwC,eAAgC;CAC7E,OAAgB,aAAa;AAC/B;AAEO,IAAM,8BAAN,cAA0C,eAAkC;CACjF,OAAgB,aAAa;AAC/B;AAEO,IAAM,6BAAN,cAAyC,eAAiC;CAC/E,OAAgB,aAAa;AAC/B"}