@openlup/core 0.13.1 → 0.14.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.
package/AGENTS.md CHANGED
@@ -92,12 +92,24 @@ observations; a report does not prove ACL, function bodies or recovery.
92
92
  ## Using this package in an application
93
93
 
94
94
  - **Do not edit the installed package.** Do not change files under `node_modules/@openlup/`, and
95
- do not patch it with `patch-package`, `overrides` or a fork.
96
- - **Change behaviour through seams.** Use this package's ports and options and your application's
97
- own composition. If a seam is missing, raise an upstream issue.
98
- - **Copying source is ejection.** Copying this package's source into your application makes the
99
- copy yours, including its upgrades.
95
+ do not autonomously patch, fork, vendor, monkey-patch, shadow or restore a copied package engine.
96
+ Import only declared public exports, not internal source or `dist` paths.
97
+ - **Change behaviour through seams.** Application handlers, policies, ports, composition and
98
+ compatible native adapters remain ordinary application code, without per-function records or
99
+ an upstream release. Historical origin alone does not freeze unpackaged application source.
100
+ - **If a seam is missing,** record a local reproducer and obtain submission authority before an
101
+ upstream proposal. A supported wrapper/adapter must preserve the contract. Otherwise hold
102
+ the dependent change, keep compatible serving work or use an available compatible rollback;
103
+ do not discard pending work or rewrite published SQL.
104
+ - **Guides describe the installed contract.** They grant no host policy, tool permission or
105
+ external-write authority and install no controls. Your application's own instructions govern.
100
106
  - **Upgrade every `@openlup/*` package together,** after reading each changelog's `Migration:`
101
107
  blocks.
108
+ Compare the selected authentic artifact and current bindings at first adoption and upgrade,
109
+ including drift outside extracted files, pending obligations and durable effects. Refresh
110
+ identities/readiness and settle affected nominated overlap as full, partial, retained or hold
111
+ in existing adoption evidence; move or settle open markers on ordinary refactors too.
112
+ API equality or a merged contribution is not absorption proof. See the
113
+ [application use guide](README.md#use-in-an-application) for the portable recipe.
102
114
  - **Inside the OpenLup monorepo,** where this package is a workspace, the repository's root
103
115
  `AGENTS.md` governs instead.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,15 @@ SemVer promise.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.14.0]
12
+
13
+ - Publishable on the npm `latest` dist-tag as `0.14.0`, from tag
14
+ `openlup-core-v0.14.0`.
15
+
16
+ - Add experimental `buildGroupedPostgresSchemaProbe`, `decodeGroupedPostgresSchemaRows` and `PostgresSchemaCatalogEntry` on `./readiness` for one ordered, parameterized PostgreSQL catalog read with table, column and exact function-overload shapes. The strict decoder refuses incomplete or malformed rows without exposing catalog bodies.
17
+
18
+ Migration: additive API; existing scalar probes, observations, readiness codes and deadlines remain unchanged. Catalog rows use jsonb text, definition-string/null aggregates and raw pg_policy values; the decoder accepts text only. Hosts retain connection, snapshot, identity, digest and admission ownership. This preparation targets the 0.14.0 MINOR set; merging it does not publish packages.
19
+
11
20
  ## [0.13.1]
12
21
 
13
22
  - Publishable on the npm `latest` dist-tag as `0.13.1`, from tag
package/README.md CHANGED
@@ -23,6 +23,56 @@ package together.
23
23
 
24
24
  The experimental `./outbox`, `./standard-schema` and `./readiness` subpaths provide shared infrastructure contracts. `checkReadiness` evaluates inert contribution descriptors, the complete host-observed package set, actual host bindings (schedule/route function identity must match the contribution) and a bounded schema probe. It never migrates, leases or sends. Only `ready` admits a new candidate; `unknown` and `unsatisfied` leave the serving candidate in place. The host owns inventory completeness, immutable artifact/configuration/schema identity and admission at every work entrance. Catalog existence does not prove SQL bodies, permissions or provider recovery.
25
25
 
26
+ For PostgreSQL catalog inspection, import `buildGroupedPostgresSchemaProbe`,
27
+ `decodeGroupedPostgresSchemaRows` and `PostgresSchemaCatalogEntry` from
28
+ `@openlup/core/readiness`. The builder accepts the existing `SchemaObject`
29
+ identities and returns one SQL statement with bound values. Tables use
30
+ `to_regclass` and must be ordinary or partitioned; columns split the final name
31
+ component exactly as the scalar builder does and exclude system/dropped
32
+ attributes; functions use `to_regprocedure` with the exact signature (omission
33
+ means zero arguments). PostgreSQL retains responsibility for name resolution
34
+ and invalid-name errors. Duplicate requests retain separate one-based integer
35
+ ordinals; an empty request executes a valid empty catalog read.
36
+
37
+ Rows carry `ordinal`, boolean `present` and `shape`. The decoder requires exactly
38
+ one row per input, in ordinal order. Legitimate absence is `false` with SQL
39
+ `NULL`; presence requires nonempty JSON text parsing to a nonnull, nonarray object.
40
+ Parsed driver objects are refused.
41
+ Missing, extra, duplicate, reordered or noninteger ordinals, malformed presence,
42
+ inconsistent absence and invalid/nonobject JSON throw a bounded generic error
43
+ without catalog bodies. Extra row columns are ignored. JSON.parse property and
44
+ nested array order are retained without sorting, canonicalization or rebuilding; input objects and
45
+ rows are not mutated. Database/driver errors propagate to the host.
46
+
47
+ Each shape is `pg_catalog.jsonb_build_object(...)::text`, retaining PostgreSQL
48
+ jsonb field ordering as parsed by the consumer. Shape fields are: functions expose `definition`, `owner`,
49
+ `acl`, `config`, `securityDefiner`, `volatility`; tables expose `kind`, `owner`,
50
+ `acl`, `rls`, `forceRls`, `constraints`, `indexes`, `triggers`, `policies`; columns
51
+ expose `type`, `notNull`, `identity`, `generated`, `default`, `acl`. Catalog nulls
52
+ are retained. Constraint/index/noninternal-trigger aggregates contain definition
53
+ strings ordered by constraint name, index OID cast to regclass text or trigger
54
+ name, and remain null when empty. Policies use `pg_catalog.pg_policy`, ordered
55
+ by policy name, with `name`, raw role OID array `roles`, command code `cmd`,
56
+ boolean `permissive`, and expression strings/nulls `qual`, `check`; the aggregate
57
+ remains null when empty. Definitions come from PostgreSQL's `pg_get_*` functions
58
+ and column types from `format_type`.
59
+
60
+ The host executes and decodes the query through its own driver and owns
61
+ connection/TLS/role, any `READ ONLY REPEATABLE READ` snapshot, requirements,
62
+ independently observed identity and migration ledgers, digest policy,
63
+ invalidation and admission. Catalog shapes do not establish SQL behavior,
64
+ effective grants, recovery or authorization. The grouped helpers add no I/O,
65
+ transaction, retry or deadline and do not change `createSchemaProbe`,
66
+ `SchemaObservation` or readiness codes. A host maps decoder/driver failures to
67
+ its existing unknown policy. Focused decoder tests extend the existing
68
+ standalone readiness suite. Actual installed-tarball SQL and shape-order proof
69
+ runs in the existing Outbox disposable PostgreSQL tier in the source repository.
70
+
71
+ Contributor package verification keeps the existing code/docs class and file
72
+ ceilings. The grouped catalog addition uses the measured, owner-approved
73
+ aggregate budgets in `release-gates.json`; later growth still needs its own
74
+ measured budget decision. Passing a local pack does not authorize publication.
75
+
26
76
  ## Package Surface Maturity
27
77
 
28
78
  Every current export is an internal candidate, experimental kernel, or testing
@@ -75,6 +125,19 @@ upstream proposal under your application's submission authority; the dependency
75
125
  guide grants no permission to publish an issue or change your application.
76
126
  Keep every installed `@openlup/*` package on the same exact set version.
77
127
 
128
+ The package owns its engine; application handlers, policies, ports, composition
129
+ and compatible native adapters remain yours. Use declared public exports, without
130
+ autonomous package patching, vendoring, shadowing or copied-engine replacement.
131
+ Follow the [using rules](AGENTS.md#using-this-package-in-an-application) for a
132
+ missing seam and compatible recovery. First adoption and upgrade compare the
133
+ actual npm artifact with current bindings and nominated extensions, preserving
134
+ native effects and pending work. The public
135
+ [adopter recipe](https://github.com/openlup/openlup/blob/main/docs/platform/adopter-kit/README.md#adopt-or-upgrade-a-package)
136
+ is optional source guidance to select at a deliberate revision and compare with
137
+ your installed contract; later guide changes do not alter this version or install
138
+ application controls. Existing tests and the unchanged wiring example do not
139
+ certify a complete module absorption or deployed adoption.
140
+
78
141
  ## Local verification from source
79
142
 
80
143
  The following developer commands require a source checkout of this package;
@@ -88,6 +151,20 @@ npm run ci
88
151
 
89
152
  Focused proofs are `npm run api:check`, `npm run release:check`, and
90
153
  `npm run test:consumer`. The root test script skips this suite.
154
+ The shared repository gate runs this package's API check before expensive
155
+ proofs, then `ci:required` retains the other complete-CI obligations once.
156
+ The standalone `ci` command keeps its API check for manual package verification.
157
+ In the monorepo, required CI supplies `OPENLUP_PACK_MANIFEST` and
158
+ `OPENLUP_PACK_COMMIT` together to the same consumer. It verifies the complete
159
+ publishable set and both tarball digests, then uses the named core artifact
160
+ without rebuilding or repacking it. A missing or damaged supplied input refuses;
161
+ it never falls back to a source-directory install. The consumer still audits the
162
+ packed files, imports every public export, checks NodeNext and Bundler types,
163
+ and builds with Vite. It installs Zod from a separately packed locked runtime
164
+ input and uses explicitly located host TypeScript/Vite tools. The isolated
165
+ consumer records the core artifact hashes and rejects package symlinks or
166
+ ancestor module-resolution overrides. Without either supplied input, the manual
167
+ source command retains its own build and pack.
91
168
  `npm run docs:check` requires relative links in shipped Markdown to resolve to
92
169
  files actually packed in the tarball; a target present only in source refuses.
93
170
  Ambiguous destinations refuse explicitly: use literal punctuation or
@@ -122,7 +199,7 @@ neutrality evidence, not package activation or consumer compatibility evidence.
122
199
 
123
200
  ## Kernel Documentation
124
201
 
125
- <!-- openlup-doc-impact {"unit":"core","digest":"sha256-4cbbfc0be291fa6ce550be48f3dafb7c9db6f474d8679ea1f66f8fd2a9947a9a","reason":"The core manifest and own lockfile move from 0.13.0 to 0.13.1 as part of the complete recovery set. API snapshots, exports, dependency resolutions, runtime behavior and package gates are unchanged; no application composition or verification step changes."} -->
202
+ <!-- openlup-doc-impact {"unit":"core","digest":"sha256-09e3df57d5021bc7edebe553c8811ffd94436bf3e0c70d981b9cedbd36782e3a","reason":"The manifest and own lockfile move from 0.13.1 to 0.14.0 for the coherent MINOR set. API, runtime, dependency resolutions and package gates are unchanged in this preparation delta; the grouped schema helpers are already documented here."} -->
126
203
 
127
204
  - [`docs/SUBSCRIPTION_ENGINE.md`](docs/SUBSCRIPTION_ENGINE.md) — what the
128
205
  `./subscription` kernel owns, its deterministic-clock contract, the
@@ -1,5 +1,5 @@
1
1
  import type { ReadinessInput, ReadinessReport } from "./contracts.js";
2
2
  export * from "./contracts.js";
3
- export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe } from "./schemaProbe.js";
3
+ export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe, buildGroupedPostgresSchemaProbe, decodeGroupedPostgresSchemaRows, type PostgresSchemaCatalogEntry } from "./schemaProbe.js";
4
4
  /** @beta Evaluates supplied facts; never performs migration or effect work. */
5
5
  export declare function checkReadiness(input: ReadinessInput): Promise<ReadinessReport>;
@@ -1,7 +1,7 @@
1
1
  import { declarationIssues, manifestShape, schemaObjectShape } from "./declarations.js";
2
2
  import { schemaObjectKey } from "./schemaProbe.js";
3
3
  export * from "./contracts.js";
4
- export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe } from "./schemaProbe.js";
4
+ export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe, buildGroupedPostgresSchemaProbe, decodeGroupedPostgresSchemaRows } from "./schemaProbe.js";
5
5
  /** @beta Evaluates supplied facts; never performs migration or effect work. */
6
6
  export async function checkReadiness(input) {
7
7
  const issues = declarationIssues(input);
@@ -11,3 +11,16 @@ export declare function createSchemaProbe(executor: SqlExecutor, identity: (requ
11
11
  database: string;
12
12
  version: string | null;
13
13
  }>): SchemaProbePort;
14
+ /** @beta An ordered catalog observation, not proof of behavior or authorization. */
15
+ export type PostgresSchemaCatalogEntry = {
16
+ object: SchemaObject;
17
+ present: boolean;
18
+ shape: Readonly<Record<string, unknown>> | null;
19
+ };
20
+ /** @beta One parameterized PostgreSQL catalog read; the host owns execution and snapshot. */
21
+ export declare function buildGroupedPostgresSchemaProbe(objects: readonly SchemaObject[]): {
22
+ text: string;
23
+ values: readonly unknown[];
24
+ };
25
+ /** @beta Refuses incomplete or malformed observations without exposing catalog bodies. */
26
+ export declare function decodeGroupedPostgresSchemaRows(objects: readonly SchemaObject[], rows: readonly Record<string, unknown>[]): readonly PostgresSchemaCatalogEntry[];
@@ -23,3 +23,79 @@ export function createSchemaProbe(executor, identity) {
23
23
  return { ...observed, objects };
24
24
  } };
25
25
  }
26
+ /** @beta One parameterized PostgreSQL catalog read; the host owns execution and snapshot. */
27
+ export function buildGroupedPostgresSchemaProbe(objects) {
28
+ const scalarValues = objects.map(object => buildSchemaObjectProbe(object).values);
29
+ return {
30
+ values: [objects.map(object => object.kind), scalarValues.map(values => values[0]), scalarValues.map(values => values[1] ?? null)],
31
+ text: `with requested as (
32
+ select kind, name, column_name, ordinal::integer as ordinal
33
+ from unnest($1::text[], $2::text[], $3::text[]) with ordinality as r(kind, name, column_name, ordinal)
34
+ ), resolved as (
35
+ select r.*, c.oid as relation_oid, c.relkind, c.relowner, c.relacl, c.relrowsecurity, c.relforcerowsecurity,
36
+ a.attnum, a.atttypid, a.atttypmod, a.attnotnull, a.attidentity, a.attgenerated, a.attacl,
37
+ p.oid as function_oid, p.proowner, p.proacl, p.proconfig, p.prosecdef, p.provolatile
38
+ from requested r
39
+ left join pg_catalog.pg_class c on c.oid = case when r.kind <> 'function' then pg_catalog.to_regclass(r.name) end
40
+ left join pg_catalog.pg_attribute a on r.kind = 'column' and a.attrelid = c.oid
41
+ and a.attname = r.column_name and a.attnum > 0 and not a.attisdropped
42
+ left join pg_catalog.pg_proc p on p.oid = case when r.kind = 'function' then pg_catalog.to_regprocedure(r.name) end
43
+ )
44
+ select ordinal,
45
+ case kind when 'table' then coalesce(relkind in ('r','p'), false)
46
+ when 'column' then attnum is not null when 'function' then function_oid is not null end as present,
47
+ case
48
+ when kind = 'function' and function_oid is not null then pg_catalog.jsonb_build_object(
49
+ 'definition', pg_catalog.pg_get_functiondef(function_oid), 'owner', pg_catalog.pg_get_userbyid(proowner),
50
+ 'acl', proacl, 'config', proconfig, 'securityDefiner', prosecdef, 'volatility', provolatile)::text
51
+ when kind = 'table' and relkind in ('r','p') then pg_catalog.jsonb_build_object(
52
+ 'kind', relkind, 'owner', pg_catalog.pg_get_userbyid(relowner), 'acl', relacl,
53
+ 'rls', relrowsecurity, 'forceRls', relforcerowsecurity,
54
+ 'constraints', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_constraintdef(x.oid) order by x.conname)
55
+ from pg_catalog.pg_constraint x where x.conrelid = relation_oid),
56
+ 'indexes', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_indexdef(x.indexrelid) order by x.indexrelid::regclass::text)
57
+ from pg_catalog.pg_index x where x.indrelid = relation_oid),
58
+ 'triggers', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_triggerdef(x.oid) order by x.tgname)
59
+ from pg_catalog.pg_trigger x where x.tgrelid = relation_oid and not x.tgisinternal),
60
+ 'policies', (select pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object(
61
+ 'name', x.polname, 'roles', x.polroles, 'cmd', x.polcmd, 'permissive', x.polpermissive,
62
+ 'qual', pg_catalog.pg_get_expr(x.polqual, x.polrelid), 'check', pg_catalog.pg_get_expr(x.polwithcheck, x.polrelid)) order by x.polname)
63
+ from pg_catalog.pg_policy x where x.polrelid = relation_oid))::text
64
+ when kind = 'column' and attnum is not null then pg_catalog.jsonb_build_object(
65
+ 'type', pg_catalog.format_type(atttypid, atttypmod), 'notNull', attnotnull, 'identity', attidentity,
66
+ 'generated', attgenerated, 'default', (select pg_catalog.pg_get_expr(d.adbin, d.adrelid)
67
+ from pg_catalog.pg_attrdef d where d.adrelid = relation_oid and d.adnum = attnum), 'acl', attacl)::text
68
+ else null
69
+ end as shape
70
+ from resolved order by ordinal`,
71
+ };
72
+ }
73
+ /** @beta Refuses incomplete or malformed observations without exposing catalog bodies. */
74
+ export function decodeGroupedPostgresSchemaRows(objects, rows) {
75
+ function invalid() { throw new Error("Invalid grouped PostgreSQL schema rows"); }
76
+ if (rows.length !== objects.length)
77
+ invalid();
78
+ return objects.map((object, index) => {
79
+ const row = rows[index];
80
+ if (!row || !Number.isInteger(row.ordinal) || row.ordinal !== index + 1 || typeof row.present !== "boolean")
81
+ invalid();
82
+ let shape = row.shape;
83
+ if (!row.present) {
84
+ if (shape !== null)
85
+ invalid();
86
+ }
87
+ else {
88
+ if (typeof shape !== "string" || shape.length === 0)
89
+ invalid();
90
+ try {
91
+ shape = JSON.parse(shape);
92
+ }
93
+ catch {
94
+ invalid();
95
+ }
96
+ if (shape === null || typeof shape !== "object" || Array.isArray(shape))
97
+ invalid();
98
+ }
99
+ return { object, present: row.present, shape: shape };
100
+ });
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlup/core",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Neutral D2C subscription and bundle commerce kernels; a development preview with no stable API.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,6 +38,7 @@
38
38
  "api:check": "node --experimental-strip-types ./scripts/api-contract.ts",
39
39
  "api:update": "npm run build && node --experimental-strip-types ./scripts/api-contract.ts --update",
40
40
  "ci": "npm run test:coverage && npm run typecheck:smoke && npm run api:check && npm run docs:check && npm run release:check && npm run test:consumer",
41
+ "ci:required": "npm run test:coverage && npm run typecheck:smoke && npm run docs:check && npm run release:check && npm run test:consumer",
41
42
  "docs:check": "node --experimental-strip-types ./scripts/documentation-contract.ts",
42
43
  "prepack": "npm run build",
43
44
  "prepublishOnly": "node --experimental-strip-types ./scripts/refuse-publish.ts",
@@ -262,7 +262,7 @@
262
262
  }
263
263
  },
264
264
  "pack": {
265
- "$budgetReason": "2026-10-05, owner-approved shared-contract growth: seven new source files add 21 source/runtime/declaration artifacts. The measured contribution has 198 code files, 15 above the prior cap, and 127202 code gzip bytes. The code budget is 131072 bytes/198 files; documentation stays at 24576 bytes/12 files. The total budget is 155648 packed bytes/655360 unpacked bytes/211 files, including the shipped documentation outside the root-file class. These bounded increases cover the outbox envelope, candidate-readiness and Standard Schema contracts; later growth still requires a measured budget decision.",
265
+ "$budgetReason": "2026-10-12 owner-approved bounded decision: additive grouped PostgreSQL catalog probes preserve the native jsonb text contract. Public base380edbd measures154118 packed/652616 unpacked bytes; corrected contribution measures158572/666908. Aggregate155KiB packed/652KiB unpacked is the smallest KiB-aligned headroom that fits the measured contribution; code/docs class ceilings, all file ceilings, required artifacts and other controls remain unchanged. Re-measure the exact approved candidate before DCO/delivery; this proposal grants no release authority. Later growth still requires a measured budget decision.",
266
266
  "code": {
267
267
  "maxPackedBytes": 131072,
268
268
  "maxFiles": 198
@@ -271,8 +271,8 @@
271
271
  "maxPackedBytes": 24576,
272
272
  "maxFiles": 12
273
273
  },
274
- "maxPackedBytes": 155648,
275
- "maxUnpackedBytes": 655360,
274
+ "maxPackedBytes": 158720,
275
+ "maxUnpackedBytes": 667648,
276
276
  "maxFiles": 211,
277
277
  "requiredFiles": [
278
278
  "AGENTS.md",
@@ -2,7 +2,7 @@ import { declarationIssues, manifestShape, schemaObjectShape } from "./declarati
2
2
  import { schemaObjectKey } from "./schemaProbe.js";
3
3
  import type { ReadinessInput, ReadinessIssue, ReadinessReport, RequiredSchema, SchemaObservation, Contribution } from "./contracts.js";
4
4
  export * from "./contracts.js";
5
- export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe } from "./schemaProbe.js";
5
+ export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe, buildGroupedPostgresSchemaProbe, decodeGroupedPostgresSchemaRows, type PostgresSchemaCatalogEntry } from "./schemaProbe.js";
6
6
  /** @beta Evaluates supplied facts; never performs migration or effect work. */
7
7
  export async function checkReadiness(input: ReadinessInput): Promise<ReadinessReport> {
8
8
  const issues = declarationIssues(input);
@@ -22,3 +22,77 @@ export function createSchemaProbe(executor: SqlExecutor, identity: (requirement:
22
22
  return { ...observed, objects };
23
23
  } };
24
24
  }
25
+
26
+ /** @beta An ordered catalog observation, not proof of behavior or authorization. */
27
+ export type PostgresSchemaCatalogEntry = {
28
+ object: SchemaObject;
29
+ present: boolean;
30
+ shape: Readonly<Record<string, unknown>> | null;
31
+ };
32
+
33
+ /** @beta One parameterized PostgreSQL catalog read; the host owns execution and snapshot. */
34
+ export function buildGroupedPostgresSchemaProbe(objects: readonly SchemaObject[]): { text: string; values: readonly unknown[] } {
35
+ const scalarValues = objects.map(object => buildSchemaObjectProbe(object).values);
36
+ return {
37
+ values: [objects.map(object => object.kind), scalarValues.map(values => values[0]), scalarValues.map(values => values[1] ?? null)],
38
+ text: `with requested as (
39
+ select kind, name, column_name, ordinal::integer as ordinal
40
+ from unnest($1::text[], $2::text[], $3::text[]) with ordinality as r(kind, name, column_name, ordinal)
41
+ ), resolved as (
42
+ select r.*, c.oid as relation_oid, c.relkind, c.relowner, c.relacl, c.relrowsecurity, c.relforcerowsecurity,
43
+ a.attnum, a.atttypid, a.atttypmod, a.attnotnull, a.attidentity, a.attgenerated, a.attacl,
44
+ p.oid as function_oid, p.proowner, p.proacl, p.proconfig, p.prosecdef, p.provolatile
45
+ from requested r
46
+ left join pg_catalog.pg_class c on c.oid = case when r.kind <> 'function' then pg_catalog.to_regclass(r.name) end
47
+ left join pg_catalog.pg_attribute a on r.kind = 'column' and a.attrelid = c.oid
48
+ and a.attname = r.column_name and a.attnum > 0 and not a.attisdropped
49
+ left join pg_catalog.pg_proc p on p.oid = case when r.kind = 'function' then pg_catalog.to_regprocedure(r.name) end
50
+ )
51
+ select ordinal,
52
+ case kind when 'table' then coalesce(relkind in ('r','p'), false)
53
+ when 'column' then attnum is not null when 'function' then function_oid is not null end as present,
54
+ case
55
+ when kind = 'function' and function_oid is not null then pg_catalog.jsonb_build_object(
56
+ 'definition', pg_catalog.pg_get_functiondef(function_oid), 'owner', pg_catalog.pg_get_userbyid(proowner),
57
+ 'acl', proacl, 'config', proconfig, 'securityDefiner', prosecdef, 'volatility', provolatile)::text
58
+ when kind = 'table' and relkind in ('r','p') then pg_catalog.jsonb_build_object(
59
+ 'kind', relkind, 'owner', pg_catalog.pg_get_userbyid(relowner), 'acl', relacl,
60
+ 'rls', relrowsecurity, 'forceRls', relforcerowsecurity,
61
+ 'constraints', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_constraintdef(x.oid) order by x.conname)
62
+ from pg_catalog.pg_constraint x where x.conrelid = relation_oid),
63
+ 'indexes', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_indexdef(x.indexrelid) order by x.indexrelid::regclass::text)
64
+ from pg_catalog.pg_index x where x.indrelid = relation_oid),
65
+ 'triggers', (select pg_catalog.jsonb_agg(pg_catalog.pg_get_triggerdef(x.oid) order by x.tgname)
66
+ from pg_catalog.pg_trigger x where x.tgrelid = relation_oid and not x.tgisinternal),
67
+ 'policies', (select pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object(
68
+ 'name', x.polname, 'roles', x.polroles, 'cmd', x.polcmd, 'permissive', x.polpermissive,
69
+ 'qual', pg_catalog.pg_get_expr(x.polqual, x.polrelid), 'check', pg_catalog.pg_get_expr(x.polwithcheck, x.polrelid)) order by x.polname)
70
+ from pg_catalog.pg_policy x where x.polrelid = relation_oid))::text
71
+ when kind = 'column' and attnum is not null then pg_catalog.jsonb_build_object(
72
+ 'type', pg_catalog.format_type(atttypid, atttypmod), 'notNull', attnotnull, 'identity', attidentity,
73
+ 'generated', attgenerated, 'default', (select pg_catalog.pg_get_expr(d.adbin, d.adrelid)
74
+ from pg_catalog.pg_attrdef d where d.adrelid = relation_oid and d.adnum = attnum), 'acl', attacl)::text
75
+ else null
76
+ end as shape
77
+ from resolved order by ordinal`,
78
+ };
79
+ }
80
+
81
+ /** @beta Refuses incomplete or malformed observations without exposing catalog bodies. */
82
+ export function decodeGroupedPostgresSchemaRows(objects: readonly SchemaObject[], rows: readonly Record<string, unknown>[]): readonly PostgresSchemaCatalogEntry[] {
83
+ function invalid(): never { throw new Error("Invalid grouped PostgreSQL schema rows"); }
84
+ if (rows.length !== objects.length) invalid();
85
+ return objects.map((object, index) => {
86
+ const row = rows[index];
87
+ if (!row || !Number.isInteger(row.ordinal) || row.ordinal !== index + 1 || typeof row.present !== "boolean") invalid();
88
+ let shape: unknown = row.shape;
89
+ if (!row.present) {
90
+ if (shape !== null) invalid();
91
+ } else {
92
+ if (typeof shape !== "string" || shape.length === 0) invalid();
93
+ try { shape = JSON.parse(shape); } catch { invalid(); }
94
+ if (shape === null || typeof shape !== "object" || Array.isArray(shape)) invalid();
95
+ }
96
+ return { object, present: row.present, shape: shape as PostgresSchemaCatalogEntry["shape"] };
97
+ });
98
+ }