@agentplat/audit-postgres 0.3.0-beta.2 → 0.3.0-beta.5
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.
- package/README.md +43 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +355 -5
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +2 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +19 -20
- package/dist/migrations.js.map +1 -1
- package/migrations/002_collective_telemetry.down.sql +4 -0
- package/migrations/002_collective_telemetry.up.sql +34 -0
- package/migrations/003_collective_telemetry_delivery_receipts.down.sql +1 -0
- package/migrations/003_collective_telemetry_delivery_receipts.up.sql +21 -0
- package/migrations/004_collective_telemetry_monotonic_anchors.down.sql +2 -0
- package/migrations/004_collective_telemetry_monotonic_anchors.up.sql +43 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -4,26 +4,64 @@ PostgreSQL sinks for applications that need durable audit or Session events
|
|
|
4
4
|
without installing the Agent Room schema.
|
|
5
5
|
|
|
6
6
|
```ts
|
|
7
|
-
import { createPostgresPool } from
|
|
7
|
+
import { createPostgresPool } from "@agentplat/postgres";
|
|
8
8
|
import {
|
|
9
9
|
PostgresAuditSink,
|
|
10
|
+
PostgresCollectiveTelemetryMonotonicAnchorV1,
|
|
11
|
+
PostgresCollectiveTelemetryStoreV1,
|
|
10
12
|
PostgresSessionEventSink,
|
|
11
13
|
runAuditMigrations,
|
|
12
|
-
} from
|
|
14
|
+
} from "@agentplat/audit-postgres";
|
|
13
15
|
|
|
14
16
|
const pool = createPostgresPool();
|
|
15
|
-
await runAuditMigrations(pool, { schema:
|
|
17
|
+
await runAuditMigrations(pool, { schema: "agentplat_orders" });
|
|
18
|
+
const protectedWitnessPool = createPostgresPool({
|
|
19
|
+
connectionString: process.env.TELEMETRY_WITNESS_DATABASE_URL,
|
|
20
|
+
});
|
|
21
|
+
await runAuditMigrations(protectedWitnessPool, {
|
|
22
|
+
schema: "agentplat_witness",
|
|
23
|
+
});
|
|
16
24
|
|
|
17
|
-
const audit = new PostgresAuditSink(pool, { schema:
|
|
25
|
+
const audit = new PostgresAuditSink(pool, { schema: "agentplat_orders" });
|
|
18
26
|
const sessionEvents = new PostgresSessionEventSink(pool, {
|
|
19
|
-
schema:
|
|
27
|
+
schema: "agentplat_orders",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const telemetryStore = new PostgresCollectiveTelemetryStoreV1(pool, {
|
|
31
|
+
schema: "agentplat_orders",
|
|
32
|
+
tenantId: "tenant-a",
|
|
20
33
|
});
|
|
34
|
+
const telemetryAnchor = new PostgresCollectiveTelemetryMonotonicAnchorV1(
|
|
35
|
+
protectedWitnessPool,
|
|
36
|
+
{ schema: "agentplat_witness", tenantId: "tenant-a" },
|
|
37
|
+
);
|
|
21
38
|
```
|
|
22
39
|
|
|
23
40
|
Both tables are append-only and all writes include `tenant_id`. Sinks redact
|
|
24
41
|
secret-like keys recursively before persistence. Deterministic IDs make event
|
|
25
42
|
retries idempotent.
|
|
26
43
|
|
|
44
|
+
`PostgresCollectiveTelemetryStoreV1` adds revision-and-state-digest CAS for the
|
|
45
|
+
signed telemetry chain and an append-only event table. Event identity conflicts
|
|
46
|
+
roll the state transaction back instead of accepting an ambiguous replay. The
|
|
47
|
+
runtime also requires an independently protected monotonic anchor. Migration
|
|
48
|
+
004 and `PostgresCollectiveTelemetryMonotonicAnchorV1` provide one bounded CAS
|
|
49
|
+
head per stream. PostgreSQL permits only an exact monotonic successor and
|
|
50
|
+
forbids deletion, so storage is bounded by stream count rather than event
|
|
51
|
+
count. For rollback separation, construct that adapter with a pool and schema
|
|
52
|
+
protected independently from the replaceable application-state pool;
|
|
53
|
+
using the same database preserves restart continuity but not backup-domain
|
|
54
|
+
separation.
|
|
55
|
+
|
|
56
|
+
Migration 003 adds bounded, short-lived delivery receipts. A scope lock makes
|
|
57
|
+
the receipt-capacity check, telemetry state CAS, append-only event insert and
|
|
58
|
+
receipt insert one transaction. The nominal runtime handoff is therefore
|
|
59
|
+
idempotent by delivery digest independently of event retention. Runtime and
|
|
60
|
+
adapter consumers receive no independent record/release primitives: the
|
|
61
|
+
module-owned handoff privately removes the exact receipt only after the source
|
|
62
|
+
persists its recorded state, then ACKs that source envelope.
|
|
63
|
+
`maximumPendingDeliveryReceipts` defaults to 4096 and exhaustion fails closed.
|
|
64
|
+
|
|
27
65
|
The package uses the shared version/checksum ledger from
|
|
28
66
|
`@agentplat/postgres`. `migrate:down` is blocked unless the expected version,
|
|
29
67
|
exact confirmation string, and explicit data-loss flag are all supplied. It is
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,60 @@
|
|
|
1
|
-
import type { AuditRecord, AuditSink } from
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
export
|
|
1
|
+
import type { AuditRecord, AuditSink } from "@agentplat/audit";
|
|
2
|
+
import type { CollectiveTelemetryAnchorV1, CollectiveTelemetryDeliveryCommitResultV1, CollectiveTelemetryDeliveryReceiptV1, CollectiveTelemetryMonotonicAnchorPortV1, CollectiveTelemetryStateV1, CollectiveTelemetryStoreV1 } from "@agentplat/audit/collective-telemetry";
|
|
3
|
+
import type { SessionEventRecord, SessionEventSink } from "@agentplat/sessions";
|
|
4
|
+
import type { Pool } from "pg";
|
|
5
|
+
export { auditMigrationDirectory, auditRollbackConfirmation, getAuditMigrationStatus, rollbackAuditMigrations, runAuditMigrations, } from "./migrations.js";
|
|
6
|
+
export type { AuditPostgresMigrationOptions } from "./migrations.js";
|
|
6
7
|
export interface PostgresSinkOptions {
|
|
7
8
|
schema?: string;
|
|
8
9
|
}
|
|
10
|
+
export interface PostgresCollectiveTelemetryStoreOptions extends PostgresSinkOptions {
|
|
11
|
+
tenantId: string;
|
|
12
|
+
maximumPendingDeliveryReceipts?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface PostgresCollectiveTelemetryAnchorOptions extends PostgresSinkOptions {
|
|
15
|
+
tenantId: string;
|
|
16
|
+
}
|
|
17
|
+
/** CAS state plus append-only signed events for collective telemetry. */
|
|
18
|
+
export declare class PostgresCollectiveTelemetryStoreV1 implements CollectiveTelemetryStoreV1 {
|
|
19
|
+
private readonly stateTable;
|
|
20
|
+
private readonly eventTable;
|
|
21
|
+
private readonly deliveryReceiptTable;
|
|
22
|
+
private readonly maximumPendingDeliveryReceipts;
|
|
23
|
+
private readonly tenantId;
|
|
24
|
+
private readonly poolQuery;
|
|
25
|
+
private readonly poolConnect;
|
|
26
|
+
constructor(pool: Pool, options: PostgresCollectiveTelemetryStoreOptions);
|
|
27
|
+
load(streamId: string): Promise<CollectiveTelemetryStateV1 | null>;
|
|
28
|
+
save(state: CollectiveTelemetryStateV1, expectedRevision: number | null, expectedStateDigest: string | null): Promise<boolean>;
|
|
29
|
+
loadDelivery(streamId: string, deliveryDigest: string): Promise<CollectiveTelemetryDeliveryReceiptV1 | null>;
|
|
30
|
+
commitDelivery(input: {
|
|
31
|
+
readonly state: CollectiveTelemetryStateV1;
|
|
32
|
+
readonly expectedRevision: number;
|
|
33
|
+
readonly expectedStateDigest: string;
|
|
34
|
+
readonly receipt: CollectiveTelemetryDeliveryReceiptV1;
|
|
35
|
+
}): Promise<CollectiveTelemetryDeliveryCommitResultV1>;
|
|
36
|
+
releaseDelivery(streamId: string, deliveryDigest: string): Promise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Bounded monotonic witness for telemetry stream heads. It can be placed
|
|
40
|
+
* on a separately protected PostgreSQL pool/schema from the mutable stream
|
|
41
|
+
* state so an application-state rollback cannot silently roll the witness
|
|
42
|
+
* back with it.
|
|
43
|
+
*/
|
|
44
|
+
export declare class PostgresCollectiveTelemetryMonotonicAnchorV1 implements CollectiveTelemetryMonotonicAnchorPortV1 {
|
|
45
|
+
private readonly table;
|
|
46
|
+
private readonly tenantId;
|
|
47
|
+
private readonly poolQuery;
|
|
48
|
+
private readonly poolConnect;
|
|
49
|
+
constructor(pool: Pool, options: PostgresCollectiveTelemetryAnchorOptions);
|
|
50
|
+
load(anchorKey: string): Promise<CollectiveTelemetryAnchorV1 | null>;
|
|
51
|
+
save(input: {
|
|
52
|
+
readonly anchorKey: string;
|
|
53
|
+
readonly anchor: CollectiveTelemetryAnchorV1;
|
|
54
|
+
readonly expectedRevision: number | null;
|
|
55
|
+
readonly expectedStateDigest: string | null;
|
|
56
|
+
}): Promise<boolean>;
|
|
57
|
+
}
|
|
9
58
|
/** Append-only, redacting PostgreSQL AuditSink. */
|
|
10
59
|
export declare class PostgresAuditSink implements AuditSink {
|
|
11
60
|
private readonly pool;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,363 @@
|
|
|
1
|
-
import { redactAuditDetails } from
|
|
2
|
-
import { defaultPostgresSchema, qualifyPostgresName, } from
|
|
3
|
-
export { auditMigrationDirectory, auditRollbackConfirmation, getAuditMigrationStatus, rollbackAuditMigrations, runAuditMigrations, } from
|
|
1
|
+
import { redactAuditDetails } from "@agentplat/audit";
|
|
2
|
+
import { defaultPostgresSchema, qualifyPostgresName, } from "@agentplat/postgres";
|
|
3
|
+
export { auditMigrationDirectory, auditRollbackConfirmation, getAuditMigrationStatus, rollbackAuditMigrations, runAuditMigrations, } from "./migrations.js";
|
|
4
|
+
/** CAS state plus append-only signed events for collective telemetry. */
|
|
5
|
+
export class PostgresCollectiveTelemetryStoreV1 {
|
|
6
|
+
stateTable;
|
|
7
|
+
eventTable;
|
|
8
|
+
deliveryReceiptTable;
|
|
9
|
+
maximumPendingDeliveryReceipts;
|
|
10
|
+
tenantId;
|
|
11
|
+
poolQuery;
|
|
12
|
+
poolConnect;
|
|
13
|
+
constructor(pool, options) {
|
|
14
|
+
if (!options.tenantId)
|
|
15
|
+
throw new TypeError("collective telemetry tenant is required");
|
|
16
|
+
const maximumPendingDeliveryReceipts = options.maximumPendingDeliveryReceipts ?? 4_096;
|
|
17
|
+
if (!Number.isSafeInteger(maximumPendingDeliveryReceipts) ||
|
|
18
|
+
maximumPendingDeliveryReceipts < 1 ||
|
|
19
|
+
maximumPendingDeliveryReceipts > 100_000)
|
|
20
|
+
throw new TypeError("maximumPendingDeliveryReceipts is invalid");
|
|
21
|
+
const schema = options.schema ?? defaultPostgresSchema;
|
|
22
|
+
this.stateTable = qualifyPostgresName(schema, "collective_telemetry_states");
|
|
23
|
+
this.eventTable = qualifyPostgresName(schema, "collective_telemetry_events");
|
|
24
|
+
this.deliveryReceiptTable = qualifyPostgresName(schema, "collective_telemetry_delivery_receipts");
|
|
25
|
+
this.tenantId = options.tenantId;
|
|
26
|
+
this.poolQuery = pool.query.bind(pool);
|
|
27
|
+
this.poolConnect = pool.connect.bind(pool);
|
|
28
|
+
this.maximumPendingDeliveryReceipts = maximumPendingDeliveryReceipts;
|
|
29
|
+
}
|
|
30
|
+
async load(streamId) {
|
|
31
|
+
const result = await this.poolQuery(`SELECT revision, sequence, logical_time_high_water_ms, state_digest, state
|
|
32
|
+
FROM ${this.stateTable}
|
|
33
|
+
WHERE tenant_id = $1 AND stream_id = $2`, [this.tenantId, streamId]);
|
|
34
|
+
const row = result.rows[0];
|
|
35
|
+
if (!row)
|
|
36
|
+
return null;
|
|
37
|
+
if (row.state.tenantId !== this.tenantId ||
|
|
38
|
+
row.state.streamId !== streamId ||
|
|
39
|
+
row.state.revision !== Number(row.revision) ||
|
|
40
|
+
row.state.sequence !== Number(row.sequence) ||
|
|
41
|
+
row.state.logicalTimeHighWaterMs !==
|
|
42
|
+
Number(row.logical_time_high_water_ms) ||
|
|
43
|
+
row.state.stateDigest !== row.state_digest)
|
|
44
|
+
throw new Error("collective telemetry PostgreSQL columns diverge from state");
|
|
45
|
+
return row.state;
|
|
46
|
+
}
|
|
47
|
+
async save(state, expectedRevision, expectedStateDigest) {
|
|
48
|
+
state = structuredClone(state);
|
|
49
|
+
if (state.tenantId !== this.tenantId ||
|
|
50
|
+
state.revision !==
|
|
51
|
+
(expectedRevision === null ? 0 : expectedRevision + 1) ||
|
|
52
|
+
(expectedRevision === null) !== (expectedStateDigest === null))
|
|
53
|
+
return false;
|
|
54
|
+
const client = await this.poolConnect();
|
|
55
|
+
try {
|
|
56
|
+
await client.query("BEGIN");
|
|
57
|
+
const result = expectedRevision === null
|
|
58
|
+
? await client.query(`INSERT INTO ${this.stateTable}
|
|
59
|
+
(tenant_id, stream_id, revision, sequence, logical_time_high_water_ms, state_digest, state)
|
|
60
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb) ON CONFLICT DO NOTHING`, [
|
|
61
|
+
state.tenantId,
|
|
62
|
+
state.streamId,
|
|
63
|
+
state.revision,
|
|
64
|
+
state.sequence,
|
|
65
|
+
state.logicalTimeHighWaterMs,
|
|
66
|
+
state.stateDigest,
|
|
67
|
+
JSON.stringify(state),
|
|
68
|
+
])
|
|
69
|
+
: await client.query(`UPDATE ${this.stateTable}
|
|
70
|
+
SET revision = $4, sequence = $5, logical_time_high_water_ms = $6,
|
|
71
|
+
state_digest = $7, state = $8::jsonb, updated_at = transaction_timestamp()
|
|
72
|
+
WHERE tenant_id = $1 AND stream_id = $2 AND revision = $3
|
|
73
|
+
AND state_digest = $9
|
|
74
|
+
AND sequence < $5 AND logical_time_high_water_ms <= $6`, [
|
|
75
|
+
state.tenantId,
|
|
76
|
+
state.streamId,
|
|
77
|
+
expectedRevision,
|
|
78
|
+
state.revision,
|
|
79
|
+
state.sequence,
|
|
80
|
+
state.logicalTimeHighWaterMs,
|
|
81
|
+
state.stateDigest,
|
|
82
|
+
JSON.stringify(state),
|
|
83
|
+
expectedStateDigest,
|
|
84
|
+
]);
|
|
85
|
+
if ((result.rowCount ?? 0) !== 1) {
|
|
86
|
+
await client.query("ROLLBACK");
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const event = state.events.at(-1);
|
|
90
|
+
if (event && event.sequence === state.sequence) {
|
|
91
|
+
await client.query(`INSERT INTO ${this.eventTable}
|
|
92
|
+
(tenant_id, stream_id, sequence, event_id, event_digest, event)
|
|
93
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb) ON CONFLICT DO NOTHING`, [
|
|
94
|
+
state.tenantId,
|
|
95
|
+
state.streamId,
|
|
96
|
+
event.sequence,
|
|
97
|
+
event.eventId,
|
|
98
|
+
event.eventDigest,
|
|
99
|
+
JSON.stringify(event),
|
|
100
|
+
]);
|
|
101
|
+
const retained = await client.query(`SELECT event_digest FROM ${this.eventTable}
|
|
102
|
+
WHERE tenant_id = $1 AND event_id = $2`, [state.tenantId, event.eventId]);
|
|
103
|
+
if (retained.rows[0]?.event_digest !== event.eventDigest)
|
|
104
|
+
throw new Error("collective telemetry event identity conflict");
|
|
105
|
+
}
|
|
106
|
+
await client.query("COMMIT");
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
await client.query("ROLLBACK");
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
client.release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async loadDelivery(streamId, deliveryDigest) {
|
|
118
|
+
const result = await this.poolQuery(`SELECT record_input_digest, event_digest, sequence
|
|
119
|
+
FROM ${this.deliveryReceiptTable}
|
|
120
|
+
WHERE tenant_id = $1 AND stream_id = $2 AND delivery_digest = $3`, [this.tenantId, streamId, deliveryDigest]);
|
|
121
|
+
const row = result.rows[0];
|
|
122
|
+
return row
|
|
123
|
+
? {
|
|
124
|
+
schemaVersion: 1,
|
|
125
|
+
streamId,
|
|
126
|
+
deliveryDigest,
|
|
127
|
+
recordInputDigest: row.record_input_digest,
|
|
128
|
+
eventDigest: row.event_digest,
|
|
129
|
+
sequence: Number(row.sequence),
|
|
130
|
+
}
|
|
131
|
+
: null;
|
|
132
|
+
}
|
|
133
|
+
async commitDelivery(input) {
|
|
134
|
+
input = structuredClone(input);
|
|
135
|
+
if (input.state.tenantId !== this.tenantId ||
|
|
136
|
+
input.state.streamId !== input.receipt.streamId ||
|
|
137
|
+
input.state.revision !== input.expectedRevision + 1)
|
|
138
|
+
return "conflict";
|
|
139
|
+
const event = input.state.events.at(-1);
|
|
140
|
+
if (!event ||
|
|
141
|
+
event.sequence !== input.state.sequence ||
|
|
142
|
+
event.eventDigest !== input.receipt.eventDigest ||
|
|
143
|
+
event.sequence !== input.receipt.sequence)
|
|
144
|
+
throw new TypeError("collective telemetry delivery receipt/event binding is invalid");
|
|
145
|
+
const client = await this.poolConnect();
|
|
146
|
+
try {
|
|
147
|
+
await client.query("BEGIN");
|
|
148
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.tenantId}:collective-telemetry-deliveries`]);
|
|
149
|
+
const existing = await client.query(`SELECT record_input_digest
|
|
150
|
+
FROM ${this.deliveryReceiptTable}
|
|
151
|
+
WHERE tenant_id = $1 AND stream_id = $2 AND delivery_digest = $3`, [this.tenantId, input.receipt.streamId, input.receipt.deliveryDigest]);
|
|
152
|
+
if (existing.rows[0]) {
|
|
153
|
+
if (existing.rows[0].record_input_digest !==
|
|
154
|
+
input.receipt.recordInputDigest)
|
|
155
|
+
throw new Error("collective telemetry delivery digest collision");
|
|
156
|
+
await client.query("COMMIT");
|
|
157
|
+
return "already_recorded";
|
|
158
|
+
}
|
|
159
|
+
const count = await client.query(`SELECT count(*) AS pending FROM ${this.deliveryReceiptTable}
|
|
160
|
+
WHERE tenant_id = $1`, [this.tenantId]);
|
|
161
|
+
if (Number(count.rows[0]?.pending ?? 0) >=
|
|
162
|
+
this.maximumPendingDeliveryReceipts)
|
|
163
|
+
throw new Error("collective telemetry delivery receipt capacity exhausted");
|
|
164
|
+
const saved = await client.query(`UPDATE ${this.stateTable}
|
|
165
|
+
SET revision = $4, sequence = $5,
|
|
166
|
+
logical_time_high_water_ms = $6, state_digest = $7,
|
|
167
|
+
state = $8::jsonb, updated_at = transaction_timestamp()
|
|
168
|
+
WHERE tenant_id = $1 AND stream_id = $2 AND revision = $3
|
|
169
|
+
AND state_digest = $9 AND sequence < $5
|
|
170
|
+
AND logical_time_high_water_ms <= $6`, [
|
|
171
|
+
input.state.tenantId,
|
|
172
|
+
input.state.streamId,
|
|
173
|
+
input.expectedRevision,
|
|
174
|
+
input.state.revision,
|
|
175
|
+
input.state.sequence,
|
|
176
|
+
input.state.logicalTimeHighWaterMs,
|
|
177
|
+
input.state.stateDigest,
|
|
178
|
+
JSON.stringify(input.state),
|
|
179
|
+
input.expectedStateDigest,
|
|
180
|
+
]);
|
|
181
|
+
if ((saved.rowCount ?? 0) !== 1) {
|
|
182
|
+
await client.query("ROLLBACK");
|
|
183
|
+
return "conflict";
|
|
184
|
+
}
|
|
185
|
+
await client.query(`INSERT INTO ${this.eventTable}
|
|
186
|
+
(tenant_id, stream_id, sequence, event_id, event_digest, event)
|
|
187
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb)`, [
|
|
188
|
+
input.state.tenantId,
|
|
189
|
+
input.state.streamId,
|
|
190
|
+
event.sequence,
|
|
191
|
+
event.eventId,
|
|
192
|
+
event.eventDigest,
|
|
193
|
+
JSON.stringify(event),
|
|
194
|
+
]);
|
|
195
|
+
await client.query(`INSERT INTO ${this.deliveryReceiptTable}
|
|
196
|
+
(tenant_id, stream_id, delivery_digest, record_input_digest,
|
|
197
|
+
event_digest, sequence)
|
|
198
|
+
VALUES ($1,$2,$3,$4,$5,$6)`, [
|
|
199
|
+
input.state.tenantId,
|
|
200
|
+
input.state.streamId,
|
|
201
|
+
input.receipt.deliveryDigest,
|
|
202
|
+
input.receipt.recordInputDigest,
|
|
203
|
+
input.receipt.eventDigest,
|
|
204
|
+
input.receipt.sequence,
|
|
205
|
+
]);
|
|
206
|
+
await client.query("COMMIT");
|
|
207
|
+
return "committed";
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
client.release();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async releaseDelivery(streamId, deliveryDigest) {
|
|
218
|
+
await this.poolQuery(`DELETE FROM ${this.deliveryReceiptTable}
|
|
219
|
+
WHERE tenant_id = $1 AND stream_id = $2 AND delivery_digest = $3`, [this.tenantId, streamId, deliveryDigest]);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Bounded monotonic witness for telemetry stream heads. It can be placed
|
|
225
|
+
* on a separately protected PostgreSQL pool/schema from the mutable stream
|
|
226
|
+
* state so an application-state rollback cannot silently roll the witness
|
|
227
|
+
* back with it.
|
|
228
|
+
*/
|
|
229
|
+
export class PostgresCollectiveTelemetryMonotonicAnchorV1 {
|
|
230
|
+
table;
|
|
231
|
+
tenantId;
|
|
232
|
+
poolQuery;
|
|
233
|
+
poolConnect;
|
|
234
|
+
constructor(pool, options) {
|
|
235
|
+
validateTelemetryIdentifier(options.tenantId, "tenantId");
|
|
236
|
+
this.tenantId = options.tenantId;
|
|
237
|
+
this.table = qualifyPostgresName(options.schema ?? defaultPostgresSchema, "collective_telemetry_monotonic_anchors");
|
|
238
|
+
this.poolQuery = pool.query.bind(pool);
|
|
239
|
+
this.poolConnect = pool.connect.bind(pool);
|
|
240
|
+
}
|
|
241
|
+
async load(anchorKey) {
|
|
242
|
+
validateTelemetryIdentifier(anchorKey, "anchorKey");
|
|
243
|
+
const result = await this.poolQuery(`SELECT revision, sequence, state_digest, logical_time_high_water_ms
|
|
244
|
+
FROM ${this.table}
|
|
245
|
+
WHERE tenant_id = $1 AND anchor_key = $2`, [this.tenantId, anchorKey]);
|
|
246
|
+
return result.rows[0] ? telemetryAnchorFromRow(result.rows[0]) : null;
|
|
247
|
+
}
|
|
248
|
+
async save(input) {
|
|
249
|
+
const request = {
|
|
250
|
+
anchorKey: input.anchorKey,
|
|
251
|
+
anchor: Object.freeze({ ...input.anchor }),
|
|
252
|
+
expectedRevision: input.expectedRevision,
|
|
253
|
+
expectedStateDigest: input.expectedStateDigest,
|
|
254
|
+
};
|
|
255
|
+
validateTelemetryIdentifier(request.anchorKey, "anchorKey");
|
|
256
|
+
validateTelemetryAnchor(request.anchor);
|
|
257
|
+
if ((request.expectedRevision === null) !==
|
|
258
|
+
(request.expectedStateDigest === null) ||
|
|
259
|
+
(request.expectedRevision !== null &&
|
|
260
|
+
(!Number.isSafeInteger(request.expectedRevision) ||
|
|
261
|
+
request.expectedRevision < 0)) ||
|
|
262
|
+
(request.expectedStateDigest !== null &&
|
|
263
|
+
!/^sha256:[0-9a-f]{64}$/.test(request.expectedStateDigest)) ||
|
|
264
|
+
request.anchor.revision !== (request.expectedRevision ?? -1) + 1)
|
|
265
|
+
return false;
|
|
266
|
+
const client = await this.poolConnect();
|
|
267
|
+
try {
|
|
268
|
+
await client.query("BEGIN");
|
|
269
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.tenantId}:collective-telemetry-anchor:${request.anchorKey}`]);
|
|
270
|
+
const retained = await client.query(`SELECT revision, sequence, state_digest, logical_time_high_water_ms
|
|
271
|
+
FROM ${this.table}
|
|
272
|
+
WHERE tenant_id = $1 AND anchor_key = $2`, [this.tenantId, request.anchorKey]);
|
|
273
|
+
const current = retained.rows[0]
|
|
274
|
+
? telemetryAnchorFromRow(retained.rows[0])
|
|
275
|
+
: null;
|
|
276
|
+
if ((current?.revision ?? null) !== request.expectedRevision ||
|
|
277
|
+
(current?.stateDigest ?? null) !== request.expectedStateDigest ||
|
|
278
|
+
request.anchor.sequence < (current?.sequence ?? -1) ||
|
|
279
|
+
request.anchor.logicalTimeHighWaterMs <
|
|
280
|
+
(current?.logicalTimeHighWaterMs ?? -1)) {
|
|
281
|
+
await client.query("ROLLBACK");
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const advanced = current === null
|
|
285
|
+
? await client.query(`INSERT INTO ${this.table}
|
|
286
|
+
(tenant_id, anchor_key, revision, sequence, state_digest,
|
|
287
|
+
logical_time_high_water_ms, previous_state_digest)
|
|
288
|
+
VALUES ($1,$2,$3,$4,$5,$6,NULL)
|
|
289
|
+
ON CONFLICT DO NOTHING`, [
|
|
290
|
+
this.tenantId,
|
|
291
|
+
request.anchorKey,
|
|
292
|
+
request.anchor.revision,
|
|
293
|
+
request.anchor.sequence,
|
|
294
|
+
request.anchor.stateDigest,
|
|
295
|
+
request.anchor.logicalTimeHighWaterMs,
|
|
296
|
+
])
|
|
297
|
+
: await client.query(`UPDATE ${this.table}
|
|
298
|
+
SET revision = $4, sequence = $5, state_digest = $6,
|
|
299
|
+
logical_time_high_water_ms = $7,
|
|
300
|
+
previous_state_digest = $8
|
|
301
|
+
WHERE tenant_id = $1 AND anchor_key = $2
|
|
302
|
+
AND revision = $3 AND state_digest = $8`, [
|
|
303
|
+
this.tenantId,
|
|
304
|
+
request.anchorKey,
|
|
305
|
+
request.expectedRevision,
|
|
306
|
+
request.anchor.revision,
|
|
307
|
+
request.anchor.sequence,
|
|
308
|
+
request.anchor.stateDigest,
|
|
309
|
+
request.anchor.logicalTimeHighWaterMs,
|
|
310
|
+
request.expectedStateDigest,
|
|
311
|
+
]);
|
|
312
|
+
if ((advanced.rowCount ?? 0) !== 1) {
|
|
313
|
+
await client.query("ROLLBACK");
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
await client.query("COMMIT");
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
client.release();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function telemetryAnchorFromRow(row) {
|
|
329
|
+
const anchor = Object.freeze({
|
|
330
|
+
revision: Number(row.revision),
|
|
331
|
+
sequence: Number(row.sequence),
|
|
332
|
+
stateDigest: row.state_digest,
|
|
333
|
+
logicalTimeHighWaterMs: Number(row.logical_time_high_water_ms),
|
|
334
|
+
});
|
|
335
|
+
validateTelemetryAnchor(anchor);
|
|
336
|
+
return anchor;
|
|
337
|
+
}
|
|
338
|
+
function validateTelemetryAnchor(anchor) {
|
|
339
|
+
if (!anchor ||
|
|
340
|
+
!Number.isSafeInteger(anchor.revision) ||
|
|
341
|
+
anchor.revision < 0 ||
|
|
342
|
+
!Number.isSafeInteger(anchor.sequence) ||
|
|
343
|
+
anchor.sequence < 0 ||
|
|
344
|
+
anchor.sequence !== anchor.revision ||
|
|
345
|
+
!Number.isSafeInteger(anchor.logicalTimeHighWaterMs) ||
|
|
346
|
+
anchor.logicalTimeHighWaterMs < 0 ||
|
|
347
|
+
!/^sha256:[0-9a-f]{64}$/.test(anchor.stateDigest))
|
|
348
|
+
throw new TypeError("collective telemetry anchor is invalid");
|
|
349
|
+
}
|
|
350
|
+
function validateTelemetryIdentifier(value, label) {
|
|
351
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:@/+-=]{0,255}$/.test(value))
|
|
352
|
+
throw new TypeError(`collective telemetry ${label} is invalid`);
|
|
353
|
+
}
|
|
4
354
|
/** Append-only, redacting PostgreSQL AuditSink. */
|
|
5
355
|
export class PostgresAuditSink {
|
|
6
356
|
pool;
|
|
7
357
|
table;
|
|
8
358
|
constructor(pool, options = {}) {
|
|
9
359
|
this.pool = pool;
|
|
10
|
-
this.table = qualifyPostgresName(options.schema ?? defaultPostgresSchema,
|
|
360
|
+
this.table = qualifyPostgresName(options.schema ?? defaultPostgresSchema, "audit_records");
|
|
11
361
|
}
|
|
12
362
|
async write(record) {
|
|
13
363
|
await this.pool.query(`INSERT INTO ${this.table} (
|
|
@@ -34,7 +384,7 @@ export class PostgresSessionEventSink {
|
|
|
34
384
|
table;
|
|
35
385
|
constructor(pool, options = {}) {
|
|
36
386
|
this.pool = pool;
|
|
37
|
-
this.table = qualifyPostgresName(options.schema ?? defaultPostgresSchema,
|
|
387
|
+
this.table = qualifyPostgresName(options.schema ?? defaultPostgresSchema, "session_events");
|
|
38
388
|
}
|
|
39
389
|
async append(record) {
|
|
40
390
|
const event = redactAuditDetails(record.event);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAOzB,mDAAmD;AACnD,MAAM,OAAO,iBAAiB;IAIT;IAHF,KAAK,CAAS;IAE/B,YACmB,IAAU,EAC3B,UAA+B,EAAE;QADhB,SAAI,GAAJ,IAAI,CAAM;QAG3B,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAC9B,OAAO,CAAC,MAAM,IAAI,qBAAqB,EACvC,eAAe,CAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAmB;QAC7B,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CACnB,eAAe,IAAI,CAAC,KAAK;;;;8CAIe,EACxC;YACE,MAAM,CAAC,QAAQ;YACf,MAAM,CAAC,EAAE;YACT,MAAM,CAAC,OAAO,IAAI,IAAI;YACtB,MAAM,CAAC,SAAS,IAAI,IAAI;YACxB,MAAM,CAAC,MAAM;YACb,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC/B,IAAI,CAAC,SAAS,CACZ,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CACzD;YACD,MAAM,CAAC,SAAS,IAAI,IAAI;YACxB,MAAM,CAAC,SAAS,IAAI,IAAI;YACxB,MAAM,CAAC,SAAS;SACjB,CACF,CAAC;IACJ,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,OAAO,wBAAwB;IAIhB;IAHF,KAAK,CAAS;IAE/B,YACmB,IAAU,EAC3B,UAA+B,EAAE;QADhB,SAAI,GAAJ,IAAI,CAAM;QAG3B,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAC9B,OAAO,CAAC,MAAM,IAAI,qBAAqB,EACvC,gBAAgB,CACjB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAA0B;QACrC,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,KAA8B,CAAC,CAAC;QACxE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CACnB,eAAe,IAAI,CAAC,KAAK;;;oDAGqB,EAC9C;YACE,MAAM,CAAC,QAAQ;YACf,MAAM,CAAC,SAAS;YAChB,MAAM,CAAC,QAAQ;YACf,MAAM,CAAC,OAAO;YACd,MAAM,CAAC,UAAU;YACjB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACtB,CACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,uBAAuB,CACrC,IAAU,EACV,OAA6B;IAE7B,OAAO,IAAI,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,IAAU,EACV,OAA6B;IAE7B,OAAO,IAAI,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/migrations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { PostgresMigrationStatus } from
|
|
2
|
-
import type { Pool } from
|
|
1
|
+
import type { PostgresMigrationStatus } from "@agentplat/postgres";
|
|
2
|
+
import type { Pool } from "pg";
|
|
3
3
|
export declare const auditMigrationDirectory: string;
|
|
4
4
|
export interface AuditPostgresMigrationOptions {
|
|
5
5
|
schema?: string;
|
package/dist/migrations.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAU/B,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAoBD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,uBAAuB,CAAC,CAOlC;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,uBAAuB,CAAC,CAMlC;AAED,wBAAgB,yBAAyB,CACvC,MAAM,SAAwB,EAC9B,OAAO,SAAI,GACV,MAAM,CAER;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,6BAA6B,GAAG;IACvC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAUlC"}
|
package/dist/migrations.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { readFile } from
|
|
2
|
-
import { fileURLToPath } from
|
|
3
|
-
import { defaultPostgresSchema, getPostgresMigrationStatus, postgresRollbackConfirmation, rollbackPostgresMigration, runPostgresMigrations, } from
|
|
4
|
-
const applicationId =
|
|
5
|
-
const
|
|
6
|
-
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { defaultPostgresSchema, getPostgresMigrationStatus, postgresRollbackConfirmation, rollbackPostgresMigration, runPostgresMigrations, } from "@agentplat/postgres";
|
|
4
|
+
const applicationId = "@agentplat/audit-postgres";
|
|
5
|
+
const migrationNames = [
|
|
6
|
+
"001_audit_and_session_events",
|
|
7
|
+
"002_collective_telemetry",
|
|
8
|
+
"003_collective_telemetry_delivery_receipts",
|
|
9
|
+
"004_collective_telemetry_monotonic_anchors",
|
|
10
|
+
];
|
|
11
|
+
export const auditMigrationDirectory = fileURLToPath(new URL("../migrations/", import.meta.url));
|
|
7
12
|
async function migrations() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
name: migrationName,
|
|
16
|
-
up,
|
|
17
|
-
down,
|
|
18
|
-
destructiveDown: true,
|
|
19
|
-
},
|
|
20
|
-
];
|
|
13
|
+
return Promise.all(migrationNames.map(async (name, index) => {
|
|
14
|
+
const [up, down] = await Promise.all([
|
|
15
|
+
readFile(new URL(`../migrations/${name}.up.sql`, import.meta.url), "utf8"),
|
|
16
|
+
readFile(new URL(`../migrations/${name}.down.sql`, import.meta.url), "utf8"),
|
|
17
|
+
]);
|
|
18
|
+
return { version: index + 1, name, up, down, destructiveDown: true };
|
|
19
|
+
}));
|
|
21
20
|
}
|
|
22
21
|
export async function runAuditMigrations(pool, options = {}) {
|
|
23
22
|
return runPostgresMigrations(pool, {
|
|
@@ -34,7 +33,7 @@ export async function getAuditMigrationStatus(pool, options = {}) {
|
|
|
34
33
|
migrations: await migrations(),
|
|
35
34
|
});
|
|
36
35
|
}
|
|
37
|
-
export function auditRollbackConfirmation(schema = defaultPostgresSchema, version =
|
|
36
|
+
export function auditRollbackConfirmation(schema = defaultPostgresSchema, version = 4) {
|
|
38
37
|
return postgresRollbackConfirmation(applicationId, schema, version);
|
|
39
38
|
}
|
|
40
39
|
export async function rollbackAuditMigrations(pool, options) {
|
package/dist/migrations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAO7B,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD,MAAM,
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAO7B,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAClD,MAAM,cAAc,GAAG;IACrB,8BAA8B;IAC9B,0BAA0B;IAC1B,4CAA4C;IAC5C,4CAA4C;CACpC,CAAC;AAEX,MAAM,CAAC,MAAM,uBAAuB,GAAG,aAAa,CAClD,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3C,CAAC;AAOF,KAAK,UAAU,UAAU;IACvB,OAAO,OAAO,CAAC,GAAG,CAChB,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACvC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACnC,QAAQ,CACN,IAAI,GAAG,CAAC,iBAAiB,IAAI,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EACxD,MAAM,CACP;YACD,QAAQ,CACN,IAAI,GAAG,CAAC,iBAAiB,IAAI,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAC1D,MAAM,CACP;SACF,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;IACvE,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAU,EACV,UAAyC,EAAE;IAE3C,OAAO,qBAAqB,CAAC,IAAI,EAAE;QACjC,aAAa;QACb,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,UAAU,EAAE,MAAM,UAAU,EAAE;KAC/B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAAU,EACV,UAAyC,EAAE;IAE3C,OAAO,0BAA0B,CAAC,IAAI,EAAE;QACtC,aAAa;QACb,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,MAAM,UAAU,EAAE;KAC/B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,MAAM,GAAG,qBAAqB,EAC9B,OAAO,GAAG,CAAC;IAEX,OAAO,4BAA4B,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,IAAU,EACV,OAIC;IAED,OAAO,yBAAyB,CAAC,IAAI,EAAE;QACrC,aAAa;QACb,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,MAAM,UAAU,EAAE;QAC9B,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;QACtD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
DROP TRIGGER IF EXISTS collective_telemetry_events_append_only_delete ON __AGENTPLAT_SCHEMA__.collective_telemetry_events;
|
|
2
|
+
DROP TRIGGER IF EXISTS collective_telemetry_events_append_only_update ON __AGENTPLAT_SCHEMA__.collective_telemetry_events;
|
|
3
|
+
DROP TABLE IF EXISTS __AGENTPLAT_SCHEMA__.collective_telemetry_events;
|
|
4
|
+
DROP TABLE IF EXISTS __AGENTPLAT_SCHEMA__.collective_telemetry_states;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
CREATE TABLE __AGENTPLAT_SCHEMA__.collective_telemetry_states (
|
|
2
|
+
tenant_id text NOT NULL,
|
|
3
|
+
stream_id text NOT NULL,
|
|
4
|
+
revision bigint NOT NULL CHECK (revision >= 0),
|
|
5
|
+
sequence bigint NOT NULL CHECK (sequence >= 0),
|
|
6
|
+
logical_time_high_water_ms bigint NOT NULL CHECK (logical_time_high_water_ms >= 0),
|
|
7
|
+
state_digest text NOT NULL CHECK (state_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
8
|
+
state jsonb NOT NULL CHECK (jsonb_typeof(state) = 'object'),
|
|
9
|
+
updated_at timestamptz NOT NULL DEFAULT transaction_timestamp(),
|
|
10
|
+
PRIMARY KEY (tenant_id, stream_id)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
CREATE TABLE __AGENTPLAT_SCHEMA__.collective_telemetry_events (
|
|
14
|
+
tenant_id text NOT NULL,
|
|
15
|
+
stream_id text NOT NULL,
|
|
16
|
+
sequence bigint NOT NULL CHECK (sequence > 0),
|
|
17
|
+
event_id text NOT NULL,
|
|
18
|
+
event_digest text NOT NULL CHECK (event_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
19
|
+
event jsonb NOT NULL CHECK (jsonb_typeof(event) = 'object'),
|
|
20
|
+
PRIMARY KEY (tenant_id, event_id),
|
|
21
|
+
UNIQUE (tenant_id, stream_id, sequence)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX collective_telemetry_states_updated_idx
|
|
25
|
+
ON __AGENTPLAT_SCHEMA__.collective_telemetry_states (tenant_id, updated_at DESC);
|
|
26
|
+
CREATE INDEX collective_telemetry_events_stream_idx
|
|
27
|
+
ON __AGENTPLAT_SCHEMA__.collective_telemetry_events (tenant_id, stream_id, sequence);
|
|
28
|
+
|
|
29
|
+
CREATE TRIGGER collective_telemetry_events_append_only_update
|
|
30
|
+
BEFORE UPDATE ON __AGENTPLAT_SCHEMA__.collective_telemetry_events
|
|
31
|
+
FOR EACH ROW EXECUTE FUNCTION __AGENTPLAT_SCHEMA__.agentplat_prevent_audit_mutation();
|
|
32
|
+
CREATE TRIGGER collective_telemetry_events_append_only_delete
|
|
33
|
+
BEFORE DELETE ON __AGENTPLAT_SCHEMA__.collective_telemetry_events
|
|
34
|
+
FOR EACH ROW EXECUTE FUNCTION __AGENTPLAT_SCHEMA__.agentplat_prevent_audit_mutation();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DROP TABLE IF EXISTS __AGENTPLAT_SCHEMA__.collective_telemetry_delivery_receipts;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CREATE TABLE __AGENTPLAT_SCHEMA__.collective_telemetry_delivery_receipts (
|
|
2
|
+
tenant_id text NOT NULL,
|
|
3
|
+
stream_id text NOT NULL,
|
|
4
|
+
delivery_digest text NOT NULL CHECK (delivery_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
5
|
+
record_input_digest text NOT NULL CHECK (record_input_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
6
|
+
event_digest text NOT NULL CHECK (event_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
7
|
+
sequence bigint NOT NULL CHECK (sequence > 0),
|
|
8
|
+
created_at timestamptz NOT NULL DEFAULT transaction_timestamp(),
|
|
9
|
+
PRIMARY KEY (tenant_id, stream_id, delivery_digest),
|
|
10
|
+
FOREIGN KEY (tenant_id, stream_id)
|
|
11
|
+
REFERENCES __AGENTPLAT_SCHEMA__.collective_telemetry_states (tenant_id, stream_id)
|
|
12
|
+
ON DELETE RESTRICT,
|
|
13
|
+
FOREIGN KEY (tenant_id, stream_id, sequence)
|
|
14
|
+
REFERENCES __AGENTPLAT_SCHEMA__.collective_telemetry_events
|
|
15
|
+
(tenant_id, stream_id, sequence)
|
|
16
|
+
ON DELETE RESTRICT
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX collective_telemetry_delivery_receipts_capacity_idx
|
|
20
|
+
ON __AGENTPLAT_SCHEMA__.collective_telemetry_delivery_receipts
|
|
21
|
+
(tenant_id, created_at ASC);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
CREATE TABLE __AGENTPLAT_SCHEMA__.collective_telemetry_monotonic_anchors (
|
|
2
|
+
tenant_id text NOT NULL,
|
|
3
|
+
anchor_key text NOT NULL,
|
|
4
|
+
revision bigint NOT NULL CHECK (revision >= 0),
|
|
5
|
+
sequence bigint NOT NULL CHECK (sequence >= 0),
|
|
6
|
+
state_digest text NOT NULL CHECK (state_digest ~ '^sha256:[0-9a-f]{64}$'),
|
|
7
|
+
logical_time_high_water_ms bigint NOT NULL CHECK (logical_time_high_water_ms >= 0),
|
|
8
|
+
previous_state_digest text NULL CHECK (
|
|
9
|
+
previous_state_digest IS NULL OR
|
|
10
|
+
previous_state_digest ~ '^sha256:[0-9a-f]{64}$'
|
|
11
|
+
),
|
|
12
|
+
created_at timestamptz NOT NULL DEFAULT transaction_timestamp(),
|
|
13
|
+
updated_at timestamptz NOT NULL DEFAULT transaction_timestamp(),
|
|
14
|
+
PRIMARY KEY (tenant_id, anchor_key),
|
|
15
|
+
CHECK ((revision = 0) = (previous_state_digest IS NULL)),
|
|
16
|
+
CHECK (revision = sequence)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE FUNCTION __AGENTPLAT_SCHEMA__.agentplat_enforce_telemetry_anchor_advance()
|
|
20
|
+
RETURNS trigger
|
|
21
|
+
LANGUAGE plpgsql
|
|
22
|
+
AS $agentplat$
|
|
23
|
+
BEGIN
|
|
24
|
+
IF NEW.tenant_id <> OLD.tenant_id OR
|
|
25
|
+
NEW.anchor_key <> OLD.anchor_key OR
|
|
26
|
+
NEW.revision <> OLD.revision + 1 OR
|
|
27
|
+
NEW.sequence <> OLD.sequence + 1 OR
|
|
28
|
+
NEW.previous_state_digest <> OLD.state_digest OR
|
|
29
|
+
NEW.created_at <> OLD.created_at OR
|
|
30
|
+
NEW.logical_time_high_water_ms < OLD.logical_time_high_water_ms THEN
|
|
31
|
+
RAISE EXCEPTION 'collective telemetry monotonic anchor must advance exactly once';
|
|
32
|
+
END IF;
|
|
33
|
+
NEW.updated_at := transaction_timestamp();
|
|
34
|
+
RETURN NEW;
|
|
35
|
+
END
|
|
36
|
+
$agentplat$;
|
|
37
|
+
|
|
38
|
+
CREATE TRIGGER collective_telemetry_monotonic_anchors_monotonic_update
|
|
39
|
+
BEFORE UPDATE ON __AGENTPLAT_SCHEMA__.collective_telemetry_monotonic_anchors
|
|
40
|
+
FOR EACH ROW EXECUTE FUNCTION __AGENTPLAT_SCHEMA__.agentplat_enforce_telemetry_anchor_advance();
|
|
41
|
+
CREATE TRIGGER collective_telemetry_monotonic_anchors_append_only_delete
|
|
42
|
+
BEFORE DELETE ON __AGENTPLAT_SCHEMA__.collective_telemetry_monotonic_anchors
|
|
43
|
+
FOR EACH ROW EXECUTE FUNCTION __AGENTPLAT_SCHEMA__.agentplat_prevent_audit_mutation();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentplat/audit-postgres",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.5",
|
|
4
4
|
"description": "PostgreSQL audit and session event sinks for AgentPlat without Agent Rooms.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@types/pg": "^8.11.10",
|
|
23
23
|
"pg": "^8.13.1",
|
|
24
|
-
"@agentplat/audit": "^0.3.0-beta.
|
|
25
|
-
"@agentplat/
|
|
26
|
-
"@agentplat/postgres": "^0.3.0-beta.
|
|
27
|
-
"@agentplat/
|
|
24
|
+
"@agentplat/audit": "^0.3.0-beta.5",
|
|
25
|
+
"@agentplat/core": "^0.3.0-beta.5",
|
|
26
|
+
"@agentplat/postgres": "^0.3.0-beta.5",
|
|
27
|
+
"@agentplat/sessions": "^0.3.0-beta.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^20.10.0"
|