ekwo-os 0.7.0 → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "schemaVersion": "0.7.0",
3
- "migration": "20260922124012",
2
+ "schemaVersion": "0.8.0",
3
+ "migration": "20260923093000",
4
4
  "socle": {
5
5
  "schema": "public",
6
6
  "tables": [
@@ -3743,6 +3743,10 @@
3743
3743
  "name": "amount_text_format",
3744
3744
  "arguments": "p_rounding money_rounding"
3745
3745
  },
3746
+ {
3747
+ "name": "api_key_company",
3748
+ "arguments": ""
3749
+ },
3746
3750
  {
3747
3751
  "name": "assert_may_close_year",
3748
3752
  "arguments": ""
@@ -3931,6 +3935,10 @@
3931
3935
  "name": "declaration_period_start",
3932
3936
  "arguments": "p_period declaration_period, p_day date"
3933
3937
  },
3938
+ {
3939
+ "name": "default_statement_code",
3940
+ "arguments": "p_company_id uuid, p_kind text, p_at date"
3941
+ },
3934
3942
  {
3935
3943
  "name": "disable_module",
3936
3944
  "arguments": "p_company_id uuid, p_code text"
@@ -3999,6 +4007,10 @@
3999
4007
  "name": "ec_sales_list",
4000
4008
  "arguments": "p_company_id uuid, p_from date, p_to date, p_report_code text"
4001
4009
  },
4010
+ {
4011
+ "name": "ekwo_pre_request",
4012
+ "arguments": ""
4013
+ },
4002
4014
  {
4003
4015
  "name": "ekwo_schema_version",
4004
4016
  "arguments": ""
@@ -4095,6 +4107,10 @@
4095
4107
  "name": "financial_statement",
4096
4108
  "arguments": "p_company_id uuid, p_statement_code text, p_from date, p_to date"
4097
4109
  },
4110
+ {
4111
+ "name": "financial_statement_of_kind",
4112
+ "arguments": "p_company_id uuid, p_kind text, p_from date, p_to date"
4113
+ },
4098
4114
  {
4099
4115
  "name": "fiscal_year_at",
4100
4116
  "arguments": "p_company_id uuid, p_date date"
@@ -4151,6 +4167,10 @@
4151
4167
  "name": "install_country_template",
4152
4168
  "arguments": "p_company_id uuid, p_country character, p_language character, p_chart_code text"
4153
4169
  },
4170
+ {
4171
+ "name": "installed_schema_version",
4172
+ "arguments": ""
4173
+ },
4154
4174
  {
4155
4175
  "name": "instance_has_no_admin",
4156
4176
  "arguments": ""
@@ -4183,6 +4203,10 @@
4183
4203
  "name": "is_instance_admin",
4184
4204
  "arguments": ""
4185
4205
  },
4206
+ {
4207
+ "name": "is_known_caller",
4208
+ "arguments": ""
4209
+ },
4186
4210
  {
4187
4211
  "name": "label_for",
4188
4212
  "arguments": "p_name text, p_i18n jsonb, p_languages text[]"
@@ -4315,6 +4339,10 @@
4315
4339
  "name": "prepare_filing",
4316
4340
  "arguments": "p_company_id uuid, p_from date, p_to date, p_report_code text"
4317
4341
  },
4342
+ {
4343
+ "name": "present_api_key",
4344
+ "arguments": "p_secret text"
4345
+ },
4318
4346
  {
4319
4347
  "name": "purge_audit_log",
4320
4348
  "arguments": "p_before date"
@@ -6817,6 +6845,19 @@
6817
6845
  "execute"
6818
6846
  ]
6819
6847
  },
6848
+ {
6849
+ "name": "api_key_company",
6850
+ "arguments": "",
6851
+ "anon": [
6852
+ "execute"
6853
+ ],
6854
+ "authenticated": [
6855
+ "execute"
6856
+ ],
6857
+ "service_role": [
6858
+ "execute"
6859
+ ]
6860
+ },
6820
6861
  {
6821
6862
  "name": "assert_may_close_year",
6822
6863
  "arguments": "",
@@ -7281,6 +7322,17 @@
7281
7322
  "execute"
7282
7323
  ]
7283
7324
  },
7325
+ {
7326
+ "name": "default_statement_code",
7327
+ "arguments": "p_company_id uuid, p_kind text, p_at date",
7328
+ "anon": [],
7329
+ "authenticated": [
7330
+ "execute"
7331
+ ],
7332
+ "service_role": [
7333
+ "execute"
7334
+ ]
7335
+ },
7284
7336
  {
7285
7337
  "name": "disable_module",
7286
7338
  "arguments": "p_company_id uuid, p_code text",
@@ -7441,6 +7493,19 @@
7441
7493
  "execute"
7442
7494
  ]
7443
7495
  },
7496
+ {
7497
+ "name": "ekwo_pre_request",
7498
+ "arguments": "",
7499
+ "anon": [
7500
+ "execute"
7501
+ ],
7502
+ "authenticated": [
7503
+ "execute"
7504
+ ],
7505
+ "service_role": [
7506
+ "execute"
7507
+ ]
7508
+ },
7444
7509
  {
7445
7510
  "name": "ekwo_schema_version",
7446
7511
  "arguments": "",
@@ -7684,6 +7749,17 @@
7684
7749
  "execute"
7685
7750
  ]
7686
7751
  },
7752
+ {
7753
+ "name": "financial_statement_of_kind",
7754
+ "arguments": "p_company_id uuid, p_kind text, p_from date, p_to date",
7755
+ "anon": [],
7756
+ "authenticated": [
7757
+ "execute"
7758
+ ],
7759
+ "service_role": [
7760
+ "execute"
7761
+ ]
7762
+ },
7687
7763
  {
7688
7764
  "name": "fiscal_year_at",
7689
7765
  "arguments": "p_company_id uuid, p_date date",
@@ -7834,6 +7910,19 @@
7834
7910
  "execute"
7835
7911
  ]
7836
7912
  },
7913
+ {
7914
+ "name": "installed_schema_version",
7915
+ "arguments": "",
7916
+ "anon": [
7917
+ "execute"
7918
+ ],
7919
+ "authenticated": [
7920
+ "execute"
7921
+ ],
7922
+ "service_role": [
7923
+ "execute"
7924
+ ]
7925
+ },
7837
7926
  {
7838
7927
  "name": "instance_has_no_admin",
7839
7928
  "arguments": "",
@@ -7932,6 +8021,19 @@
7932
8021
  "execute"
7933
8022
  ]
7934
8023
  },
8024
+ {
8025
+ "name": "is_known_caller",
8026
+ "arguments": "",
8027
+ "anon": [
8028
+ "execute"
8029
+ ],
8030
+ "authenticated": [
8031
+ "execute"
8032
+ ],
8033
+ "service_role": [
8034
+ "execute"
8035
+ ]
8036
+ },
7935
8037
  {
7936
8038
  "name": "label_for",
7937
8039
  "arguments": "p_name text, p_i18n jsonb, p_languages text[]",
@@ -8294,6 +8396,19 @@
8294
8396
  "execute"
8295
8397
  ]
8296
8398
  },
8399
+ {
8400
+ "name": "present_api_key",
8401
+ "arguments": "p_secret text",
8402
+ "anon": [
8403
+ "execute"
8404
+ ],
8405
+ "authenticated": [
8406
+ "execute"
8407
+ ],
8408
+ "service_role": [
8409
+ "execute"
8410
+ ]
8411
+ },
8297
8412
  {
8298
8413
  "name": "purge_audit_log",
8299
8414
  "arguments": "p_before date",
@@ -0,0 +1,52 @@
1
+ -- Ekwo OS — the version of the schema, for somebody who is on no company yet.
2
+ --
3
+ -- A client of this schema has one question to ask before it draws anything:
4
+ -- is this installation new enough for the code I ship? The answer is
5
+ -- `instance.schema_version`, and the policy on that table admits members of
6
+ -- at least one company and instance administrators — for the reason written
7
+ -- in `20260911140000`: self sign-up is on by default on a Supabase project,
8
+ -- so a signed-in stranger is ordinary and the organisation's name, its country
9
+ -- and the day it was installed are not theirs to read.
10
+ --
11
+ -- A freshly created account is exactly that stranger. It signs in, reads no
12
+ -- row, and the client cannot tell an instance behind its schema from one
13
+ -- whose row it was never allowed to see. Both are silence, and the two call
14
+ -- for opposite behaviour: print `ekwo migrate`, or say nothing and carry on.
15
+ -- A web application meets this on its first screen, with an account whose
16
+ -- invitation has not arrived yet.
17
+ --
18
+ -- So the version leaves the table and becomes a question of its own.
19
+ -- `installed_schema_version()` is definer — it reads the singleton row past a
20
+ -- policy that was written about the rest of it — and it returns two fields
21
+ -- and no others: the version the migrations wrote, and the edition, which is
22
+ -- `community` or `cloud` and gates nothing anywhere in this repository.
23
+ -- Whoever the caller is, what they learn is what release this software is at.
24
+ -- Nothing about the customer comes with it.
25
+ --
26
+ -- **`anon` does not get it**, and the exception is not worth making. Decision
27
+ -- 0002 keeps the anonymous surface closed, and the list of what `anon` may
28
+ -- execute is the policy helpers, which answer about `auth.uid()` and therefore
29
+ -- say nothing, plus `shared_document()`, which somebody was handed a link to.
30
+ -- The version is not in that shape: nobody asks it before signing in — the
31
+ -- client asks it *after*, which is the moment the old answer went missing —
32
+ -- and granting it would hand an unauthenticated scanner the exact release an
33
+ -- installation runs, for a convenience nothing needs. A reader who needs it
34
+ -- signs in first.
35
+
36
+ create or replace function installed_schema_version()
37
+ returns table (schema_version text, edition text)
38
+ language sql
39
+ stable
40
+ security definer
41
+ set search_path = public, pg_temp
42
+ as $$
43
+ select i.schema_version, i.edition::text from instance i where i.id = 1;
44
+ $$;
45
+
46
+ comment on function installed_schema_version() is
47
+ 'The schema version this installation runs, and its edition. Definer, so a signed-in account that is on no company yet can still tell an instance behind its schema from one it may not read the row of — which is the same silence otherwise. Nothing else of the instance row comes with it.';
48
+
49
+ revoke execute on all functions in schema public from public;
50
+
51
+ revoke execute on function installed_schema_version() from public, anon;
52
+ grant execute on function installed_schema_version() to authenticated, service_role;
@@ -0,0 +1,117 @@
1
+ -- Ekwo OS — asking for a balance sheet without knowing which pack answers.
2
+ --
3
+ -- `financial_statement()` takes `p_statement_code`, and a code belongs to a
4
+ -- country pack: `BE-BNB-ABBR-BS`, `FR-2050`, the generic `IFRS-SME-BS`. A
5
+ -- client that knows which company it is looking at does not know that string,
6
+ -- and it must not learn it — writing `FR-2050` into an application is writing
7
+ -- a country into code, one screen at a time, which is the thing
8
+ -- `docs/decisions/0025` exists to stop.
9
+ --
10
+ -- `available_statements(company, at)` has been the way out since the day the
11
+ -- statements landed: it gives every scheme that company may ask for, with its
12
+ -- kind and whether its chart declares it. What was missing is the step after
13
+ -- it — going from *a balance sheet* to the one statement that answers, and
14
+ -- doing it the same way twice. Every caller that wanted one wrote its own
15
+ -- choice: take the first row, prefer `is_default`, prefer the country over the
16
+ -- generic. Three readers, three orders, and the day a pack carries two balance
17
+ -- sheets they disagree about which is the company's.
18
+ --
19
+ -- Two functions, and no new rule about accounting:
20
+ --
21
+ -- `default_statement_code(company, kind, at)` — the one scheme of that kind
22
+ -- this company reports on. The order is the pack's own preferences,
23
+ -- stated once: a statement the company's chart declares
24
+ -- (`chart_templates.statements`, which is what `is_default` marks) comes
25
+ -- first; then one of the company's country, because a national scheme is
26
+ -- what an administration reads; then the generic framework, which fits
27
+ -- any chart and is the fallback and never the first answer. `code`
28
+ -- settles a tie, so the answer does not depend on the order rows were
29
+ -- loaded in.
30
+ --
31
+ -- `financial_statement_of_kind(company, kind, from, to)` — the same table
32
+ -- `financial_statement()` returns, resolved that way, with the code it
33
+ -- chose in the first column. The code comes back because a reader has to
34
+ -- be able to say which scheme it is printing, and because the next call
35
+ -- — `unmapped_accounts()`, an XBRL export — takes the code.
36
+ --
37
+ -- The kind is the one the schema already has: `balance_sheet`,
38
+ -- `income_statement`, `allocation`, `cash_flow` — the vocabulary of
39
+ -- `statement_templates.kind`, not a new one. A company with no statement of
40
+ -- the kind asked for is told so, and told what it does have; a company that
41
+ -- installed no pack at all has the generic framework and so has both of the
42
+ -- two that are produced.
43
+ --
44
+ -- The date the validity is read at is `p_to`: a statement is the one that was
45
+ -- in force at the end of the period being reported, not the one in force
46
+ -- today. A scheme replaced last month still prints last year.
47
+
48
+ create or replace function default_statement_code(
49
+ p_company_id uuid,
50
+ p_kind text,
51
+ p_at date default null
52
+ )
53
+ returns text
54
+ language sql
55
+ stable
56
+ as $$
57
+ select s.code
58
+ from available_statements(p_company_id, p_at) s
59
+ where s.kind = p_kind
60
+ order by s.is_default desc, (s.country is null), s.code
61
+ limit 1;
62
+ $$;
63
+
64
+ comment on function default_statement_code(uuid, text, date) is
65
+ 'The statement of a kind this company reports on: one its chart declares, else one of its country, else the generic framework. Null when it has none of that kind. The one place that choice is made, so two readers cannot disagree about which balance sheet is the company''s.';
66
+
67
+ create or replace function financial_statement_of_kind(
68
+ p_company_id uuid,
69
+ p_kind text,
70
+ p_from date,
71
+ p_to date
72
+ )
73
+ returns table (
74
+ statement_code text,
75
+ line_code text,
76
+ parent_code text,
77
+ name text,
78
+ sequence integer,
79
+ is_total boolean,
80
+ amount numeric,
81
+ xbrl_element text
82
+ )
83
+ language plpgsql
84
+ stable
85
+ as $$
86
+ declare
87
+ v_code text;
88
+ v_kinds text;
89
+ begin
90
+ if not exists (select 1 from companies c where c.id = p_company_id) then
91
+ raise exception 'unknown_company: %', p_company_id;
92
+ end if;
93
+
94
+ v_code := default_statement_code(p_company_id, p_kind, p_to);
95
+ if v_code is null then
96
+ select string_agg(distinct s.kind, ', ' order by s.kind) into v_kinds
97
+ from available_statements(p_company_id, p_to) s;
98
+ raise exception 'unknown_statement: this company reports no % — it reports %. available_statements() names each one by code.',
99
+ p_kind, coalesce(v_kinds, 'nothing');
100
+ end if;
101
+
102
+ return query
103
+ select v_code, f.line_code, f.parent_code, f.name, f.sequence,
104
+ f.is_total, f.amount, f.xbrl_element
105
+ from financial_statement(p_company_id, v_code, p_from, p_to) f;
106
+ end;
107
+ $$;
108
+
109
+ comment on function financial_statement_of_kind(uuid, text, date, date) is
110
+ 'One financial statement of a company by kind — balance_sheet, income_statement, allocation, cash_flow — for a caller that does not know which scheme its country pack carries. Resolves through default_statement_code() at the end date of the period, then answers as financial_statement() does, with the code it chose in the first column.';
111
+
112
+ revoke execute on all functions in schema public from public;
113
+
114
+ revoke execute on function default_statement_code(uuid, text, date) from public, anon;
115
+ grant execute on function default_statement_code(uuid, text, date) to authenticated, service_role;
116
+ revoke execute on function financial_statement_of_kind(uuid, text, date, date) from public, anon;
117
+ grant execute on function financial_statement_of_kind(uuid, text, date, date) to authenticated, service_role;
@@ -0,0 +1,265 @@
1
+ -- Ekwo OS — a machine key that reaches the API, and not only a connection.
2
+ --
3
+ -- Decision 0006 wrote the limit down rather than hiding it: "because PostgREST
4
+ -- runs each request in its own transaction, a key is for a client holding a
5
+ -- connection". `use_api_key()` puts the key's fingerprint in `ekwo.api_key`
6
+ -- transaction-locally, and two REST calls are two transactions, so the key was
7
+ -- never presented when the work ran. Anything operated for somebody else — a
8
+ -- backup, a scheduled report, a script on a host with no psql — had to hold a
9
+ -- password of theirs or their `service_role` key. Both are worse than the key
10
+ -- the schema already mints.
11
+ --
12
+ -- The same decision left one question open, in as many words: "Deciding what
13
+ -- of `companies` a key may read is an open question, recorded rather than
14
+ -- patched per function." This answers it.
15
+ --
16
+ -- ---------------------------------------------------------------------------
17
+ -- 1. The key travels in a header, and the transaction reads it
18
+ -- ---------------------------------------------------------------------------
19
+ --
20
+ -- PostgREST calls one function of our choosing at the start of every request's
21
+ -- transaction — `pgrst.db_pre_request` — which is exactly the place
22
+ -- `use_api_key()` was missing. `ekwo_pre_request()` reads
23
+ -- `X-Ekwo-Api-Key` off `request.headers` and presents the key. No header, no
24
+ -- effect: a request that carries none is the request it was before this
25
+ -- migration, down to the role it runs as.
26
+ --
27
+ -- The name of the header is ours and not `Authorization`: PostgREST reads the
28
+ -- role out of that one, and a value it cannot parse as a JWT fails the request
29
+ -- before any function of ours runs. Two doors, two names.
30
+ --
31
+ -- ---------------------------------------------------------------------------
32
+ -- 2. The role, which is the part that is not obvious
33
+ -- ---------------------------------------------------------------------------
34
+ --
35
+ -- A request carrying only the publishable key runs as `anon`, and `anon` holds
36
+ -- no privilege on any table of this schema — decision 0003, and
37
+ -- `tests/grants.test.ts` asserts it. Row level security never gets a chance to
38
+ -- decide: the grant refuses first. So presenting a key has to move the request
39
+ -- off `anon`, and `ekwo_pre_request()` does that, *after* the key has been
40
+ -- checked and never before:
41
+ --
42
+ -- set_config('role', 'authenticated', true)
43
+ --
44
+ -- Three things make that sound rather than clever.
45
+ --
46
+ -- **It is not a definer function.** PostgreSQL refuses `set role` inside
47
+ -- SECURITY DEFINER — "cannot set parameter role within security-definer
48
+ -- function" — so `ekwo_pre_request()` runs as its caller, and the privilege
49
+ -- check is the ordinary one: the session user is `authenticator`, which is a
50
+ -- member of `authenticated`, and that is what makes the switch legal. A role
51
+ -- that is a member of nothing is refused by the server, not by us.
52
+ --
53
+ -- **Nothing is reached before the key is proven.** `present_api_key()` raises
54
+ -- on a key that is unknown, withdrawn or expired, and a raise inside the
55
+ -- pre-request fails the whole request. The switch is on the line after it.
56
+ --
57
+ -- **`authenticated` is not a person.** `auth.uid()` stays null — a key is not
58
+ -- a session, which is the sentence decision 0006 opens with — so every policy
59
+ -- that asks for a signed-in user still answers no, and what the caller may do
60
+ -- is decided by `has_capability()`, which has answered for a key since the
61
+ -- keys landed. The role is the door; the capabilities are the rooms.
62
+ --
63
+ -- ---------------------------------------------------------------------------
64
+ -- 3. What a key is on: its own company
65
+ -- ---------------------------------------------------------------------------
66
+ --
67
+ -- Past the grants, five policies still refused, and they are the ones that ask
68
+ -- `is_company_member()` rather than a capability: the company row itself, its
69
+ -- members, which modules it has on, how often it files, its matching settings,
70
+ -- its audit trail. They are structural — a caller that cannot read
71
+ -- `companies` cannot export the company, cannot round at its currency and
72
+ -- cannot find its financial year — and none of them has a capability of its
73
+ -- own, by decision 0004: "no capability without something that can refuse on
74
+ -- it".
75
+ --
76
+ -- So the answer is written once, where the question is asked:
77
+ --
78
+ -- a key of a company is on that company.
79
+ --
80
+ -- `is_company_member()` says true for the company the presented key belongs
81
+ -- to, and every policy that asks it follows without being rewritten —
82
+ -- including `module_enabled()`, which is the same question for a module's
83
+ -- tables. A key is still narrower than the member who issued it: every
84
+ -- capability on it was one the issuer held, and every table that has a
85
+ -- capability keeps asking for it. What it gains is the handful of rows that
86
+ -- describe the company it was minted for, which is the company it can already
87
+ -- read the ledger of.
88
+ --
89
+ -- Widened deliberately and stated so a reviewer can object: whatever its
90
+ -- capabilities, a key reads its company's row, its members, its modules, its
91
+ -- filing periods, its matching settings and its audit trail — exactly what the
92
+ -- narrowest member of that company reads. It reads nothing of any other
93
+ -- company, because `api_keys.company_id` is one company and there is no second
94
+ -- one to name.
95
+ --
96
+ -- The reference tables of the installation — currencies, charts, statement
97
+ -- schemes, the capability list — ask a different question again,
98
+ -- `auth.uid() is not null`, and they are the subject of the migration that
99
+ -- follows this one: they are not a company's rows, and what they were reaching
100
+ -- for was never a session but a caller this installation knows.
101
+
102
+ -- ---------------------------------------------------------------------------
103
+ -- The company of the key presented in this transaction
104
+ -- ---------------------------------------------------------------------------
105
+
106
+ create or replace function api_key_company()
107
+ returns uuid
108
+ language sql
109
+ stable
110
+ security definer
111
+ set search_path = public, pg_temp
112
+ as $$
113
+ select k.company_id
114
+ from api_keys k
115
+ where k.key_hash = nullif(current_setting('ekwo.api_key', true), '')
116
+ and k.revoked_at is null
117
+ and (k.expires_at is null or k.expires_at > now());
118
+ $$;
119
+
120
+ comment on function api_key_company() is
121
+ 'The company of the machine key presented in this transaction, or nothing. What `is_company_member()` asks so that a key is on the company it was minted for, and the only thing of a key the policy helpers read.';
122
+
123
+ -- ---------------------------------------------------------------------------
124
+ -- A member, or a key of this company
125
+ --
126
+ -- The body is the only place the rule lives. `company_role()` still answers
127
+ -- about `auth.uid()` alone, and stays the answer to "which preset is this
128
+ -- person on" — a key is on no preset, holds a list, and must not be made to
129
+ -- look like a role.
130
+ -- ---------------------------------------------------------------------------
131
+
132
+ create or replace function is_company_member(p_company_id uuid)
133
+ returns boolean
134
+ language sql
135
+ stable
136
+ as $$
137
+ -- `coalesce`, and decision 0005 is why: `p_company_id = api_key_company()`
138
+ -- is NULL when no key is presented, `false or NULL` is NULL, and a guard
139
+ -- written as `if not is_company_member(...)` would not fire for a stranger.
140
+ -- A helper a guard is written on answers true or false and never NULL.
141
+ select company_role(p_company_id) is not null
142
+ or coalesce(p_company_id = api_key_company(), false);
143
+ $$;
144
+
145
+ comment on function is_company_member(uuid) is
146
+ 'Whether the caller is on a company: a member of it, or the holder of a machine key minted on it. False, never NULL, for a stranger.';
147
+
148
+ create or replace function is_any_company_member()
149
+ returns boolean
150
+ language sql
151
+ stable
152
+ security definer
153
+ set search_path = public, pg_temp
154
+ as $$
155
+ select exists (select 1 from company_members m where m.user_id = auth.uid())
156
+ or api_key_company() is not null;
157
+ $$;
158
+
159
+ comment on function is_any_company_member() is
160
+ 'Whether the caller belongs to at least one company of this installation, as a member or as the holder of a machine key.';
161
+
162
+ -- ---------------------------------------------------------------------------
163
+ -- Presenting a key without reading one
164
+ --
165
+ -- `use_api_key()` returns the row, which is what a client holding a connection
166
+ -- wants and what a pre-request must not hand back: the pre-request is called
167
+ -- by `anon`, so whatever it can call, an anonymous caller can call. This
168
+ -- returns nothing at all, and the row — `key_hash` included — stays behind the
169
+ -- grant it has always been behind.
170
+ -- ---------------------------------------------------------------------------
171
+
172
+ create or replace function present_api_key(p_secret text)
173
+ returns void
174
+ language plpgsql
175
+ security definer
176
+ set search_path = public, pg_temp
177
+ as $$
178
+ begin
179
+ perform use_api_key(p_secret);
180
+ end;
181
+ $$;
182
+
183
+ comment on function present_api_key(text) is
184
+ 'Presents a machine key for the current transaction and answers nothing. What `ekwo_pre_request()` calls, and the only form of the question an anonymous caller may ask: the refusals of use_api_key() word for word, and no row.';
185
+
186
+ -- ---------------------------------------------------------------------------
187
+ -- The pre-request
188
+ --
189
+ -- Invoker, deliberately: PostgreSQL refuses `set role` inside a definer
190
+ -- function, and the privilege that makes the switch legal is the session's —
191
+ -- `authenticator` is a member of `authenticated` — not ours to lend.
192
+ --
193
+ -- `request.headers` is set by PostgREST and absent everywhere else, so the
194
+ -- function is a no-op on a direct connection, which is what the command line
195
+ -- and the MCP server use.
196
+ -- ---------------------------------------------------------------------------
197
+
198
+ create or replace function ekwo_pre_request()
199
+ returns void
200
+ language plpgsql
201
+ set search_path = public, pg_temp
202
+ as $$
203
+ declare
204
+ v_headers json := nullif(current_setting('request.headers', true), '')::json;
205
+ v_secret text := nullif(v_headers ->> 'x-ekwo-api-key', '');
206
+ begin
207
+ if v_secret is null then
208
+ return;
209
+ end if;
210
+
211
+ -- Raises on a key that is unknown, withdrawn or expired, and the raise fails
212
+ -- the request. Nothing below this line runs for a key that is not one.
213
+ perform present_api_key(v_secret);
214
+
215
+ -- Off `anon`, which holds no privilege on any table, and onto the role whose
216
+ -- grants row level security is written against. `auth.uid()` stays null.
217
+ perform set_config('role', 'authenticated', true);
218
+ end;
219
+ $$;
220
+
221
+ comment on function ekwo_pre_request() is
222
+ 'Called by PostgREST at the start of every request (pgrst.db_pre_request). Presents the key in X-Ekwo-Api-Key when there is one, then moves the request off `anon` so that row level security decides instead of the grants. No header, no effect.';
223
+
224
+ -- ---------------------------------------------------------------------------
225
+ -- Telling PostgREST to call it
226
+ --
227
+ -- The setting lives on the `authenticator` role, which is the login PostgREST
228
+ -- connects with. It is configuration of the API and not of the schema, so a
229
+ -- database that has no such role — PGlite under the tests, a plain Postgres
230
+ -- behind the command line — skips it and loses nothing.
231
+ --
232
+ -- Where the role exists and the migration is not allowed to write its
233
+ -- settings, the schema is still correct and the header is simply not read yet.
234
+ -- That is a state somebody has to be told about rather than left to discover,
235
+ -- so it is a notice here and a check in `ekwo doctor`, which names the exact
236
+ -- statement to run.
237
+ -- ---------------------------------------------------------------------------
238
+
239
+ do $$
240
+ begin
241
+ if not exists (select 1 from pg_roles where rolname = 'authenticator') then
242
+ return;
243
+ end if;
244
+ begin
245
+ execute 'alter role authenticator set pgrst.db_pre_request = ''public.ekwo_pre_request''';
246
+ notify pgrst, 'reload config';
247
+ exception when insufficient_privilege then
248
+ raise notice 'ekwo: could not set pgrst.db_pre_request on the authenticator role. Machine keys will not be read from X-Ekwo-Api-Key until somebody with the privilege runs: alter role authenticator set pgrst.db_pre_request = ''public.ekwo_pre_request''; notify pgrst, ''reload config''; — `ekwo doctor` reports this.';
249
+ end;
250
+ end
251
+ $$;
252
+
253
+ revoke execute on all functions in schema public from public;
254
+
255
+ -- `anon` gains two functions, and the decision record says why. `api_key_company()`
256
+ -- is read by `is_company_member()`, which `anon` already executes on behalf of
257
+ -- a policy; `present_api_key()` and `ekwo_pre_request()` are the door itself,
258
+ -- and both answer `void`.
259
+ revoke execute on function api_key_company() from public;
260
+ revoke execute on function present_api_key(text) from public;
261
+ revoke execute on function ekwo_pre_request() from public;
262
+
263
+ grant execute on function api_key_company() to anon, authenticated, service_role;
264
+ grant execute on function present_api_key(text) to anon, authenticated, service_role;
265
+ grant execute on function ekwo_pre_request() to anon, authenticated, service_role;
@@ -0,0 +1,42 @@
1
+ -- Ekwo OS — the version answers a key too, and a stranger not at all.
2
+ --
3
+ -- `installed_schema_version()` was granted to `authenticated` alone, because
4
+ -- the client that asks it asks after signing in. A machine key now reaches the
5
+ -- API (`20260922160000`), and it asks the same question first and for the same
6
+ -- reason: a backup that does not know whether the instance is ahead of the
7
+ -- code reading it writes an archive nobody can check.
8
+ --
9
+ -- Two ways to give it that answer. Granting `anon` the function as it stands
10
+ -- would hand an unauthenticated scanner the exact release an installation
11
+ -- runs, which is what the first migration declined to do. Granting it and
12
+ -- letting the *body* decide gives nothing away: the function returns a row to
13
+ -- a caller this installation knows — a signed-in user, or the holder of a live
14
+ -- key — and no rows at all to anybody else. `anon` may call it; calling it
15
+ -- anonymously is a way of learning nothing.
16
+ --
17
+ -- The grant is the one exception worth the sentence: a key presented through
18
+ -- the pre-request arrives as `authenticated` and does not need it. It is the
19
+ -- call made *before* the key is presented — the screen that checks a pasted
20
+ -- key against an instance — that arrives as `anon`, and the shape of the
21
+ -- answer is the same for both: zero rows unless somebody is there.
22
+
23
+ create or replace function installed_schema_version()
24
+ returns table (schema_version text, edition text)
25
+ language sql
26
+ stable
27
+ security definer
28
+ set search_path = public, pg_temp
29
+ as $$
30
+ select i.schema_version, i.edition::text
31
+ from instance i
32
+ where i.id = 1
33
+ and (auth.uid() is not null or api_key_company() is not null);
34
+ $$;
35
+
36
+ comment on function installed_schema_version() is
37
+ 'The schema version this installation runs, and its edition, for a caller it knows: a signed-in account that is on no company yet, or the holder of a machine key. No rows for anybody else, so an anonymous call learns nothing. Nothing else of the instance row comes with it.';
38
+
39
+ revoke execute on all functions in schema public from public;
40
+
41
+ revoke execute on function installed_schema_version() from public;
42
+ grant execute on function installed_schema_version() to anon, authenticated, service_role;
@@ -0,0 +1,127 @@
1
+ -- Ekwo OS — reference data is for a caller this installation knows.
2
+ --
3
+ -- Eighteen tables of the socle carry the reference data every reading depends
4
+ -- on: the currencies and their rates, the charts of accounts and their
5
+ -- templates, the taxes and where they post, the boxes of the declaration
6
+ -- forms, the financial statement schemes, the legal mentions, the territories,
7
+ -- the modules, and the list of capabilities itself. None of them belongs to a
8
+ -- company. Their policy has always been the same sentence — `auth.uid() is not
9
+ -- null` — which reads "somebody signed in", and was the right sentence while
10
+ -- the only caller this installation had was a person.
11
+ --
12
+ -- `20260922160000` gave it a second kind of caller. A machine key now reaches
13
+ -- the API, arrives as `authenticated` and is on its own company, and every
14
+ -- policy that asks a capability follows. These eighteen ask none: they ask for
15
+ -- a session, and a key is not a session, so they answered no — and a caller
16
+ -- that cannot read `modules` cannot even be told which tables an archive
17
+ -- carries, which is how `export_company()` was still refusing a key that held
18
+ -- `company.export`. The refusal was not about the company's data at all. It
19
+ -- was about a list of module codes.
20
+ --
21
+ -- So the sentence is replaced by what it was always reaching for, in one
22
+ -- helper that every one of them now calls:
23
+ --
24
+ -- is_known_caller() — a signed-in user, or the holder of a live key.
25
+ --
26
+ -- Nothing else changes. These tables hold no customer data: a chart of
27
+ -- accounts and a currency are the same rows on every installation, which is
28
+ -- why the seed writes them and nobody may. What widens is who may read what
29
+ -- the installation already publishes to every person signed into it, and the
30
+ -- set of callers grows by exactly one: somebody holding a key this
31
+ -- installation minted, which is a stronger claim than having signed up.
32
+ --
33
+ -- `anon` is untouched and still reads none of them: `is_known_caller()`
34
+ -- answers false without a session and without a key, which is the same word
35
+ -- `auth.uid() is not null` gave it.
36
+ --
37
+ -- The two reference tables of the assets module are the same question in a
38
+ -- module's schema, and are moved in the module's own migration, where they
39
+ -- belong.
40
+
41
+ create or replace function is_known_caller()
42
+ returns boolean
43
+ language sql
44
+ stable
45
+ as $$
46
+ select auth.uid() is not null or api_key_company() is not null;
47
+ $$;
48
+
49
+ comment on function is_known_caller() is
50
+ 'Whether this installation knows who is asking: a signed-in user, or the holder of a live machine key. What the policies of the reference tables ask, in place of the `auth.uid() is not null` they asked while a person was the only caller.';
51
+
52
+ drop policy account_templates_select on account_templates;
53
+ create policy account_templates_select on account_templates
54
+ for select using (is_known_caller());
55
+
56
+ drop policy capabilities_select on capabilities;
57
+ create policy capabilities_select on capabilities
58
+ for select using (is_known_caller());
59
+
60
+ drop policy chart_templates_select on chart_templates;
61
+ create policy chart_templates_select on chart_templates
62
+ for select using (is_known_caller());
63
+
64
+ drop policy country_defaults_select on country_defaults;
65
+ create policy country_defaults_select on country_defaults
66
+ for select using (is_known_caller());
67
+
68
+ drop policy currencies_select on currencies;
69
+ create policy currencies_select on currencies
70
+ for select using (is_known_caller());
71
+
72
+ drop policy currency_rates_select on currency_rates;
73
+ create policy currency_rates_select on currency_rates
74
+ for select using (is_known_caller());
75
+
76
+ drop policy journal_templates_select on journal_templates;
77
+ create policy journal_templates_select on journal_templates
78
+ for select using (is_known_caller());
79
+
80
+ drop policy legal_mention_templates_select on legal_mention_templates;
81
+ create policy legal_mention_templates_select on legal_mention_templates
82
+ for select using (is_known_caller());
83
+
84
+ drop policy modules_select on modules;
85
+ create policy modules_select on modules
86
+ for select using (is_known_caller());
87
+
88
+ drop policy role_capabilities_select on role_capabilities;
89
+ create policy role_capabilities_select on role_capabilities
90
+ for select using (is_known_caller());
91
+
92
+ drop policy statement_line_rules_select on statement_line_rules;
93
+ create policy statement_line_rules_select on statement_line_rules
94
+ for select using (is_known_caller());
95
+
96
+ drop policy statement_line_templates_select on statement_line_templates;
97
+ create policy statement_line_templates_select on statement_line_templates
98
+ for select using (is_known_caller());
99
+
100
+ drop policy statement_templates_select on statement_templates;
101
+ create policy statement_templates_select on statement_templates
102
+ for select using (is_known_caller());
103
+
104
+ drop policy tax_posting_templates_select on tax_posting_templates;
105
+ create policy tax_posting_templates_select on tax_posting_templates
106
+ for select using (is_known_caller());
107
+
108
+ drop policy tax_report_box_templates_select on tax_report_box_templates;
109
+ create policy tax_report_box_templates_select on tax_report_box_templates
110
+ for select using (is_known_caller());
111
+
112
+ drop policy tax_report_templates_select on tax_report_templates;
113
+ create policy tax_report_templates_select on tax_report_templates
114
+ for select using (is_known_caller());
115
+
116
+ drop policy tax_templates_select on tax_templates;
117
+ create policy tax_templates_select on tax_templates
118
+ for select using (is_known_caller());
119
+
120
+ drop policy territories_select on territories;
121
+ create policy territories_select on territories
122
+ for select using (is_known_caller());
123
+
124
+ revoke execute on all functions in schema public from public;
125
+
126
+ revoke execute on function is_known_caller() from public;
127
+ grant execute on function is_known_caller() to anon, authenticated, service_role;
@@ -0,0 +1,82 @@
1
+ -- Ekwo OS — presenting a key must not need a transaction that can write.
2
+ --
3
+ -- Found against a real project, which is the only place it could be found:
4
+ -- PostgREST runs a GET, and an RPC whose function is not volatile, inside a
5
+ -- **read-only transaction**. `use_api_key()` ends by stamping
6
+ -- `api_keys.last_used_at` through `touch_api_key()`, and an UPDATE there is
7
+ -- refused:
8
+ --
9
+ -- cannot execute UPDATE in a read-only transaction (SQLSTATE 25006)
10
+ --
11
+ -- The refusal happens inside `ekwo_pre_request()`, so it fails the whole
12
+ -- request — with a 405 and a message about an UPDATE nobody asked for, on a
13
+ -- plain `GET /rest/v1/companies`. Every read a machine key makes is that
14
+ -- request, which is most of what a machine key is for: a backup, a report, a
15
+ -- dashboard. PGlite never said so, because a test opens the transaction it
16
+ -- wants and none of ours asked for a read-only one.
17
+ --
18
+ -- **The stamp is worth less than the request.** `last_used_at` answers "has
19
+ -- this key been used lately", which is what an operator withdraws keys on. It
20
+ -- is a convenience, and the work the key was presented for is not. So the use
21
+ -- is recorded when the transaction can record it, and skipped when it cannot,
22
+ -- rather than the other way round.
23
+ --
24
+ -- This is not an error being swallowed. Nothing is caught here and nothing
25
+ -- raises: the function asks whether the transaction it is in may write, and
26
+ -- the answer is a fact about the transaction, known before anything is
27
+ -- attempted. A key presented on a connection — the command line, the MCP
28
+ -- server, `ekwo` itself — opens a read-write transaction and is stamped
29
+ -- exactly as before. A key presented on a read request is not, and
30
+ -- `last_used_at` then means "last used in a way that could write it down",
31
+ -- which the column comment now says.
32
+ --
33
+ -- The alternative was an autonomous transaction, which in PostgreSQL means an
34
+ -- extension — dblink or pg_background — and this schema requires none and is
35
+ -- not going to start over a timestamp.
36
+
37
+ create or replace function use_api_key(p_secret text)
38
+ returns api_keys
39
+ language plpgsql
40
+ security definer
41
+ set search_path = public, pg_temp
42
+ as $$
43
+ declare
44
+ v_row api_keys%rowtype;
45
+ begin
46
+ select * into v_row
47
+ from api_keys
48
+ where key_hash = encode(sha256(convert_to(coalesce(p_secret, ''), 'UTF8')), 'hex');
49
+
50
+ if v_row.id is null then
51
+ raise exception 'unknown_api_key: that is not a key of this installation'
52
+ using errcode = '42501';
53
+ end if;
54
+ if v_row.revoked_at is not null then
55
+ raise exception 'api_key_revoked: that key was withdrawn on %', v_row.revoked_at
56
+ using errcode = '42501';
57
+ end if;
58
+ if v_row.expires_at is not null and v_row.expires_at <= now() then
59
+ raise exception 'api_key_expired: that key expired on %', v_row.expires_at
60
+ using errcode = '42501';
61
+ end if;
62
+
63
+ -- Transaction-local: the key is presented for the work at hand and not for
64
+ -- the life of a connection somebody else may inherit from a pool.
65
+ perform set_config('ekwo.api_key', v_row.key_hash, true);
66
+
67
+ -- Recorded where it can be. A read request runs in a read-only transaction
68
+ -- and an UPDATE in one is refused, which would fail the request the key was
69
+ -- presented for.
70
+ if not current_setting('transaction_read_only')::boolean then
71
+ perform touch_api_key(v_row.id);
72
+ end if;
73
+
74
+ return v_row;
75
+ end;
76
+ $$;
77
+
78
+ comment on function use_api_key(text) is
79
+ 'Presents a machine key for the current transaction: has_capability() answers for it until the transaction ends. Refuses a key that is unknown, withdrawn or expired. Records the use when the transaction may write — a read request runs in a read-only transaction, where the stamp is worth less than the request.';
80
+
81
+ comment on column api_keys.last_used_at is
82
+ 'When this key was last presented in a transaction that could write it down. A read over the API runs read-only and is not stamped, so this is a floor and never a ceiling: a key with an old date may still be in daily use for reading.';
@@ -0,0 +1,42 @@
1
+ -- Ekwo OS — the schema this release defines is 0.8.0.
2
+ --
3
+ -- `ekwo_schema_version()` is the one place the number lives, and a migration
4
+ -- is the only thing that moves it. A database then answers for the files it
5
+ -- has actually run rather than for what somebody believed it had run: the
6
+ -- column default and `init_instance()` both call this function, so a fresh
7
+ -- install records the new number without a second edit, and `ekwo migrate`
8
+ -- reads it back onto `instance.schema_version` for a database that was
9
+ -- installed at an older one.
10
+ --
11
+ -- Between 0.7.0 and here the schema gained the way a key reaches the API.
12
+ -- `ekwo_pre_request()` is called by PostgREST at the start of every request,
13
+ -- named in `pgrst.db_pre_request`, and presents the secret sent in
14
+ -- `X-Ekwo-Api-Key` before the request leaves `anon`; `present_api_key()` does
15
+ -- the presenting, `is_known_caller()` answers for a signed-in user or the
16
+ -- holder of a live key, and `is_company_member()` says true for the company a
17
+ -- key was minted on. It also gained `installed_schema_version()`, which
18
+ -- answers a caller this installation knows and nobody else, and
19
+ -- `default_statement_code()` with `financial_statement_of_kind()`, which give
20
+ -- a balance sheet to a client that does not know the country pack. A 0.7.0
21
+ -- database has none of them, and the packages of this release call them: the
22
+ -- floor of `ekwo-os`, `@ekwo-ai/core` and `@ekwo-ai/mcp` rises to 0.8.0 and
23
+ -- refuses an older database by name, as every release has, rather than
24
+ -- answering for one they were never run against.
25
+
26
+ create or replace function ekwo_schema_version()
27
+ returns text
28
+ language sql
29
+ immutable
30
+ as $$
31
+ select '0.8.0'::text;
32
+ $$;
33
+
34
+ comment on function ekwo_schema_version() is
35
+ 'Schema version of the installed release. Bumped by a migration, never by hand.';
36
+
37
+ -- `create or replace` keeps the privileges the function already had, and the
38
+ -- revoke is what every migration of this directory ends with: a function that
39
+ -- comes out with the built-in default is published by Supabase as an
40
+ -- anonymous RPC endpoint.
41
+ revoke execute on all functions in schema public from public;
42
+ grant execute on function ekwo_schema_version() to authenticated, service_role;
@@ -0,0 +1,24 @@
1
+ -- Ekwo module `assets` — its reference tables answer the same caller the
2
+ -- socle's do.
3
+ --
4
+ -- `assets.country_rules` and `assets.category_templates` are reference data of
5
+ -- the module: the depreciation rules a country allows, and the categories a
6
+ -- register starts from. Their policy is the sentence the socle used everywhere
7
+ -- before `20260922162500` — `auth.uid() is not null`, which reads "somebody
8
+ -- signed in" — and the socle has since replaced it with what it was reaching
9
+ -- for: `is_known_caller()`, a signed-in user or the holder of a live machine
10
+ -- key.
11
+ --
12
+ -- A module does not get to answer that question differently. A key that may
13
+ -- read the register and cannot read the rules the register is built on reads
14
+ -- half a module, and the half it is missing is the half that explains the
15
+ -- other. The company's own rows are untouched: they keep asking
16
+ -- `has_capability()` and `module_enabled()`, as they did.
17
+
18
+ drop policy assets_country_rules_select on assets.country_rules;
19
+ create policy assets_country_rules_select on assets.country_rules
20
+ for select using (public.is_known_caller());
21
+
22
+ drop policy assets_category_templates_select on assets.category_templates;
23
+ create policy assets_category_templates_select on assets.category_templates
24
+ for select using (public.is_known_caller());
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,SAAS,EAAE,EACvB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA0BvB"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpD,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,wBAAsB,MAAM,CAC1B,EAAE,EAAE,SAAS,EACb,UAAU,EAAE,SAAS,EAAE,EACvB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA2BvB"}
package/dist/doctor.js CHANGED
@@ -26,6 +26,7 @@ export async function doctor(db, migrations, options = {}) {
26
26
  checks.push(await checkRowLevelSecurity(db));
27
27
  checks.push(await checkPolicies(db));
28
28
  checks.push(await checkGrants(db, expected));
29
+ checks.push(await checkPreRequest(db));
29
30
  checks.push(await checkOrphanMembers(db));
30
31
  checks.push(await checkOrphanAdmins(db));
31
32
  checks.push(await checkBankAccounts(db));
@@ -342,6 +343,45 @@ async function checkBankAccounts(db) {
342
343
  ],
343
344
  };
344
345
  }
346
+ /**
347
+ * Whether PostgREST has been told to call `ekwo_pre_request()`.
348
+ *
349
+ * A machine key travels in `X-Ekwo-Api-Key` and is read by that function at
350
+ * the start of a request's transaction — which only happens if
351
+ * `pgrst.db_pre_request` names it on the `authenticator` role. The migration
352
+ * sets it where it is allowed to, and a managed project may not allow it, so
353
+ * this is the check that says so out loud instead of leaving somebody to
354
+ * discover that their key does nothing.
355
+ *
356
+ * A database with no `authenticator` role has no PostgREST in front of it —
357
+ * the command line's own connection, a plain Postgres — and there is nothing
358
+ * to report.
359
+ */
360
+ async function checkPreRequest(db) {
361
+ const name = 'api keys over the API';
362
+ const found = await db.query(`select (select s from unnest(coalesce(r.rolconfig, '{}')) as s
363
+ where s like 'pgrst.db_pre_request=%') as setting
364
+ from pg_roles r where r.rolname = 'authenticator'`);
365
+ if (found.length === 0) {
366
+ return { name, severity: 'ok', summary: 'no PostgREST in front of this database' };
367
+ }
368
+ const setting = found[0]?.setting ?? null;
369
+ if (setting !== null && setting.endsWith('ekwo_pre_request')) {
370
+ return { name, severity: 'ok', summary: 'PostgREST calls ekwo_pre_request()' };
371
+ }
372
+ return {
373
+ name,
374
+ severity: 'warning',
375
+ summary: setting === null
376
+ ? 'PostgREST calls no pre-request, so a machine key in X-Ekwo-Api-Key is ignored'
377
+ : `PostgREST calls another pre-request (${setting}), so a machine key in X-Ekwo-Api-Key is ignored`,
378
+ details: [
379
+ "alter role authenticator set pgrst.db_pre_request = 'public.ekwo_pre_request';",
380
+ "notify pgrst, 'reload config';",
381
+ 'Run both as a role that may write the settings of `authenticator`. Nothing else is affected: keys presented on a direct connection with use_api_key() work either way.',
382
+ ],
383
+ };
384
+ }
345
385
  async function checkStatements(db) {
346
386
  const rows = await db.query(`select c.name as company, s.statement_date::text,
347
387
  s.balance_end_declared::text as declared,
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -11,5 +11,5 @@
11
11
  * every package of this repository; `tests/cli/package.test.ts` keeps the two
12
12
  * equal.
13
13
  */
14
- export declare const SCHEMA_MIN = "0.7.0";
14
+ export declare const SCHEMA_MIN = "0.8.0";
15
15
  //# sourceMappingURL=schema.d.ts.map
package/dist/schema.js CHANGED
@@ -11,5 +11,5 @@
11
11
  * every package of this repository; `tests/cli/package.test.ts` keeps the two
12
12
  * equal.
13
13
  */
14
- export const SCHEMA_MIN = '0.7.0';
14
+ export const SCHEMA_MIN = '0.8.0';
15
15
  //# sourceMappingURL=schema.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ekwo-os",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Install and operate Ekwo OS on your own Supabase project: npx ekwo-os init.",
5
5
  "keywords": ["accounting", "supabase", "postgres", "migrations", "cli", "bookkeeping", "ekwo"],
6
6
  "license": "AGPL-3.0-only",
@@ -10,7 +10,7 @@
10
10
  "bugs": { "url": "https://github.com/Ekwo-ai/ekwo-os/issues" },
11
11
  "type": "module",
12
12
  "engines": { "node": ">=20" },
13
- "ekwo": { "schemaMin": "0.7.0" },
13
+ "ekwo": { "schemaMin": "0.8.0" },
14
14
  "files": ["dist", "schema", "README.md"],
15
15
  "bin": { "ekwo": "./dist/bin.js" },
16
16
  "main": "./dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "typecheck": "tsc -p tsconfig.json --noEmit"
24
24
  },
25
25
  "dependencies": {
26
- "@ekwo-ai/core": "^0.7.0",
26
+ "@ekwo-ai/core": "^0.8.0",
27
27
  "postgres": "^3.4.5"
28
28
  }
29
29
  }