@openlup/core 0.13.0 → 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 +17 -5
- package/CHANGELOG.md +16 -0
- package/README.md +78 -1
- package/dist/readiness/index.d.ts +1 -1
- package/dist/readiness/index.js +1 -1
- package/dist/readiness/schemaProbe.d.ts +13 -0
- package/dist/readiness/schemaProbe.js +76 -0
- package/package.json +2 -1
- package/release-gates.json +3 -3
- package/src/readiness/index.ts +1 -1
- package/src/readiness/schemaProbe.ts +74 -0
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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,22 @@ 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
|
+
|
|
20
|
+
## [0.13.1]
|
|
21
|
+
|
|
22
|
+
- Publishable on the npm `latest` dist-tag as `0.13.1`, from tag
|
|
23
|
+
`openlup-core-v0.13.1`.
|
|
24
|
+
|
|
25
|
+
- Republish the unchanged core API and implementation with the repaired outbox publication as the complete `0.13.1` package set.
|
|
26
|
+
|
|
11
27
|
## [0.13.0]
|
|
12
28
|
|
|
13
29
|
- Publishable on the npm `latest` dist-tag as `0.13.0`, 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-
|
|
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>;
|
package/dist/readiness/index.js
CHANGED
|
@@ -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.
|
|
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",
|
package/release-gates.json
CHANGED
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
}
|
|
263
263
|
},
|
|
264
264
|
"pack": {
|
|
265
|
-
"$budgetReason": "2026-10-
|
|
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":
|
|
275
|
-
"maxUnpackedBytes":
|
|
274
|
+
"maxPackedBytes": 158720,
|
|
275
|
+
"maxUnpackedBytes": 667648,
|
|
276
276
|
"maxFiles": 211,
|
|
277
277
|
"requiredFiles": [
|
|
278
278
|
"AGENTS.md",
|
package/src/readiness/index.ts
CHANGED
|
@@ -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
|
+
}
|