@kernhq/module-hr 0.1.0

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 (75) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +71 -0
  3. package/dist/contract/capabilities.d.ts +45 -0
  4. package/dist/contract/capabilities.d.ts.map +1 -0
  5. package/dist/contract/capabilities.js +114 -0
  6. package/dist/contract/capabilities.js.map +1 -0
  7. package/dist/contract/events.d.ts +74 -0
  8. package/dist/contract/events.d.ts.map +1 -0
  9. package/dist/contract/events.js +63 -0
  10. package/dist/contract/events.js.map +1 -0
  11. package/dist/contract/index.d.ts +15 -0
  12. package/dist/contract/index.d.ts.map +1 -0
  13. package/dist/contract/index.js +15 -0
  14. package/dist/contract/index.js.map +1 -0
  15. package/dist/contract/models.d.ts +457 -0
  16. package/dist/contract/models.d.ts.map +1 -0
  17. package/dist/contract/models.js +381 -0
  18. package/dist/contract/models.js.map +1 -0
  19. package/dist/contract/permissions.d.ts +174 -0
  20. package/dist/contract/permissions.d.ts.map +1 -0
  21. package/dist/contract/permissions.js +209 -0
  22. package/dist/contract/permissions.js.map +1 -0
  23. package/dist/contract/router.d.ts +2720 -0
  24. package/dist/contract/router.d.ts.map +1 -0
  25. package/dist/contract/router.js +520 -0
  26. package/dist/contract/router.js.map +1 -0
  27. package/dist/contract/settings.d.ts +20 -0
  28. package/dist/contract/settings.d.ts.map +1 -0
  29. package/dist/contract/settings.js +34 -0
  30. package/dist/contract/settings.js.map +1 -0
  31. package/dist/policy/calendar.d.ts +64 -0
  32. package/dist/policy/calendar.d.ts.map +1 -0
  33. package/dist/policy/calendar.js +113 -0
  34. package/dist/policy/calendar.js.map +1 -0
  35. package/dist/server/index.d.ts +8 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +141 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/packs/index.d.ts +50 -0
  40. package/dist/server/packs/index.d.ts.map +1 -0
  41. package/dist/server/packs/index.js +121 -0
  42. package/dist/server/packs/index.js.map +1 -0
  43. package/dist/server/router.d.ts +3095 -0
  44. package/dist/server/router.d.ts.map +1 -0
  45. package/dist/server/router.js +1537 -0
  46. package/dist/server/router.js.map +1 -0
  47. package/dist/server/schema.d.ts +2828 -0
  48. package/dist/server/schema.d.ts.map +1 -0
  49. package/dist/server/schema.js +313 -0
  50. package/dist/server/schema.js.map +1 -0
  51. package/dist/server/services/db.d.ts +22 -0
  52. package/dist/server/services/db.d.ts.map +1 -0
  53. package/dist/server/services/db.js +23 -0
  54. package/dist/server/services/db.js.map +1 -0
  55. package/dist/server/services/people.d.ts +110 -0
  56. package/dist/server/services/people.d.ts.map +1 -0
  57. package/dist/server/services/people.js +182 -0
  58. package/dist/server/services/people.js.map +1 -0
  59. package/dist/server/services/resolve.d.ts +66 -0
  60. package/dist/server/services/resolve.d.ts.map +1 -0
  61. package/dist/server/services/resolve.js +145 -0
  62. package/dist/server/services/resolve.js.map +1 -0
  63. package/migrations/0000_init.sql +223 -0
  64. package/migrations/0001_rls.sql +141 -0
  65. package/migrations/meta/0000_snapshot.json +1715 -0
  66. package/migrations/meta/_journal.json +20 -0
  67. package/package.json +59 -0
  68. package/src/client/index.ts +62 -0
  69. package/src/contract/capabilities.ts +117 -0
  70. package/src/contract/events.ts +84 -0
  71. package/src/contract/index.ts +14 -0
  72. package/src/contract/models.ts +437 -0
  73. package/src/contract/permissions.ts +217 -0
  74. package/src/contract/router.ts +611 -0
  75. package/src/contract/settings.ts +35 -0
@@ -0,0 +1,141 @@
1
+ -- Row-level security, plus the constraints that carry this module's real invariants.
2
+ --
3
+ -- RLS is the last line, not the first: the API checks membership and permission before anything
4
+ -- reaches here. It exists for the query that skips them — a job, a report, a mistake — and for
5
+ -- anyone who reaches the database another way. A tenant table without a policy is simply readable.
6
+ --
7
+ -- Superusers bypass RLS, so a database owned by one will pass a test that proves nothing. Run the
8
+ -- application as a plain role.
9
+
10
+ alter table "mod_hr"."legal_entities" enable row level security;--> statement-breakpoint
11
+ alter table "mod_hr"."legal_entities" force row level security;--> statement-breakpoint
12
+ create policy "legal_entities_ws_isolation" on "mod_hr"."legal_entities"
13
+ using (workspace_id::text = current_setting('app.workspace_id', true))
14
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
15
+
16
+ alter table "mod_hr"."offices" enable row level security;--> statement-breakpoint
17
+ alter table "mod_hr"."offices" force row level security;--> statement-breakpoint
18
+ create policy "offices_ws_isolation" on "mod_hr"."offices"
19
+ using (workspace_id::text = current_setting('app.workspace_id', true))
20
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
21
+
22
+ alter table "mod_hr"."cost_centers" enable row level security;--> statement-breakpoint
23
+ alter table "mod_hr"."cost_centers" force row level security;--> statement-breakpoint
24
+ create policy "cost_centers_ws_isolation" on "mod_hr"."cost_centers"
25
+ using (workspace_id::text = current_setting('app.workspace_id', true))
26
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
27
+
28
+ alter table "mod_hr"."org_units" enable row level security;--> statement-breakpoint
29
+ alter table "mod_hr"."org_units" force row level security;--> statement-breakpoint
30
+ create policy "org_units_ws_isolation" on "mod_hr"."org_units"
31
+ using (workspace_id::text = current_setting('app.workspace_id', true))
32
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
33
+
34
+ alter table "mod_hr"."positions" enable row level security;--> statement-breakpoint
35
+ alter table "mod_hr"."positions" force row level security;--> statement-breakpoint
36
+ create policy "positions_ws_isolation" on "mod_hr"."positions"
37
+ using (workspace_id::text = current_setting('app.workspace_id', true))
38
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
39
+
40
+ alter table "mod_hr"."people" enable row level security;--> statement-breakpoint
41
+ alter table "mod_hr"."people" force row level security;--> statement-breakpoint
42
+ create policy "people_ws_isolation" on "mod_hr"."people"
43
+ using (workspace_id::text = current_setting('app.workspace_id', true))
44
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
45
+
46
+ alter table "mod_hr"."people_sensitive" enable row level security;--> statement-breakpoint
47
+ alter table "mod_hr"."people_sensitive" force row level security;--> statement-breakpoint
48
+ create policy "people_sensitive_ws_isolation" on "mod_hr"."people_sensitive"
49
+ using (workspace_id::text = current_setting('app.workspace_id', true))
50
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
51
+
52
+ alter table "mod_hr"."employments" enable row level security;--> statement-breakpoint
53
+ alter table "mod_hr"."employments" force row level security;--> statement-breakpoint
54
+ create policy "employments_ws_isolation" on "mod_hr"."employments"
55
+ using (workspace_id::text = current_setting('app.workspace_id', true))
56
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
57
+
58
+ alter table "mod_hr"."office_assignments" enable row level security;--> statement-breakpoint
59
+ alter table "mod_hr"."office_assignments" force row level security;--> statement-breakpoint
60
+ create policy "office_assignments_ws_isolation" on "mod_hr"."office_assignments"
61
+ using (workspace_id::text = current_setting('app.workspace_id', true))
62
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
63
+
64
+ alter table "mod_hr"."person_history" enable row level security;--> statement-breakpoint
65
+ alter table "mod_hr"."person_history" force row level security;--> statement-breakpoint
66
+ create policy "person_history_ws_isolation" on "mod_hr"."person_history"
67
+ using (workspace_id::text = current_setting('app.workspace_id', true))
68
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
69
+
70
+ alter table "mod_hr"."person_documents" enable row level security;--> statement-breakpoint
71
+ alter table "mod_hr"."person_documents" force row level security;--> statement-breakpoint
72
+ create policy "person_documents_ws_isolation" on "mod_hr"."person_documents"
73
+ using (workspace_id::text = current_setting('app.workspace_id', true))
74
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
75
+
76
+ alter table "mod_hr"."custom_field_defs" enable row level security;--> statement-breakpoint
77
+ alter table "mod_hr"."custom_field_defs" force row level security;--> statement-breakpoint
78
+ create policy "custom_field_defs_ws_isolation" on "mod_hr"."custom_field_defs"
79
+ using (workspace_id::text = current_setting('app.workspace_id', true))
80
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
81
+
82
+ alter table "mod_hr"."calendars" enable row level security;--> statement-breakpoint
83
+ alter table "mod_hr"."calendars" force row level security;--> statement-breakpoint
84
+ create policy "calendars_ws_isolation" on "mod_hr"."calendars"
85
+ using (workspace_id::text = current_setting('app.workspace_id', true))
86
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
87
+
88
+ alter table "mod_hr"."calendar_days" enable row level security;--> statement-breakpoint
89
+ alter table "mod_hr"."calendar_days" force row level security;--> statement-breakpoint
90
+ create policy "calendar_days_ws_isolation" on "mod_hr"."calendar_days"
91
+ using (workspace_id::text = current_setting('app.workspace_id', true))
92
+ with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
93
+
94
+ -- ---------------------------------------------------------------------------------------------
95
+ -- The invariants, in the database rather than in application code.
96
+ --
97
+ -- Every one of these is something two concurrent requests could otherwise both "win". Enforcing
98
+ -- them here means the loser gets a constraint violation instead of the system holding two
99
+ -- contradictory answers to a question an auditor will eventually ask.
100
+
101
+ create extension if not exists btree_gist;--> statement-breakpoint
102
+
103
+ -- One workspace, one default office. It is where a person with no assignment lands and where the
104
+ -- resolution ladder bottoms out, so two of them would make "which calendar applies" ambiguous.
105
+ create unique index "hr_offices_one_default_per_ws"
106
+ on "mod_hr"."offices" (workspace_id)
107
+ where is_default;--> statement-breakpoint
108
+
109
+ -- No two employment rows for one person may overlap. Without this, a backdated correction racing a
110
+ -- forward-dated change leaves two rows in force on the same day, and "what was her FTE in March"
111
+ -- has two answers.
112
+ alter table "mod_hr"."employments"
113
+ add constraint "hr_employments_no_overlap"
114
+ exclude using gist (
115
+ person_id with =,
116
+ daterange(effective_from, effective_to, '[]') with &&
117
+ );--> statement-breakpoint
118
+
119
+ -- Nor may one person hold the same office twice over an overlapping period.
120
+ alter table "mod_hr"."office_assignments"
121
+ add constraint "hr_office_assignments_no_duplicate"
122
+ exclude using gist (
123
+ person_id with =,
124
+ office_id with =,
125
+ daterange(effective_from, effective_to, '[]') with &&
126
+ );--> statement-breakpoint
127
+
128
+ -- And exactly one of a person's concurrent assignments may be primary. Only the primary office
129
+ -- decides holidays, timezone and policy; two would mean two answers, which is the one thing the
130
+ -- multi-office model must never allow.
131
+ alter table "mod_hr"."office_assignments"
132
+ add constraint "hr_office_assignments_one_primary"
133
+ exclude using gist (
134
+ person_id with =,
135
+ daterange(effective_from, effective_to, '[]') with &&
136
+ ) where (is_primary);--> statement-breakpoint
137
+
138
+ -- An org unit's ltree path is unique within a workspace: two departments at the same path would
139
+ -- make a subtree query return one of them arbitrarily.
140
+ create unique index "hr_org_units_ws_path_uq" on "mod_hr"."org_units" (workspace_id, path);--> statement-breakpoint
141
+ create index "hr_org_units_path_gist" on "mod_hr"."org_units" using gist (path);