pipework 0.58.0 → 0.60.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 (61) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/REFERENCE.md +18 -8
  3. package/dist/audit/chain/install.d.ts +4 -2
  4. package/dist/audit/chain/install.d.ts.map +1 -1
  5. package/dist/audit/chain/install.js +5 -3
  6. package/dist/audit/chain/install.js.map +1 -1
  7. package/dist/audit/query/query.d.ts +4 -0
  8. package/dist/audit/query/query.d.ts.map +1 -1
  9. package/dist/audit/query/query.js +6 -0
  10. package/dist/audit/query/query.js.map +1 -1
  11. package/dist/audit/schema/audit-record-fields.d.ts +5 -0
  12. package/dist/audit/schema/audit-record-fields.d.ts.map +1 -1
  13. package/dist/audit/schema/audit-record-fields.js +5 -0
  14. package/dist/audit/schema/audit-record-fields.js.map +1 -1
  15. package/dist/audit/schema/audit-record.d.ts +8 -0
  16. package/dist/audit/schema/audit-record.d.ts.map +1 -1
  17. package/dist/audit/schema/audit-record.js +7 -0
  18. package/dist/audit/schema/audit-record.js.map +1 -1
  19. package/dist/data/migrate/provision-framework.d.ts.map +1 -1
  20. package/dist/data/migrate/provision-framework.js +3 -0
  21. package/dist/data/migrate/provision-framework.js.map +1 -1
  22. package/dist/data/write/audit/audit-columns.d.ts +1 -1
  23. package/dist/data/write/audit/audit-columns.d.ts.map +1 -1
  24. package/dist/data/write/audit/audit-select.d.ts +9 -2
  25. package/dist/data/write/audit/audit-select.d.ts.map +1 -1
  26. package/dist/data/write/audit/audit-select.js +22 -2
  27. package/dist/data/write/audit/audit-select.js.map +1 -1
  28. package/dist/data/write/audit/primary-key.d.ts +8 -0
  29. package/dist/data/write/audit/primary-key.d.ts.map +1 -1
  30. package/dist/data/write/audit/primary-key.js +32 -7
  31. package/dist/data/write/audit/primary-key.js.map +1 -1
  32. package/dist/data/write/audit/verb-delete.d.ts.map +1 -1
  33. package/dist/data/write/audit/verb-delete.js +2 -2
  34. package/dist/data/write/audit/verb-delete.js.map +1 -1
  35. package/dist/data/write/audit/verb-insert.d.ts.map +1 -1
  36. package/dist/data/write/audit/verb-insert.js +2 -2
  37. package/dist/data/write/audit/verb-insert.js.map +1 -1
  38. package/dist/data/write/audit/verb-update.d.ts.map +1 -1
  39. package/dist/data/write/audit/verb-update.js +2 -2
  40. package/dist/data/write/audit/verb-update.js.map +1 -1
  41. package/dist/data/write/audit/verb-upsert.d.ts.map +1 -1
  42. package/dist/data/write/audit/verb-upsert.js +2 -2
  43. package/dist/data/write/audit/verb-upsert.js.map +1 -1
  44. package/dist/infra/access-links/handle.d.ts +47 -1
  45. package/dist/infra/access-links/handle.d.ts.map +1 -1
  46. package/dist/infra/access-links/handle.js +84 -13
  47. package/dist/infra/access-links/handle.js.map +1 -1
  48. package/dist/infra/access-links/schema.d.ts +10 -0
  49. package/dist/infra/access-links/schema.d.ts.map +1 -1
  50. package/dist/infra/access-links/schema.js +2 -0
  51. package/dist/infra/access-links/schema.js.map +1 -1
  52. package/dist/infra/audit/namespace.d.ts +6 -1
  53. package/dist/infra/audit/namespace.d.ts.map +1 -1
  54. package/dist/infra/audit/namespace.js +6 -1
  55. package/dist/infra/audit/namespace.js.map +1 -1
  56. package/dist/request/http/server.d.ts.map +1 -1
  57. package/dist/request/http/server.js +33 -1
  58. package/dist/request/http/server.js.map +1 -1
  59. package/dist/request/http/types.d.ts +9 -0
  60. package/dist/request/http/types.d.ts.map +1 -1
  61. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.60.0
4
+
5
+ **Schema note: `audit_record` gains a nullable `entity_id` column and an index on `(tenant_id, entity, entity_id, occurred_at, id)`. `pipework migrate` adds both.**
6
+
7
+ - **New: `audit.query({ entity, entityId })` returns one row's history.** Every audited write records the written row's primary key in `entity_id` (the value as text; a composite key as a JSON array of the values in key order, built with `audit.entityId(table, row)`), and the filter is served by an index instead of scanning the tenant's records for that entity. `entityId` requires `entity`. Access-link audit rows carry the link id. Rows written earlier have no `entity_id` and are not found by it. With `audit.chain` on, new rows' hashes cover `entity_id`; existing rows hash as before and `audit.verify` stays ok across both.
8
+
9
+ ## 0.59.0
10
+
11
+ **Schema note: `access_link_use` gains two nullable columns, `detail` (jsonb) and `claimed_at` (timestamptz). Run `pipework generate` and `pipework migrate` in apps that use access links.**
12
+
13
+ - **New: a use counts only when the response was sent.** `.response()` handlers get `response.onFinish(cb)`, called once with `'sent'` or `'aborted'` (the client went away). `accessLinks.completeUseWhenSent(response, useToken)` completes the use on `'sent'` and abandons it on `'aborted'`; it runs after the request's transaction ended, so it writes no `AccessLink:use` audit row.
14
+ - **New: `link` and `revoke` are audited.** Under a traced step they write `AccessLink:create` / `AccessLink:revoke` audit records with the actor, in the same transaction, without the code or `checkData`.
15
+ - **New: a use carries app-defined detail.** `beginUse(..., { detail })` and `completeUse(token, { detail })` (shallow merge); `uses()` returns it and the use audit row includes it.
16
+ - **New: `accessLinks.claimUse(useToken)` takes a pending use once across processes.** For single-use issued URLs: `beginUse` at issue, `claimUse` at follow (exactly one of any number of concurrent calls gets `ok`), then serve and `completeUseWhenSent`. Claiming does not count the use.
17
+
3
18
  ## 0.58.0
4
19
 
5
20
  - **New: `accessLinks.asLink(grant, fn)` runs a block as the tenant an access link resolved to.** In a `.public()` route, pass the active `resolve` result or the ok `beginUse` result and `fn` runs with `pipe()` scoped to the link's tenant, on the app role under row-level security, in the request's trace, with `auth = { userId: 'link:<linkId>', accessLinkId }` so audit records name the link as the actor. The block has its own transaction on the default database: it commits when `fn` resolves and rolls back when it throws. Only a result this handle returned is accepted; a hand-built or refused one throws, so a tenant can be earned but not chosen. Outside the block the Flow still has no tenant. Apps that bound a savepoint to the tenant by hand through `Flow.transactions` can drop that code.
package/REFERENCE.md CHANGED
@@ -1035,7 +1035,13 @@ Creates an Audit instance that writes audit records to a Postgres table.
1035
1035
 
1036
1036
  ### `audit.query`
1037
1037
 
1038
- Queries the framework-managed `audit_record` table. Tenant-scoped via the active Flow.
1038
+ Queries the framework-managed `audit_record` table. Tenant-scoped via the active Flow. `{ entity, entityId }` returns one row's history from an index; build a composite key's `entityId` with `audit.entityId`.
1039
+
1040
+ Every audited write records the written row's primary key in `audit_record.entity_id`: the key's value as text, or for a composite key a compact JSON array of the key values as strings in key order. `entityId` filters on it, requires `entity` (else a `ValidationError`), and is served by the index `audit_record_entity_idx` on `(tenant_id, entity, entity_id, occurred_at, id)`. Delete rows carry the deleted row's key. Access-link rows (`AccessLink` create, revoke, use) carry the link id. Read-touches rows, and rows written before the column existed, have no `entity_id` and never match.
1041
+
1042
+ ### `audit.entityId`
1043
+
1044
+ The `entityId` filter text for a written table's row: `audit.entityId(table, row)`. The primary key's value as text, or for a composite key a compact JSON array of the key values as strings in key order (`["a","7"]`). Dates become ISO strings.
1039
1045
 
1040
1046
  ### `audit.chainHead`
1041
1047
 
@@ -1051,7 +1057,7 @@ Opt in with blueprint `audit: { chain: true }`. Every `audit_record` row then ca
1051
1057
 
1052
1058
  **Lock cost.** The trigger locks the tenant's chain-head row, so one tenant's audited writes serialize from their first audit row until their transaction commits. Different tenants do not block each other. Keep transactions that write audit rows short.
1053
1059
 
1054
- **Canonical form.** A row's hash is `sha256(prev_hash_bytes || utf8(canonical))`, where `prev_hash_bytes` is the previous row's hash (32 zero bytes for `chain_seq` 1) and `canonical` is the text of this JSON array, as Postgres renders it: `[chain_seq, id, trace_day (YYYY-MM-DD), trace_id, trace_step_id, tenant_id, actor_id, entity, verb, before, after, captured_inputs, occurred_at]`. `occurred_at` is UTC, formatted `YYYY-MM-DDTHH:MI:SS.UUUUUUZ` (microseconds), so the result does not depend on the session TimeZone. `before`, `after` and `captured_inputs` are jsonb (keys in jsonb order). The SQL function `pipework_audit_record_canonical(audit_record)` returns this text.
1060
+ **Canonical form.** A row's hash is `sha256(prev_hash_bytes || utf8(canonical))`, where `prev_hash_bytes` is the previous row's hash (32 zero bytes for `chain_seq` 1) and `canonical` is the text of this JSON array, as Postgres renders it: `[chain_seq, id, trace_day (YYYY-MM-DD), trace_id, trace_step_id, tenant_id, actor_id, entity, verb, before, after, captured_inputs, occurred_at]`, followed by `entity_id` only when it is not null (so a row without one, such as a row written before the column existed, hashes as it always did). `occurred_at` is UTC, formatted `YYYY-MM-DDTHH:MI:SS.UUUUUUZ` (microseconds), so the result does not depend on the session TimeZone. `before`, `after` and `captured_inputs` are jsonb (keys in jsonb order). The SQL function `pipework_audit_record_canonical(audit_record)` returns this text.
1055
1061
 
1056
1062
  **`audit.verify(options?)`** checks every seq in the range. Options: `fromSeq`, `toSeq`, `from` / `to` (`occurredAt >= from`, `< to`), and `anchor: { seq, hash }` (a pair saved from `audit.chainHead()`). Without an upper bound, the range ends at the chain head and its last record must equal the head, which catches deletion of the newest rows (reported as `missing`); rows committed after the head was read are not checked. Breaks, always the first in seq order: `hash-mismatch` (row edited), `prev-mismatch` (link to the previous row broken), `missing` (a seq has no row), `head-mismatch` (the chain head disagrees with the newest record, or is gone while records remain), `anchor-mismatch`. A tenant-scoped Flow verifies that tenant's chain; outside one, the chain of tenant-less rows. `seq` values are returned as numbers.
1057
1063
 
@@ -1095,7 +1101,7 @@ Opt in with blueprint `accessLinks: {}`. The generated migration then includes t
1095
1101
 
1096
1102
  - `access_link_code` — `code` (primary key), `tenant_id`, `link_id`, `created_at`. Not tenant-scoped, no row-level security: it maps a code to its tenant so a request that has no tenant can find where to look, and holds nothing else.
1097
1103
  - `access_link` — one row per link (target, permissions, note, expiry, `max_uses`, `require_checks`, `check_data`, revoked). Tenant-scoped; forced RLS.
1098
- - `access_link_use` — one row per use (kind, status `pending | completed | abandoned`, `reserved_until`, client, location). Tenant-scoped; forced RLS.
1104
+ - `access_link_use` — one row per use (kind, status `pending | completed | abandoned`, `reserved_until`, client, location, `detail` jsonb, `claimed_at`). Tenant-scoped; forced RLS.
1099
1105
 
1100
1106
  ### `accessLinks.create`
1101
1107
 
@@ -1112,18 +1118,22 @@ Creates a handle. The config:
1112
1118
 
1113
1119
  Tenant methods need a tenant in the Flow and run on the request transaction, under RLS like any other tenant write. `createdBy` and `revokedBy` come from `auth.userId` when present.
1114
1120
 
1115
- - `link({ target: { type, id }, permissions, note?, expiresAt?, maxUses?, requireChecks?, checkData? })` — creates a link and returns it with its `code`. Permissions must be a non-empty subset of the configured set and every `requireChecks` name must exist in `checks`, else a `ValidationError`. The code and its tenant mapping are inserted in one transaction; a code collision retries with a new code up to 5 times, then throws an error naming the code length and alphabet size.
1121
+ - `link({ target: { type, id }, permissions, note?, expiresAt?, maxUses?, requireChecks?, checkData? })` — creates a link and returns it with its `code`. Permissions must be a non-empty subset of the configured set and every `requireChecks` name must exist in `checks`, else a `ValidationError`. The code and its tenant mapping are inserted in one transaction; a code collision retries with a new code up to 5 times, then throws an error naming the code length and alphabet size. When the Flow has a trace with an open step, an `audit_record` (`AccessLink`, verb `create`, actor from the Flow, the link without its code or `checkData`) is written in the same transaction.
1116
1122
  - `get(id)` — the link or `null`.
1117
1123
  - `list({ target? })` — the tenant's links, newest first, optionally for one target.
1118
- - `revoke(id)` — sets `revokedAt` / `revokedBy`; revoking a revoked link returns it unchanged; `null` when not found.
1119
- - `uses(linkId)` — the use log: kind, status, start and completion time, client, location.
1124
+ - `revoke(id)` — sets `revokedAt` / `revokedBy`; revoking a revoked link returns it unchanged; `null` when not found. The call that revokes writes an `AccessLink:revoke` audit record under the same rule as `link`.
1125
+ - `uses(linkId)` — the use log: kind, status, start and completion time, client, location, `detail`, `claimedAt`.
1120
1126
 
1121
1127
  Public methods work with no tenant and no auth in the Flow, so they can run in a `.public()` route. Each finds the tenant through `access_link_code`, then runs its statements in one transaction on the owner connection with `pipework.tenant_id` set to that tenant for that transaction only — the same `set_config` the request path uses. The row-level-security policies on `access_link` and `access_link_use` apply as normal; there is no bypass role and no policy exception.
1122
1128
 
1123
1129
  - `resolve(code, { client? })` — returns `{ status: 'active', link, tenantId }` or `{ status: 'not-found' | 'revoked' | 'expired' | 'exhausted' | 'rate-limited' }`. `link` is a `PublicLink` (id, target, permissions, note, expiresAt, maxUses, usesCompleted, requireChecks), never `checkData` or the code. Resolving never counts a use, because mail scanners open every link. `exhausted` here means completed uses have reached `maxUses`. A not-found logs a structured warn event `access link resolve miss` with the client and calls `onMiss`.
1124
- - `beginUse(code, { kind, client?, location?, proof? })` — phase one of a use. Returns `{ ok: true, useToken, link }` or `{ ok: false, reason: 'not-found' | 'revoked' | 'expired' | 'exhausted' | 'kind-not-permitted' | 'check-failed' | 'rate-limited' }`. Each `requireChecks` verifier runs with `proof?.[name]`; a `false` or a throw is `check-failed`. Then, in one transaction, the link row is locked (`SELECT ... FOR UPDATE`), the uses that are `completed` or `pending` with an unexpired reservation are counted, and when that count has reached `maxUses` the result is `exhausted`; otherwise a `pending` use is inserted with `reservedUntil = now + reservationMs`. Concurrent begins queue on the lock, so N callers racing for a 1-use link get exactly one `ok`. `useToken` is `<code>.<use id>`. Not-found codes also feed `onMiss`.
1125
- - `completeUse(useToken)` — phase two, called when the app confirms the view or download finished. Marks the use `completed` and returns `{ ok: true }`, or `{ ok: false, reason: 'not-found' | 'expired-reservation' | 'already-settled' }`. Only a pending use with an unexpired reservation completes, and only once. A reservation begun before the link was revoked still completes. A link whose `expiresAt` has passed since the begin returns `expired-reservation`. When the Flow has an open trace step, an `audit_record` (`AccessLink`, verb `use`, with the link, use, kind and target) is written in the same transaction.
1130
+ - `beginUse(code, { kind, client?, location?, proof?, detail? })` — phase one of a use. Returns `{ ok: true, useToken, link }` or `{ ok: false, reason: 'not-found' | 'revoked' | 'expired' | 'exhausted' | 'kind-not-permitted' | 'check-failed' | 'rate-limited' }`. Each `requireChecks` verifier runs with `proof?.[name]`; a `false` or a throw is `check-failed`. Then, in one transaction, the link row is locked (`SELECT ... FOR UPDATE`), the uses that are `completed` or `pending` with an unexpired reservation are counted, and when that count has reached `maxUses` the result is `exhausted`; otherwise a `pending` use is inserted with `reservedUntil = now + reservationMs`. Concurrent begins queue on the lock, so N callers racing for a 1-use link get exactly one `ok`. `useToken` is `<code>.<use id>`. Not-found codes also feed `onMiss`. `detail` (a JSON object) is stored on the use and shown by `uses()`.
1131
+ - `completeUse(useToken, { detail? })` — phase two, called when the app confirms the view or download finished. Marks the use `completed` and returns `{ ok: true }`, or `{ ok: false, reason: 'not-found' | 'expired-reservation' | 'already-settled' }`. Only a pending use with an unexpired reservation completes, and only once. A reservation begun before the link was revoked still completes. A link whose `expiresAt` has passed since the begin returns `expired-reservation`. When the Flow has an open trace step, an `audit_record` (`AccessLink`, verb `use`, with the link, use, kind, target and detail) is written in the same transaction. `detail` is shallow-merged over the detail given to `beginUse` (new keys win).
1126
1132
  - `abandonUse(useToken)` — marks a pending use `abandoned`, freeing its slot at once; `{ ok: true }` or `{ ok: false, reason: 'not-found' | 'already-settled' }`. A use that is neither completed nor abandoned frees itself when its reservation ends.
1133
+ - `completeUseWhenSent(response, useToken, { detail? })` — counts the use when the response has been sent and frees it when the client went away first. Pass the `.response()` dependency and call it before returning a streamed body: it registers `response.onFinish`, which runs `completeUse` on `'sent'` and `abandonUse` on `'aborted'`. The callback runs after the request's transaction has ended, with the Flow captured at the call and no transaction of its own. The trace's step has ended by then, so no `AccessLink:use` audit row is written; the use log row is the record. A failure is logged, never thrown into the response.
1134
+ - `claimUse(useToken)` — takes a pending use exactly once, across processes. One `UPDATE ... SET claimed_at = now() WHERE claimed_at IS NULL AND status = 'pending' AND reserved_until > clock_timestamp()` under the link's tenant, so of any number of concurrent calls for a token exactly one gets `{ ok: true, link }` (a `PublicLink`, accepted by `asLink`); the rest get `{ ok: false, reason: 'already-claimed' }`. Other reasons: `not-found`, `expired-reservation` (reservation or link expiry passed), `already-settled`. Claiming does not count the use; `completeUse` / `abandonUse` still do. Single-use URL pattern: `beginUse` when the URL is issued, hand out a URL carrying `useToken`; on the follow, `claimUse`, serve, `completeUseWhenSent`.
1135
+
1136
+ `HttpResponse.onFinish(cb)` — available on the `.response()` dependency: `cb('sent' | 'aborted')` runs once, `'sent'` on the raw response's `finish`, `'aborted'` when the connection closed first. An error in `cb` is logged, never thrown.
1127
1137
 
1128
1138
  - `asLink(grant, fn)` — runs `fn` as the tenant an access link resolved to, from a `.public()` route. `grant` must be the object `resolve` returned with `status: 'active'` or `beginUse` returned with `ok: true`; a copied, hand-built or refused result throws, so a tenant cannot be named, only earned. Inside `fn` the Flow is a child of the current one — same request id, same trace — with `tenant` set to the link's tenant and `auth = { userId: 'link:<linkId>', accessLinkId }`, so `pipe()` is scoped to the tenant and an audited write records `actor_id = 'link:<linkId>'`. `fn` runs in its own transaction on the default database as the app role (row-level security applies), opened with the tenant's session variables. It commits when `fn` resolves and rolls back when `fn` throws; under the vitest harness it is a savepoint of the test's transaction. Nested `pipe.transaction` calls inside `fn` are savepoints of it. Only the default database is carried into `fn`: other databases bound in the outer Flow are not. Outside `fn` the Flow is unchanged: no tenant, no auth.
1129
1139
 
@@ -9,9 +9,11 @@ export declare const CHAIN_KEY_SQL = "coalesce(tenant_id, '00000000-0000-0000-00
9
9
  * The text hashed for one row. `jsonb_build_array(...)::text` of, in order:
10
10
  * chain_seq, id, trace_day (YYYY-MM-DD), trace_id, trace_step_id, tenant_id,
11
11
  * actor_id, entity, verb, before, after, captured_inputs, occurred_at (UTC,
12
- * `YYYY-MM-DD"T"HH24:MI:SS.US"Z"`). Independent of the session TimeZone.
12
+ * `YYYY-MM-DD"T"HH24:MI:SS.US"Z"`), then entity_id only when it is not null.
13
+ * A row without an entity_id (read-touches, rows written before the column
14
+ * existed) hashes exactly as it always did. Independent of the session TimeZone.
13
15
  */
14
- export declare const CANONICAL_FUNCTION_SQL = "\nCREATE OR REPLACE FUNCTION pipework_audit_record_canonical(r audit_record) RETURNS text\nLANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $$\n SELECT jsonb_build_array(\n r.chain_seq, r.id, to_char(r.trace_day, 'YYYY-MM-DD'), r.trace_id, r.trace_step_id,\n r.tenant_id, r.actor_id, r.entity, r.verb, r.before, r.after, r.captured_inputs,\n to_char(r.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.US\"Z\"')\n )::text\n$$";
16
+ export declare const CANONICAL_FUNCTION_SQL = "\nCREATE OR REPLACE FUNCTION pipework_audit_record_canonical(r audit_record) RETURNS text\nLANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $$\n SELECT (jsonb_build_array(\n r.chain_seq, r.id, to_char(r.trace_day, 'YYYY-MM-DD'), r.trace_id, r.trace_step_id,\n r.tenant_id, r.actor_id, r.entity, r.verb, r.before, r.after, r.captured_inputs,\n to_char(r.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.US\"Z\"')\n ) || CASE WHEN r.entity_id IS NULL THEN '[]'::jsonb ELSE jsonb_build_array(r.entity_id) END)::text\n$$";
15
17
  /**
16
18
  * Install the audit hash chain on `audit_record`. The table must exist.
17
19
  * Idempotent.
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/audit/chain/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAGhD,oEAAoE;AACpE,eAAO,MAAM,qBAAqB,yCAAyC,CAAA;AAE3E,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAC3D,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AAEjE,sFAAsF;AACtF,eAAO,MAAM,aAAa,sEAA0D,CAAA;AAIpF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,wcAQhC,CAAA;AAgCH;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B7D"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/audit/chain/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAA;AAGhD,oEAAoE;AACpE,eAAO,MAAM,qBAAqB,yCAAyC,CAAA;AAE3E,eAAO,MAAM,gBAAgB,8BAA8B,CAAA;AAC3D,eAAO,MAAM,mBAAmB,iCAAiC,CAAA;AAEjE,sFAAsF;AACtF,eAAO,MAAM,aAAa,sEAA0D,CAAA;AAIpF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,oiBAQhC,CAAA;AAgCH;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B7D"}
@@ -20,16 +20,18 @@ const GENESIS_HASH_HEX = '00'.repeat(32);
20
20
  * The text hashed for one row. `jsonb_build_array(...)::text` of, in order:
21
21
  * chain_seq, id, trace_day (YYYY-MM-DD), trace_id, trace_step_id, tenant_id,
22
22
  * actor_id, entity, verb, before, after, captured_inputs, occurred_at (UTC,
23
- * `YYYY-MM-DD"T"HH24:MI:SS.US"Z"`). Independent of the session TimeZone.
23
+ * `YYYY-MM-DD"T"HH24:MI:SS.US"Z"`), then entity_id only when it is not null.
24
+ * A row without an entity_id (read-touches, rows written before the column
25
+ * existed) hashes exactly as it always did. Independent of the session TimeZone.
24
26
  */
25
27
  export const CANONICAL_FUNCTION_SQL = `
26
28
  CREATE OR REPLACE FUNCTION pipework_audit_record_canonical(r audit_record) RETURNS text
27
29
  LANGUAGE sql IMMUTABLE SET search_path = pg_catalog AS $$
28
- SELECT jsonb_build_array(
30
+ SELECT (jsonb_build_array(
29
31
  r.chain_seq, r.id, to_char(r.trace_day, 'YYYY-MM-DD'), r.trace_id, r.trace_step_id,
30
32
  r.tenant_id, r.actor_id, r.entity, r.verb, r.before, r.after, r.captured_inputs,
31
33
  to_char(r.occurred_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')
32
- )::text
34
+ ) || CASE WHEN r.entity_id IS NULL THEN '[]'::jsonb ELSE jsonb_build_array(r.entity_id) END)::text
33
35
  $$`;
34
36
  function insertTriggerFunctionSql(schema) {
35
37
  return `
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/audit/chain/install.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,6EAA6E;AAC7E,qEAAqE;AACrE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,sDAAsD;AAEtD,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAE1C,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAE9D,oEAAoE;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,sCAAsC,CAAA;AAE3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAA8B,CAAA;AAEjE,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAAG,wBAAwB,qBAAqB,UAAU,CAAA;AAEpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;GAQnC,CAAA;AAEH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO;;kEAEyD,MAAM;;uCAEjC,qBAAqB;;;gBAG5C,gBAAgB;4BACJ,gBAAgB;;iCAEX,gBAAgB;;;;WAItC,gBAAgB;;;GAGxB,CAAA;AACH,CAAC;AAED,MAAM,wBAAwB,GAAG;;;;;;;GAO9B,CAAA;AAEH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAM;IAC5C,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA,mCAAmC,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IAE1E,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,8BAA8B,gBAAgB,IAAI;QAClD,uEAAuE,CACxE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,8BAA8B,mBAAmB,IAAI;QACrD,kGAAkG,CACnG,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAA;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,sFAAsF;QACtF,oEAAoE,CACrE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,qGAAqG;QACrG,kEAAkE,CACnE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,uEAAuE,aAAa,eAAe,CACpG,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/audit/chain/install.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,6EAA6E;AAC7E,qEAAqE;AACrE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,sDAAsD;AAEtD,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAE1C,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAE9D,oEAAoE;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,sCAAsC,CAAA;AAE3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAA8B,CAAA;AAEjE,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAAG,wBAAwB,qBAAqB,UAAU,CAAA;AAEpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;GAQnC,CAAA;AAEH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO;;kEAEyD,MAAM;;uCAEjC,qBAAqB;;;gBAG5C,gBAAgB;4BACJ,gBAAgB;;iCAEX,gBAAgB;;;;WAItC,gBAAgB;;;GAGxB,CAAA;AACH,CAAC;AAED,MAAM,wBAAwB,GAAG;;;;;;;GAO9B,CAAA;AAEH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAM;IAC5C,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA,mCAAmC,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IAE1E,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,8BAA8B,gBAAgB,IAAI;QAClD,uEAAuE,CACxE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,8BAA8B,mBAAmB,IAAI;QACrD,kGAAkG,CACnG,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAA;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,sFAAsF;QACtF,oEAAoE,CACrE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,qGAAqG;QACrG,kEAAkE,CACnE,CAAC,CAAA;IACF,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CACtB,uEAAuE,aAAa,eAAe,CACpG,CAAC,CAAA;AACJ,CAAC"}
@@ -8,6 +8,10 @@ export type AuditRecordRow = typeof auditRecord.Select;
8
8
  export interface AuditQueryFilters {
9
9
  /** Match audit rows whose `entity` is in this set. Single string accepted as shorthand. */
10
10
  readonly entity?: string | readonly string[];
11
+ /**
12
+ * Match audit rows written for the row with this primary key — one row's history, served by an index on `(tenantId, entity, entityId, occurredAt, id)`. Requires `entity`. The text is the key's value, or for a composite key `audit.entityId(table, row)`; access-link rows carry the link id. Rows written before `entityId` existed, and read-touches rows, have none and never match.
13
+ */
14
+ readonly entityId?: string;
11
15
  /** Match audit rows whose `verb` is in this set. Single string accepted as shorthand. */
12
16
  readonly verb?: string | readonly string[];
13
17
  /** Match audit rows whose `actorId` equals this. `null` matches system-actor rows. */
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/audit/query/query.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAuBvD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,WAAW,CAAC,MAAM,CAAA;AAEtD,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IAC5C,yFAAyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IAC1C,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,cAAc,EAAE,CAAA;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAKD;;;;GAIG;AACH,wBAAsB,KAAK,CACzB,OAAO,GAAE,iBAAsB,EAC/B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA8C3B"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../../src/audit/query/query.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAuBvD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,WAAW,CAAC,MAAM,CAAA;AAEtD,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IAC5C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IAC1C,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,cAAc,EAAE,CAAA;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAKD;;;;GAIG;AACH,wBAAsB,KAAK,CACzB,OAAO,GAAE,iBAAsB,EAC/B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAsD3B"}
@@ -28,9 +28,15 @@ export async function query(filters = {}, options = {}) {
28
28
  const ascending = options.asc === true;
29
29
  const table = auditRecord.table();
30
30
  const cols = table;
31
+ if (filters.entityId !== undefined && (filters.entity === undefined || (Array.isArray(filters.entity) && filters.entity.length === 0))) {
32
+ throw new ValidationError('audit.query: entityId requires entity. A row id is only meaningful within one entity — ' +
33
+ 'pass { entity: "Item", entityId } (use audit.entityId(table, row) for a composite key).');
34
+ }
31
35
  const conditions = [];
32
36
  appendFilter(conditions, filters.entity, v => buildInOrEq(cols['entity'], v));
33
37
  appendFilter(conditions, filters.verb, v => buildInOrEq(cols['verb'], v));
38
+ if (filters.entityId !== undefined)
39
+ conditions.push(eq(cols['entityId'], filters.entityId));
34
40
  if (filters.actorId !== undefined) {
35
41
  conditions.push(eq(cols['actorId'], filters.actorId));
36
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/audit/query/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/audit/query/query.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,11 @@ export declare const auditRecordFields: {
21
21
  }>;
22
22
  entity: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
23
23
  verb: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
24
+ entityId: import("../../data/domain/types.js").FieldDescriptor<string | null, {
25
+ nullable: true;
26
+ }> & import("../../data/domain/field.js").FieldModifiers<string | null, {
27
+ nullable: true;
28
+ }>;
24
29
  before: import("../../data/domain/types.js").FieldDescriptor<import("zod/v4").JSONType, {
25
30
  nullable: true;
26
31
  }> & import("../../data/domain/field.js").FieldModifiers<import("zod/v4").JSONType, {
@@ -1 +1 @@
1
- {"version":3,"file":"audit-record-fields.d.ts","sourceRoot":"","sources":["../../../src/audit/schema/audit-record-fields.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuC7B,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAA6C,CAAA"}
1
+ {"version":3,"file":"audit-record-fields.d.ts","sourceRoot":"","sources":["../../../src/audit/schema/audit-record-fields.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C7B,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAA6C,CAAA"}
@@ -38,6 +38,11 @@ export const auditRecordFields = {
38
38
  // non-null — a `@touches` annotation requires both halves.
39
39
  entity: field.text(),
40
40
  verb: field.text(),
41
+ // The audited row's primary key as text: the single key's value, or for a
42
+ // composite key a JSON array of the key values (as text, in key order). Null
43
+ // on read-touches rows and on rows written before the column existed.
44
+ // Indexed with the tenant and entity so one row's history is an index range.
45
+ entityId: field.text().nullable(),
41
46
  // Server-side `.traceSafe()` projection of the row before/after the write.
42
47
  // Both nullable: create-verb has no `before`, delete-verb has no `after`,
43
48
  // read-touches has neither.
@@ -1 +1 @@
1
- {"version":3,"file":"audit-record-fields.js","sourceRoot":"","sources":["../../../src/audit/schema/audit-record-fields.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,wBAAwB;AACxB,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,oEAAoE;AACpE,0EAA0E;AAC1E,2EAA2E;AAC3E,8DAA8D;AAE9D,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAE5D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE;IACvD,4EAA4E;IAC5E,uEAAuE;IACvE,qEAAqE;IACrE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE;IACtB,0EAA0E;IAC1E,yEAAyE;IACzE,kEAAkE;IAClE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClE,2EAA2E;IAC3E,6DAA6D;IAC7D,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC1E,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAChC,0EAA0E;IAC1E,2DAA2D;IAC3D,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE;IACpB,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;IAClB,2EAA2E;IAC3E,0EAA0E;IAC1E,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IACnD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IAClD,4EAA4E;IAC5E,4EAA4E;IAC5E,0EAA0E;IAC1E,8BAA8B;IAC9B,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3D,UAAU,EAAE,KAAK,CAAC,SAAS,EAAE;IAC7B,oEAAoE;IACpE,0EAA0E;IAC1E,0DAA0D;IAC1D,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAA;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"audit-record-fields.js","sourceRoot":"","sources":["../../../src/audit/schema/audit-record-fields.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,2EAA2E;AAC3E,wBAAwB;AACxB,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,oEAAoE;AACpE,0EAA0E;AAC1E,2EAA2E;AAC3E,8DAA8D;AAE9D,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAE5D,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE;IACvD,4EAA4E;IAC5E,uEAAuE;IACvE,qEAAqE;IACrE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE;IACtB,0EAA0E;IAC1E,yEAAyE;IACzE,kEAAkE;IAClE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClE,2EAA2E;IAC3E,6DAA6D;IAC7D,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC1E,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAChC,0EAA0E;IAC1E,2DAA2D;IAC3D,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE;IACpB,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;IAClB,0EAA0E;IAC1E,6EAA6E;IAC7E,sEAAsE;IACtE,6EAA6E;IAC7E,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IACjC,2EAA2E;IAC3E,0EAA0E;IAC1E,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IACnD,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IAClD,4EAA4E;IAC5E,4EAA4E;IAC5E,0EAA0E;IAC1E,8BAA8B;IAC9B,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3D,UAAU,EAAE,KAAK,CAAC,SAAS,EAAE;IAC7B,oEAAoE;IACpE,0EAA0E;IAC1E,0DAA0D;IAC1D,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAA;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA"}
@@ -1,3 +1,6 @@
1
+ /** One row's history: `audit.query({ entity, entityId })` is a range scan of this index. */
2
+ export declare const AUDIT_ENTITY_INDEX = "audit_record_entity_idx";
3
+ export declare const AUDIT_ENTITY_INDEX_SQL = "CREATE INDEX IF NOT EXISTS audit_record_entity_idx ON audit_record (tenant_id, entity, entity_id, occurred_at, id)";
1
4
  export declare const auditRecord: import("../../data/domain/types.js").DefinedTable<{
2
5
  id: import("../../data/domain/types.js").FieldDescriptor<import("../../data/domain/brand.js").Branded<string, "AuditRecordId">, {
3
6
  defaultRandom: true;
@@ -21,6 +24,11 @@ export declare const auditRecord: import("../../data/domain/types.js").DefinedTa
21
24
  }>;
22
25
  entity: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
23
26
  verb: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
27
+ entityId: import("../../data/domain/types.js").FieldDescriptor<string | null, {
28
+ nullable: true;
29
+ }> & import("../../data/domain/field.js").FieldModifiers<string | null, {
30
+ nullable: true;
31
+ }>;
24
32
  before: import("../../data/domain/types.js").FieldDescriptor<import("zod/v4").JSONType, {
25
33
  nullable: true;
26
34
  }> & import("../../data/domain/field.js").FieldModifiers<import("zod/v4").JSONType, {
@@ -1 +1 @@
1
- {"version":3,"file":"audit-record.d.ts","sourceRoot":"","sources":["../../../src/audit/schema/audit-record.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAGtB,CAAA"}
1
+ {"version":3,"file":"audit-record.d.ts","sourceRoot":"","sources":["../../../src/audit/schema/audit-record.ts"],"names":[],"mappings":"AAWA,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,4BAA4B,CAAA;AAC3D,eAAO,MAAM,sBAAsB,uHACiF,CAAA;AAEpH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAMtB,CAAA"}
@@ -5,9 +5,16 @@
5
5
  // to inherit the TraceId/TraceStepId brands without emitting an FK — the
6
6
  // audit partition is free to outlive its source trace partition.
7
7
  import { define } from '../../data/domain/index.js';
8
+ import { index } from '../../data/db/idx/index.js';
8
9
  import { auditRecordFields } from './audit-record-fields.js';
10
+ /** One row's history: `audit.query({ entity, entityId })` is a range scan of this index. */
11
+ export const AUDIT_ENTITY_INDEX = 'audit_record_entity_idx';
12
+ export const AUDIT_ENTITY_INDEX_SQL = `CREATE INDEX IF NOT EXISTS ${AUDIT_ENTITY_INDEX} ON audit_record (tenant_id, entity, entity_id, occurred_at, id)`;
9
13
  export const auditRecord = define('audit_record', auditRecordFields, {
10
14
  primaryKey: ['id', 'traceDay'],
11
15
  partitionBy: { strategy: 'range', column: 'traceDay' },
16
+ indexes: (t) => ({
17
+ entity: index(AUDIT_ENTITY_INDEX).on(t['tenantId'], t['entity'], t['entityId'], t['occurredAt'], t['id']),
18
+ }),
12
19
  });
13
20
  //# sourceMappingURL=audit-record.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-record.js","sourceRoot":"","sources":["../../../src/audit/schema/audit-record.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,2EAA2E;AAC3E,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,iEAAiE;AAEjE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE,iBAAiB,EAAE;IACnE,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC;IAC9B,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE;CACvD,CAAC,CAAA"}
1
+ {"version":3,"file":"audit-record.js","sourceRoot":"","sources":["../../../src/audit/schema/audit-record.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,2EAA2E;AAC3E,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,iEAAiE;AAEjE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,4FAA4F;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAG,yBAAyB,CAAA;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GACjC,8BAA8B,kBAAkB,kEAAkE,CAAA;AAEpH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE,iBAAiB,EAAE;IACnE,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC;IAC9B,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE;IACtD,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACf,MAAM,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;KAC1G,CAAC;CACH,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"provision-framework.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/provision-framework.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AAQ/F,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEhF,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAExE;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,SAAkB,IAAI,+BAA8B;CACrD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,oBAAoB,EACjC,iBAAiB,EAAE,OAAO,EAC1B,OAAO,GAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CAAuB,GAClF,OAAO,CAAC,qBAAqB,CAAC,CAiDhC"}
1
+ {"version":3,"file":"provision-framework.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/provision-framework.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AAS/F,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEhF,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAExE;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,SAAkB,IAAI,+BAA8B;CACrD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,oBAAoB,EACjC,iBAAiB,EAAE,OAAO,EAC1B,OAAO,GAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CAAuB,GAClF,OAAO,CAAC,qBAAqB,CAAC,CAkDhC"}
@@ -8,6 +8,7 @@ import { sql } from '../db/sql.js';
8
8
  import { quoteIdentifier } from '../db/identifiers.js';
9
9
  import { syncTracePartitions } from '../../trace/partition/index.js';
10
10
  import { PARTITIONED_TRACE_TABLE_NAMES } from '../../trace/partition/tables.js';
11
+ import { AUDIT_ENTITY_INDEX_SQL } from '../../audit/schema/audit-record.js';
11
12
  import { installAuditChain } from '../../audit/chain/install.js';
12
13
  import { loadInternalDefinitions } from './internal-definitions.js';
13
14
  import { buildSnapshot, EMPTY_SNAPSHOT } from './snapshot.js';
@@ -76,6 +77,8 @@ export async function provisionFrameworkTables(db, blueprint, traceConfig, isDef
76
77
  }
77
78
  for (const statement of alterStatements)
78
79
  await db.execute(sql.raw(statement));
80
+ if (tables.some(t => t.name === 'audit_record'))
81
+ await db.execute(sql.raw(AUDIT_ENTITY_INDEX_SQL));
79
82
  if (blueprint.audit?.chain === true)
80
83
  await installAuditChain(db);
81
84
  if (options.rls !== undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"provision-framework.js","sourceRoot":"","sources":["../../../src/data/migrate/provision-framework.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provision-framework.js","sourceRoot":"","sources":["../../../src/data/migrate/provision-framework.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /** Field keys on `audit_record` an INSERT supplies (the chain columns are trigger-owned), in declaration order — the SELECT projection's positional order. */
2
- export declare const AUDIT_FIELD_KEYS: ("hash" | "tenantId" | "traceId" | "capturedInputs" | "id" | "traceDay" | "chainSeq" | "prevHash" | "traceStepId" | "actorId" | "entity" | "verb" | "before" | "after" | "occurredAt")[];
2
+ export declare const AUDIT_FIELD_KEYS: ("hash" | "tenantId" | "traceId" | "capturedInputs" | "id" | "traceDay" | "chainSeq" | "prevHash" | "traceStepId" | "actorId" | "entity" | "verb" | "entityId" | "before" | "after" | "occurredAt")[];
3
3
  /** Quoted column list, comma-separated — the `INSERT INTO audit_record (...)` parenthetical. */
4
4
  export declare const AUDIT_COLUMNS_SQL: string;
5
5
  //# sourceMappingURL=audit-columns.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-columns.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-columns.ts"],"names":[],"mappings":"AAUA,8JAA8J;AAC9J,eAAO,MAAM,gBAAgB,0LACiB,CAAA;AAE9C,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,QAEjB,CAAA"}
1
+ {"version":3,"file":"audit-columns.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-columns.ts"],"names":[],"mappings":"AAUA,8JAA8J;AAC9J,eAAO,MAAM,gBAAgB,uMACiB,CAAA;AAE9C,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,QAEjB,CAAA"}
@@ -1,6 +1,13 @@
1
1
  import type { SQL } from '../../db/types.js';
2
2
  import type { ActiveAuditStep } from '../../../trace/runtime/step-buffer.js';
3
3
  import type { MetaTouch } from '../../../trace/runtime/step-meta.js';
4
+ import type { DefinedTable } from '../../domain/types.js';
5
+ /**
6
+ * SQL for `audit_record.entity_id` read from CTE alias `alias`: the single key
7
+ * column cast to text, or a compact JSON array of the key columns as text for
8
+ * a composite key; null for a table with no declared key. Matches `auditEntityId` in `primary-key.ts`.
9
+ */
10
+ export declare function buildEntityIdExpr(table: DefinedTable<any>, alias: string): SQL;
4
11
  /**
5
12
  * Information about the active flow_step plus the actor/tenant the row is
6
13
  * attributed to. The CTE compiler resolves these from the active `Flow` and
@@ -21,11 +28,11 @@ export interface AuditSelectContext {
21
28
  *
22
29
  * The shape is positional — matches `AUDIT_COLUMNS_SQL` exactly:
23
30
  * id, trace_day, trace_id, trace_step_id, tenant_id, actor_id,
24
- * entity, verb, before, after, captured_inputs, occurred_at
31
+ * entity, verb, entity_id, before, after, captured_inputs, occurred_at
25
32
  *
26
33
  * `from` is the verb's contributing FROM clause (e.g. `FROM updated u LEFT
27
34
  * JOIN before b ON ...`), `before` / `after` are the per-row jsonb
28
35
  * expressions to project (or `NULL::jsonb` when the half doesn't apply).
29
36
  */
30
- export declare function buildAuditInserts(ctx: AuditSelectContext, from: SQL, before: SQL, after: SQL): SQL;
37
+ export declare function buildAuditInserts(ctx: AuditSelectContext, from: SQL, entityId: SQL, before: SQL, after: SQL): SQL;
31
38
  //# sourceMappingURL=audit-select.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-select.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-select.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAA;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAA;AAEpE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAA;IACtC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,kBAAkB,EACvB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,GAAG,GACT,GAAG,CAwBL"}
1
+ {"version":3,"file":"audit-select.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-select.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAA;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAA;AAIpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAE/B,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EACxB,KAAK,EAAE,MAAM,GACZ,GAAG,CAOL;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAA;IACtC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,kBAAkB,EACvB,IAAI,EAAE,GAAG,EACT,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,GAAG,EACX,KAAK,EAAE,GAAG,GACT,GAAG,CAyBL"}
@@ -11,19 +11,38 @@
11
11
  // FROM. (Postgres folds these into a single insert plan.) Empty targets is
12
12
  // a programmer error — the executor short-circuits to the vanilla branch.
13
13
  import { sql } from '../../db/sql.js';
14
+ import { quoteIdentifier } from '../../db/identifiers.js';
15
+ import { camelToSnake } from '../../domain/naming.js';
16
+ import { declaredPrimaryKeyFieldKeys } from './primary-key.js';
17
+ /**
18
+ * SQL for `audit_record.entity_id` read from CTE alias `alias`: the single key
19
+ * column cast to text, or a compact JSON array of the key columns as text for
20
+ * a composite key; null for a table with no declared key. Matches `auditEntityId` in `primary-key.ts`.
21
+ */
22
+ export function buildEntityIdExpr(
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
24
+ table, alias) {
25
+ const keys = declaredPrimaryKeyFieldKeys(table);
26
+ if (keys.length === 0)
27
+ return sql `NULL::text`;
28
+ const cols = keys.map(k => `${alias}.${quoteIdentifier(camelToSnake(k), 'pk column')}::text`);
29
+ return cols.length === 1
30
+ ? sql.raw(cols[0])
31
+ : sql.raw(`array_to_json(ARRAY[${cols.join(', ')}])::text`);
32
+ }
14
33
  /**
15
34
  * Composes the `audit_inserts` CTE body: one `SELECT` per `@touches` target,
16
35
  * joined by `UNION ALL`, projecting onto the `audit_record` column order.
17
36
  *
18
37
  * The shape is positional — matches `AUDIT_COLUMNS_SQL` exactly:
19
38
  * id, trace_day, trace_id, trace_step_id, tenant_id, actor_id,
20
- * entity, verb, before, after, captured_inputs, occurred_at
39
+ * entity, verb, entity_id, before, after, captured_inputs, occurred_at
21
40
  *
22
41
  * `from` is the verb's contributing FROM clause (e.g. `FROM updated u LEFT
23
42
  * JOIN before b ON ...`), `before` / `after` are the per-row jsonb
24
43
  * expressions to project (or `NULL::jsonb` when the half doesn't apply).
25
44
  */
26
- export function buildAuditInserts(ctx, from, before, after) {
45
+ export function buildAuditInserts(ctx, from, entityId, before, after) {
27
46
  const capturedInputs = ctx.step.capturedInputs;
28
47
  const capturedJson = capturedInputs === null
29
48
  ? sql `NULL::jsonb`
@@ -38,6 +57,7 @@ export function buildAuditInserts(ctx, from, before, after) {
38
57
  ${ctx.actorId},
39
58
  ${t.entity},
40
59
  ${t.verb},
60
+ ${entityId},
41
61
  ${before},
42
62
  ${after},
43
63
  ${capturedJson},
@@ -1 +1 @@
1
- {"version":3,"file":"audit-select.js","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-select.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,wEAAwE;AACxE,4EAA4E;AAC5E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAE1E,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAoBrC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAuB,EACvB,IAAS,EACT,MAAW,EACX,KAAU;IAEV,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,cAAc,CAAA;IAC9C,MAAM,YAAY,GAAG,cAAc,KAAK,IAAI;QAC1C,CAAC,CAAC,GAAG,CAAA,aAAa;QAClB,CAAC,CAAC,GAAG,CAAA,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,SAAS,CAAA;IAEjD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAA;;;QAGlC,GAAG,CAAC,IAAI,CAAC,QAAQ;QACjB,GAAG,CAAC,IAAI,CAAC,OAAO;QAChB,GAAG,CAAC,IAAI,CAAC,MAAM;QACf,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,OAAO;QACX,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,IAAI;QACN,MAAM;QACN,KAAK;QACL,YAAY;;MAEd,IAAI;GACP,CAAC,CAAA;IAEF,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAA,aAAa,CAAC,CAAA;AAC5C,CAAC"}
1
+ {"version":3,"file":"audit-select.js","sourceRoot":"","sources":["../../../../src/data/write/audit/audit-select.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAC7E,wEAAwE;AACxE,4EAA4E;AAC5E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAE1E,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAIrC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AAG9D;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;AAC/B,yFAAyF;AACzF,KAAwB,EACxB,KAAa;IAEb,MAAM,IAAI,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAA,YAAY,CAAA;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IAC7F,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC;QACtB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;AAC/D,CAAC;AAiBD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAuB,EACvB,IAAS,EACT,QAAa,EACb,MAAW,EACX,KAAU;IAEV,MAAM,cAAc,GAAG,GAAG,CAAC,IAAI,CAAC,cAAc,CAAA;IAC9C,MAAM,YAAY,GAAG,cAAc,KAAK,IAAI;QAC1C,CAAC,CAAC,GAAG,CAAA,aAAa;QAClB,CAAC,CAAC,GAAG,CAAA,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,SAAS,CAAA;IAEjD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAA;;;QAGlC,GAAG,CAAC,IAAI,CAAC,QAAQ;QACjB,GAAG,CAAC,IAAI,CAAC,OAAO;QAChB,GAAG,CAAC,IAAI,CAAC,MAAM;QACf,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,OAAO;QACX,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,IAAI;QACN,QAAQ;QACR,MAAM;QACN,KAAK;QACL,YAAY;;MAEd,IAAI;GACP,CAAC,CAAA;IAEF,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAA,aAAa,CAAC,CAAA;AAC5C,CAAC"}
@@ -4,4 +4,12 @@ import type { DefinedTable } from '../../domain/types.js';
4
4
  * table declares no primary key — the audit CTE needs one to pair rows.
5
5
  */
6
6
  export declare function primaryKeyFieldKeys(table: DefinedTable<any>): readonly string[];
7
+ /** The declared primary-key field keys in declaration order; empty when the table has none. */
8
+ export declare function declaredPrimaryKeyFieldKeys(table: DefinedTable<any>): readonly string[];
9
+ /**
10
+ * The audited row's primary key as `audit_record.entity_id` text: the single
11
+ * key's value, or for a composite key a compact JSON array of the key values
12
+ * as strings, in key order (`["a","7"]`).
13
+ */
14
+ export declare function auditEntityId(table: DefinedTable<any>, row: Readonly<Record<string, unknown>>): string;
7
15
  //# sourceMappingURL=primary-key.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"primary-key.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/primary-key.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,uBAAuB,CAAA;AAEtE;;;GAGG;AACH,wBAAgB,mBAAmB,CAEjC,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,GACvB,SAAS,MAAM,EAAE,CAkBnB"}
1
+ {"version":3,"file":"primary-key.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/primary-key.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,uBAAuB,CAAA;AAEtE;;;GAGG;AACH,wBAAgB,mBAAmB,CAEjC,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,GACvB,SAAS,MAAM,EAAE,CAYnB;AAED,+FAA+F;AAC/F,wBAAgB,2BAA2B,CAEzC,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,GACvB,SAAS,MAAM,EAAE,CASnB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAE3B,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EACxB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACrC,MAAM,CAUR"}
@@ -10,6 +10,20 @@
10
10
  */
11
11
  export function primaryKeyFieldKeys(
12
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- DefinedTable's field generics erase at this boundary; we only read $fields and $options
13
+ table) {
14
+ const keys = declaredPrimaryKeyFieldKeys(table);
15
+ if (keys.length === 0) {
16
+ throw new Error(`[pipework] Cannot emit audit CTE for "${table.$name}": ` +
17
+ `the table declares no primary key.\n\n` +
18
+ ` An audit-emitting write needs a primary key so the pre-image\n` +
19
+ ` and post-image rows can be paired. Add .primaryKey() to a field\n` +
20
+ ` or pass { primaryKey: ['col1', 'col2'] } to the table options.\n`);
21
+ }
22
+ return keys;
23
+ }
24
+ /** The declared primary-key field keys in declaration order; empty when the table has none. */
25
+ export function declaredPrimaryKeyFieldKeys(
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
13
27
  table) {
14
28
  const composite = table.$options.primaryKey;
15
29
  if (composite !== undefined && composite.length > 0)
@@ -20,13 +34,24 @@ table) {
20
34
  if (descriptor._meta.primaryKey)
21
35
  single.push(key);
22
36
  }
23
- if (single.length === 0) {
24
- throw new Error(`[pipework] Cannot emit audit CTE for "${table.$name}": ` +
25
- `the table declares no primary key.\n\n` +
26
- ` An audit-emitting write needs a primary key so the pre-image\n` +
27
- ` and post-image rows can be paired. Add .primaryKey() to a field\n` +
28
- ` or pass { primaryKey: ['col1', 'col2'] } to the table options.\n`);
29
- }
30
37
  return single;
31
38
  }
39
+ /**
40
+ * The audited row's primary key as `audit_record.entity_id` text: the single
41
+ * key's value, or for a composite key a compact JSON array of the key values
42
+ * as strings, in key order (`["a","7"]`).
43
+ */
44
+ export function auditEntityId(
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see primaryKeyFieldKeys
46
+ table, row) {
47
+ const keys = primaryKeyFieldKeys(table);
48
+ const values = keys.map(k => {
49
+ const v = row[k];
50
+ if (v === null || v === undefined) {
51
+ throw new Error(`audit entity id for "${table.$name}": primary key field "${k}" is missing from the row.`);
52
+ }
53
+ return v instanceof Date ? v.toISOString() : String(v);
54
+ });
55
+ return keys.length === 1 ? values[0] : JSON.stringify(values);
56
+ }
32
57
  //# sourceMappingURL=primary-key.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"primary-key.js","sourceRoot":"","sources":["../../../../src/data/write/audit/primary-key.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,qCAAqC;AAIrC;;;GAGG;AACH,MAAM,UAAU,mBAAmB;AACjC,yJAAyJ;AACzJ,KAAwB;IAExB,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAA;IAC3C,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IACrE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAsB,CAAA;IAC3C,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnD,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,yCAAyC,KAAK,CAAC,KAAK,KAAK;YACzD,wCAAwC;YACxC,kEAAkE;YAClE,qEAAqE;YACrE,oEAAoE,CACrE,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"primary-key.js","sourceRoot":"","sources":["../../../../src/data/write/audit/primary-key.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,qCAAqC;AAIrC;;;GAGG;AACH,MAAM,UAAU,mBAAmB;AACjC,yJAAyJ;AACzJ,KAAwB;IAExB,MAAM,IAAI,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAA;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,yCAAyC,KAAK,CAAC,KAAK,KAAK;YACzD,wCAAwC;YACxC,kEAAkE;YAClE,qEAAqE;YACrE,oEAAoE,CACrE,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,2BAA2B;AACzC,yFAAyF;AACzF,KAAwB;IAExB,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAA;IAC3C,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IACrE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAsB,CAAA;IAC3C,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;AAC3B,yFAAyF;AACzF,KAAwB,EACxB,GAAsC;IAEtC,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QAChB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,KAAK,yBAAyB,CAAC,4BAA4B,CAAC,CAAA;QAC5G,CAAC;QACD,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;AAChE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"verb-delete.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/verb-delete.ts"],"names":[],"mappings":"AAYA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAI9E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAG7C,KAAK,KAAK,GAAG,GAAG,CAAA;AAEhB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,kBAAkB,GAAG,GAAG,CAuC7F"}
1
+ {"version":3,"file":"verb-delete.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/verb-delete.ts"],"names":[],"mappings":"AAYA,OAAO,EAAwC,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAIjG,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAG7C,KAAK,KAAK,GAAG,GAAG,CAAA;AAEhB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,kBAAkB,GAAG,GAAG,CAuC7F"}
@@ -9,7 +9,7 @@ import { sql } from '../../db/sql.js';
9
9
  import { quoteIdentifier } from '../../db/identifiers.js';
10
10
  import { camelToSnake } from '../../domain/naming.js';
11
11
  import { buildTraceSafeProjection } from './projection.js';
12
- import { buildAuditInserts } from './audit-select.js';
12
+ import { buildAuditInserts, buildEntityIdExpr } from './audit-select.js';
13
13
  import { primaryKeyFieldKeys } from './primary-key.js';
14
14
  import { AUDIT_COLUMNS_SQL } from './audit-columns.js';
15
15
  import { buildFinalSelect } from './final-select.js';
@@ -37,7 +37,7 @@ export function compileDeleteAudit(db, state, ctx) {
37
37
  // for the pre-image join, and `deleted` includes only the rows that actually
38
38
  // matched. Joining onto `before` carries the FOR UPDATE lock.
39
39
  const fromClause = sql.raw(`FROM deleted JOIN before ON ${joinOn}`);
40
- const auditSelect = buildAuditInserts(ctx, fromClause, before, sql `NULL::jsonb`);
40
+ const auditSelect = buildAuditInserts(ctx, fromClause, buildEntityIdExpr(state.table, 'deleted'), before, sql `NULL::jsonb`);
41
41
  const finalSelect = buildFinalSelect(state, 'deleted');
42
42
  return sql `
43
43
  WITH before AS (${beforeInner}),