@openlup/core 0.12.0 → 0.13.1

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
@@ -27,6 +27,9 @@ process: an application binds its ports and composes it.
27
27
  | `./risk` | kernel | candidate |
28
28
  | `./shipping` | kernel | candidate |
29
29
  | `./subscription` | kernel | candidate |
30
+ | `./outbox` | kernel | experimental |
31
+ | `./readiness` | kernel | experimental |
32
+ | `./standard-schema` | kernel | experimental |
30
33
  | `./testing` | testing | testing |
31
34
 
32
35
  Any version may change an export, and each change is recorded in
@@ -80,4 +83,21 @@ those before guessing at a contract.
80
83
 
81
84
  ## Readiness codes
82
85
 
83
- This version raises no `OPENLUP_E_*` readiness code.
86
+ The checker reports `OPENLUP_E_PORT_MISSING`, `OPENLUP_E_EVENT_UNHANDLED`,
87
+ `OPENLUP_E_EVENT_DUPLICATE`, `OPENLUP_E_SCHEDULE_UNBOUND`, `OPENLUP_E_SCHEMA_BEHIND`,
88
+ `OPENLUP_E_SET_MISMATCH` and `OPENLUP_E_ENV_MISSING`. Only a `ready` report admits
89
+ its exact candidate. A host must provide complete installed metadata and selected-adapter
90
+ observations; a report does not prove ACL, function bodies or recovery.
91
+
92
+ ## Using this package in an application
93
+
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.
100
+ - **Upgrade every `@openlup/*` package together,** after reading each changelog's `Migration:`
101
+ blocks.
102
+ - **Inside the OpenLup monorepo,** where this package is a workspace, the repository's root
103
+ `AGENTS.md` governs instead.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,30 @@ SemVer promise.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.13.1]
12
+
13
+ - Publishable on the npm `latest` dist-tag as `0.13.1`, from tag
14
+ `openlup-core-v0.13.1`.
15
+
16
+ - Republish the unchanged core API and implementation with the repaired outbox publication as the complete `0.13.1` package set.
17
+
18
+ ## [0.13.0]
19
+
20
+ - Publishable on the npm `latest` dist-tag as `0.13.0`, from tag
21
+ `openlup-core-v0.13.0`.
22
+
23
+ - Add experimental outbox envelope/handler descriptors, structural validation, job lease and candidate readiness contracts.
24
+ - Check package inventory, event ownership/coverage, actual bindings and qualified schema observations before admitting a new candidate.
25
+
26
+ Migration: existing exports remain; compose the selected rail and bind its schema probe before activation. Unknown observations refuse the new candidate and do not stop a serving version.
27
+
28
+
29
+ ### Changed
30
+
31
+ - `AGENTS.md` gains a required "Using this package in an application" section: do not edit or
32
+ patch the installed package, change behaviour through its seams, and upgrade every
33
+ `@openlup/*` package together. `docs:check` refuses a guide without it.
34
+
11
35
  ## [0.12.0]
12
36
 
13
37
  ### Removed
package/README.md CHANGED
@@ -3,7 +3,10 @@
3
3
  `@openlup/core` is the kernel of the `@openlup/*` package family: the
4
4
  framework-independent TypeScript contracts and logic for subscription and
5
5
  bundle commerce that the other packages and an application's composition read.
6
- Agents start with [AGENTS.md](AGENTS.md).
6
+ Agents start with [AGENTS.md](AGENTS.md). Its "Using this package in an
7
+ application" section, which `docs:check` requires, tells an application's agent
8
+ not to edit or patch the installed package and to upgrade every `@openlup/*`
9
+ package together.
7
10
 
8
11
  ## Channel and stability
9
12
 
@@ -18,6 +21,8 @@ Agents start with [AGENTS.md](AGENTS.md).
18
21
  each change is recorded in [the changelog](CHANGELOG.md). Pin an exact
19
22
  version and read the changelog when you upgrade.
20
23
 
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
+
21
26
  ## Package Surface Maturity
22
27
 
23
28
  Every current export is an internal candidate, experimental kernel, or testing
@@ -40,6 +45,9 @@ promise.
40
45
  | `./risk` | kernel | candidate | `smoke/riskStandalone.test.ts` |
41
46
  | `./shipping` | kernel | candidate | `smoke/shippingStandalone.test.ts` |
42
47
  | `./subscription` | kernel | candidate | `smoke/subscriptionBundleStandalone.test.ts` |
48
+ | `./outbox` | kernel | experimental | `smoke/outboxStandalone.test.ts` |
49
+ | `./readiness` | kernel | experimental | `smoke/readinessStandalone.test.ts` |
50
+ | `./standard-schema` | kernel | experimental | `smoke/standardSchemaStandalone.test.ts` |
43
51
  | `./testing` | testing | testing | `smoke/testingStandalone.test.ts` |
44
52
 
45
53
  `release-gates.json` records four distinct evidence classes:
@@ -52,9 +60,26 @@ promise.
52
60
  - `externalConsumerEvidence`: not yet evaluated; a first-party packed consumer
53
61
  remains package smoke, not external adoption.
54
62
 
55
- ## Local verification
63
+ ## Use in an application
56
64
 
57
- From this package directory:
65
+ From your application's root, with Node 24 and npm 11.19.0:
66
+
67
+ ```sh
68
+ npm install --save-exact @openlup/core@0.12.0
69
+ ```
70
+
71
+ Read the installed `AGENTS.md`, then compose the [typed wiring example](AGENTS.md#wiring-example)
72
+ with your application's own ports and options. Inspect the shipped sources and
73
+ declarations for the exact version you installed. A missing seam needs an
74
+ upstream proposal under your application's submission authority; the dependency
75
+ guide grants no permission to publish an issue or change your application.
76
+ Keep every installed `@openlup/*` package on the same exact set version.
77
+
78
+ ## Local verification from source
79
+
80
+ The following developer commands require a source checkout of this package;
81
+ the npm tarball omits its development scripts and tests. From the source package
82
+ directory:
58
83
 
59
84
  ```sh
60
85
  npm ci
@@ -63,10 +88,17 @@ npm run ci
63
88
 
64
89
  Focused proofs are `npm run api:check`, `npm run release:check`, and
65
90
  `npm run test:consumer`. The root test script skips this suite.
66
-
67
- Repository neutrality checks reuse the existing source scanner through
68
- [the tree counting interface](./scripts/neutrality-tree-counts.ts). This
69
- process interface reads JSON containing source paths, contents and a policy
91
+ `npm run docs:check` requires relative links in shipped Markdown to resolve to
92
+ files actually packed in the tarball; a target present only in source refuses.
93
+ Ambiguous destinations refuse explicitly: use literal punctuation or
94
+ percent-encoded filenames, with ASCII whitespace before an optional title.
95
+ Raw ampersands, angle characters and Unicode whitespace in local destinations
96
+ require a canonical rewrite rather than passing an incomplete link check.
97
+
98
+ The [repository neutrality checks](https://github.com/openlup/openlup/blob/openlup-core-v0.12.0/CONTRIBUTING.md#development-preview-checks)
99
+ reuse the source scanner through `./scripts/neutrality-tree-counts.ts` relative
100
+ to this source package. This process interface reads JSON containing source paths,
101
+ contents and a policy
70
102
  from standard input, then emits finding counts for each path. Shell sources
71
103
  retain the scanner's existing shell handling. The interface adds no package
72
104
  export and changes no matcher or kernel behavior.
@@ -74,7 +106,7 @@ export and changes no matcher or kernel behavior.
74
106
  Repository lint keeps this package's production source independent of configured
75
107
  provider SDKs and explicit industry contract names. Adopter adapters compose
76
108
  providers; generic ports can forward opaque extension data. See the
77
- [syntax scope and exceptions](https://github.com/openlup/openlup/blob/main/CONTRIBUTING.md#development-preview-checks).
109
+ [syntax scope and exceptions](https://github.com/openlup/openlup/blob/openlup-core-v0.12.0/CONTRIBUTING.md#development-preview-checks).
78
110
 
79
111
  The repository checker also calls the UI neutrality counting process with
80
112
  `--counts-json` to count its existing patterns over the supplied sources. Running that checker without
@@ -90,6 +122,8 @@ neutrality evidence, not package activation or consumer compatibility evidence.
90
122
 
91
123
  ## Kernel Documentation
92
124
 
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."} -->
126
+
93
127
  - [`docs/SUBSCRIPTION_ENGINE.md`](docs/SUBSCRIPTION_ENGINE.md) — what the
94
128
  `./subscription` kernel owns, its deterministic-clock contract, the
95
129
  late-payment cycle-shift rule and its monotonic clamp, and how a host
@@ -0,0 +1,55 @@
1
+ /** @beta Shared event envelope. Storage and execution policy belong to the rail. */
2
+ export interface OutboxEventRow {
3
+ id: string;
4
+ created_at: string;
5
+ available_at: string;
6
+ processed_at: string | null;
7
+ aggregate_type: string;
8
+ aggregate_id: string;
9
+ event_type: string;
10
+ idempotency_key: string;
11
+ status: string;
12
+ attempts: number;
13
+ payload: Record<string, unknown>;
14
+ error: string | null;
15
+ metadata: Record<string, unknown>;
16
+ }
17
+ /** @beta */
18
+ export type OutboxHandlerOutcome = {
19
+ kind: "processed";
20
+ detail?: Record<string, unknown>;
21
+ } | {
22
+ kind: "retry";
23
+ reason: string;
24
+ } | {
25
+ kind: "discard";
26
+ reason: string;
27
+ benign?: boolean;
28
+ } | {
29
+ kind: "snooze";
30
+ reason: string;
31
+ };
32
+ /** @beta Phase observations are transient; never put payloads or credentials here. */
33
+ export interface OutboxHandlerExecutionContext {
34
+ setPhase(phase: string): void;
35
+ }
36
+ /** @beta Inert registration shared by preflight and the run-bound builder. */
37
+ export interface OutboxHandlerDescriptor {
38
+ readonly eventType: string;
39
+ readonly timeoutMs: number;
40
+ }
41
+ /** @beta Structural abort signal, shared by browser and server implementations. */
42
+ export interface OutboxAbortSignal {
43
+ readonly aborted: boolean;
44
+ readonly reason: unknown;
45
+ addEventListener(type: "abort", listener: () => void, options?: {
46
+ once?: boolean;
47
+ }): void;
48
+ removeEventListener(type: "abort", listener: () => void): void;
49
+ }
50
+ /** @beta Effects must be idempotent and honor the abort signal. */
51
+ export interface OutboxHandler<Signal extends OutboxAbortSignal = OutboxAbortSignal> extends OutboxHandlerDescriptor {
52
+ handle(row: OutboxEventRow, signal: Signal, execution?: OutboxHandlerExecutionContext): Promise<OutboxHandlerOutcome>;
53
+ }
54
+ export { PLATFORM_EVENT_VOCABULARY } from "./vocabulary.js";
55
+ export type { EventVocabularyEntry } from "./vocabulary.js";
@@ -0,0 +1 @@
1
+ export { PLATFORM_EVENT_VOCABULARY } from "./vocabulary.js";
@@ -0,0 +1,7 @@
1
+ /** @beta Vocabulary ownership is separate from consumer registration. */
2
+ export interface EventVocabularyEntry {
3
+ eventType: string;
4
+ owner: string;
5
+ }
6
+ /** @beta Applications supply their complete ordered persisted vocabulary separately. */
7
+ export declare const PLATFORM_EVENT_VOCABULARY: readonly EventVocabularyEntry[];
@@ -0,0 +1,36 @@
1
+ /** @beta Applications supply their complete ordered persisted vocabulary separately. */
2
+ export const PLATFORM_EVENT_VOCABULARY = [
3
+ { eventType: "commerce.order_draft.created", owner: "@openlup/core" },
4
+ { eventType: "commerce.order.paid", owner: "@openlup/core" },
5
+ { eventType: "commerce.order.paid.email", owner: "@openlup/core" },
6
+ { eventType: "commerce.payment.failed", owner: "@openlup/core" },
7
+ { eventType: "commerce.checkout.expired", owner: "@openlup/core" },
8
+ { eventType: "commerce.order.canceled", owner: "@openlup/core" },
9
+ { eventType: "commerce.order.refunded", owner: "@openlup/core" },
10
+ { eventType: "commerce.shipment.dispatched", owner: "@openlup/core" },
11
+ { eventType: "commerce.shipment.delivered", owner: "@openlup/core" },
12
+ { eventType: "commerce.shipment.exception", owner: "@openlup/core" },
13
+ { eventType: "commerce.return.approved", owner: "@openlup/core" },
14
+ { eventType: "commerce.return.rejected", owner: "@openlup/core" },
15
+ { eventType: "commerce.order.review_request", owner: "@openlup/core" },
16
+ { eventType: "commerce.order.review_effects", owner: "@openlup/core" },
17
+ { eventType: "commerce.product.back_in_stock", owner: "@openlup/core" },
18
+ { eventType: "commerce.order_draft.abandoned.1h", owner: "@openlup/core" },
19
+ { eventType: "commerce.order_draft.abandoned.24h", owner: "@openlup/core" },
20
+ { eventType: "commerce.order_draft.abandoned.72h", owner: "@openlup/core" },
21
+ { eventType: "commerce.order.reorder_reminder", owner: "@openlup/core" },
22
+ { eventType: "commerce.checkout_recovery", owner: "@openlup/core" },
23
+ { eventType: "subscription.cancelled", owner: "@openlup/core" },
24
+ { eventType: "subscription.created", owner: "@openlup/core" },
25
+ { eventType: "subscription.activation_action_required", owner: "@openlup/core" },
26
+ { eventType: "subscription.pause_reminder_due", owner: "@openlup/core" },
27
+ { eventType: "subscription.paused", owner: "@openlup/core" },
28
+ { eventType: "subscription.resumed", owner: "@openlup/core" },
29
+ { eventType: "subscription.delivery_rescheduled", owner: "@openlup/core" },
30
+ { eventType: "subscription.cycle_skipped", owner: "@openlup/core" },
31
+ { eventType: "subscription.address_changed", owner: "@openlup/core" },
32
+ { eventType: "subscription.package_changed", owner: "@openlup/core" },
33
+ { eventType: "subscription.renewal_upcoming", owner: "@openlup/core" },
34
+ { eventType: "accounting.order.reversal_requested", owner: "@openlup/core" },
35
+ { eventType: "commerce.fulfillment.handed_over", owner: "@openlup/core" },
36
+ ];
@@ -8,3 +8,4 @@ export { createPlatformBundleRegistry, platformEnvSchema, } from "./platformKern
8
8
  export type { CreatePlatformBundleRegistryInput, PlatformBundleDescriptor, PlatformBundleRegistry, PlatformBundleReadiness, PlatformEnv, PlatformEnvInput, } from "./platformKernel.js";
9
9
  /** @beta */
10
10
  export type { AnalyticsPort, BlobStoragePort, DataGatewayPort, HttpRuntimePort, MigrationRunnerPort, PlatformHttpRequest, PlatformHttpResponse, SchedulerPort, TransactionalRuntimePort, } from "./ports.js";
11
+ export type { JobLeasePort, PlatformJobTriggerKind, PlatformJobInvocation, PlatformJobClaim, PlatformJobFinishStatus, PlatformJobFinishSummary } from "./ports.js";
@@ -98,3 +98,27 @@ export interface TransactionalRuntimePort {
98
98
  status: number;
99
99
  }>;
100
100
  }
101
+ /** @beta Invocation evidence and claim/finish contract shared by infrastructure rails. */
102
+ export type PlatformJobTriggerKind = "worker" | "scheduler" | "operator";
103
+ export type PlatformJobInvocation = {
104
+ triggerKind: PlatformJobTriggerKind;
105
+ /** Observational adapter evidence; it never grants execution authority. */
106
+ invocationSource: string;
107
+ };
108
+ export type PlatformJobClaim = {
109
+ acquired: boolean;
110
+ runId: string | null;
111
+ reason: string;
112
+ };
113
+ export type PlatformJobFinishStatus = "success" | "failed";
114
+ export type PlatformJobFinishSummary = {
115
+ checked: number;
116
+ updated: number;
117
+ failures: number;
118
+ skipped: boolean;
119
+ reason?: string;
120
+ };
121
+ export interface JobLeasePort {
122
+ claimJobRun(jobName: string, invocation: PlatformJobInvocation, leaseSeconds?: number): Promise<PlatformJobClaim>;
123
+ finishJobRun(jobName: string, runId: string, invocation: PlatformJobInvocation, status: PlatformJobFinishStatus, result: PlatformJobFinishSummary, extraMetadata?: Record<string, unknown>): Promise<boolean>;
124
+ }
@@ -0,0 +1,121 @@
1
+ import type { OutboxHandlerDescriptor } from "../outbox/index.js";
2
+ import type { EventVocabularyEntry } from "../outbox/vocabulary.js";
3
+ /** @beta */
4
+ export declare const READINESS_CODES: readonly ["OPENLUP_E_PORT_MISSING", "OPENLUP_E_EVENT_UNHANDLED", "OPENLUP_E_EVENT_DUPLICATE", "OPENLUP_E_SCHEDULE_UNBOUND", "OPENLUP_E_SCHEMA_BEHIND", "OPENLUP_E_SET_MISMATCH", "OPENLUP_E_ENV_MISSING"];
5
+ /** @beta */
6
+ export type ReadinessCode = typeof READINESS_CODES[number];
7
+ /** @beta */
8
+ export interface ReadinessIssue {
9
+ code: ReadinessCode;
10
+ package: string;
11
+ subject: string;
12
+ fix: string;
13
+ observation?: string;
14
+ }
15
+ /** @beta */
16
+ export interface SchemaObject {
17
+ kind: "table" | "column" | "function";
18
+ name: string;
19
+ signature?: string;
20
+ }
21
+ /** @beta Version is an adapter-specific migration identity, not inferred from object existence. */
22
+ export interface RequiredSchema {
23
+ version: string;
24
+ migration: string;
25
+ objects: readonly SchemaObject[];
26
+ }
27
+ /** @beta */
28
+ export interface PackageManifest {
29
+ name: string;
30
+ version: string;
31
+ kind: "kernel" | "rail" | "capability" | "implementation";
32
+ emits: readonly string[];
33
+ handles: readonly string[];
34
+ schedules: readonly {
35
+ id: string;
36
+ cadenceSeconds: number;
37
+ }[];
38
+ routes: readonly string[];
39
+ requiredPorts: readonly string[];
40
+ env: readonly string[];
41
+ requiredSchema?: RequiredSchema;
42
+ }
43
+ /** @beta Run-bound handlers are represented by their shared inert descriptors. */
44
+ export interface Contribution {
45
+ manifest: PackageManifest;
46
+ handlers: readonly OutboxHandlerDescriptor[];
47
+ schedules: readonly {
48
+ id: string;
49
+ cadenceSeconds: number;
50
+ run: (...args: never[]) => unknown;
51
+ }[];
52
+ routes: readonly {
53
+ id: string;
54
+ handle: (...args: never[]) => unknown;
55
+ }[];
56
+ }
57
+ /** @beta Host-observed inventory, including core. The loader owns completeness. */
58
+ export interface LoadedPackage {
59
+ name: string;
60
+ version: string;
61
+ kind: PackageManifest["kind"];
62
+ }
63
+ /** @beta Candidate identity must change when configuration, schema or artifact changes. */
64
+ export interface CandidateIdentity {
65
+ artifact: string;
66
+ configuration: string;
67
+ environment: string;
68
+ database: string;
69
+ schema: string;
70
+ }
71
+ /** @beta */
72
+ export interface EventExemption {
73
+ pattern: string;
74
+ prefix: boolean;
75
+ owner: string;
76
+ reason: string;
77
+ state: "ignored" | "dormant";
78
+ }
79
+ /** @beta */
80
+ export interface ReadinessInput {
81
+ candidate: CandidateIdentity;
82
+ inventoryComplete: boolean;
83
+ loadedPackages: readonly LoadedPackage[];
84
+ contributions: readonly Contribution[];
85
+ vocabulary: readonly EventVocabularyEntry[];
86
+ exemptions: readonly EventExemption[];
87
+ bindings: {
88
+ ports: Readonly<Record<string, unknown>>;
89
+ triggers: Readonly<Record<string, unknown>>;
90
+ routes: Readonly<Record<string, unknown>>;
91
+ env: Readonly<Record<string, string | undefined>>;
92
+ };
93
+ schemaProbe: SchemaProbePort;
94
+ probeTimeoutMs?: number;
95
+ }
96
+ /** @beta A bounded catalog observation, not proof of SQL bodies, ACL or recovery. */
97
+ export interface SchemaObservation {
98
+ database: string;
99
+ version: string | null;
100
+ objects: readonly {
101
+ object: SchemaObject;
102
+ present: boolean | null;
103
+ }[];
104
+ }
105
+ /** @beta */
106
+ export interface SchemaProbePort {
107
+ observe(requirement: RequiredSchema): Promise<SchemaObservation>;
108
+ }
109
+ /** @beta Each operation commits before resolving; never lend a producer transaction to dispatch. */
110
+ export interface SqlExecutor {
111
+ query<Row extends Record<string, unknown> = Record<string, unknown>>(text: string, values?: readonly unknown[]): Promise<{
112
+ rows: Row[];
113
+ }>;
114
+ }
115
+ /** @beta */
116
+ export interface ReadinessReport {
117
+ state: "ready" | "unsatisfied" | "unknown";
118
+ candidate: CandidateIdentity;
119
+ loadedPackages: readonly LoadedPackage[];
120
+ issues: readonly ReadinessIssue[];
121
+ }
@@ -0,0 +1,2 @@
1
+ /** @beta */
2
+ export const READINESS_CODES = ["OPENLUP_E_PORT_MISSING", "OPENLUP_E_EVENT_UNHANDLED", "OPENLUP_E_EVENT_DUPLICATE", "OPENLUP_E_SCHEDULE_UNBOUND", "OPENLUP_E_SCHEMA_BEHIND", "OPENLUP_E_SET_MISMATCH", "OPENLUP_E_ENV_MISSING"];
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import type { ReadinessInput, ReadinessIssue } from "./contracts.js";
3
+ export declare const schemaObjectShape: z.ZodObject<{
4
+ kind: z.ZodEnum<{
5
+ function: "function";
6
+ table: "table";
7
+ column: "column";
8
+ }>;
9
+ name: z.ZodString;
10
+ signature: z.ZodOptional<z.ZodString>;
11
+ }, z.core.$strict>;
12
+ export declare const manifestShape: z.ZodObject<{
13
+ name: z.ZodString;
14
+ version: z.ZodString;
15
+ kind: z.ZodEnum<{
16
+ kernel: "kernel";
17
+ rail: "rail";
18
+ capability: "capability";
19
+ implementation: "implementation";
20
+ }>;
21
+ emits: z.ZodArray<z.ZodString>;
22
+ handles: z.ZodArray<z.ZodString>;
23
+ schedules: z.ZodArray<z.ZodObject<{
24
+ id: z.ZodString;
25
+ cadenceSeconds: z.ZodNumber;
26
+ }, z.core.$strict>>;
27
+ routes: z.ZodArray<z.ZodString>;
28
+ requiredPorts: z.ZodArray<z.ZodString>;
29
+ env: z.ZodArray<z.ZodString>;
30
+ requiredSchema: z.ZodOptional<z.ZodObject<{
31
+ version: z.ZodString;
32
+ migration: z.ZodString;
33
+ objects: z.ZodArray<z.ZodObject<{
34
+ kind: z.ZodEnum<{
35
+ function: "function";
36
+ table: "table";
37
+ column: "column";
38
+ }>;
39
+ name: z.ZodString;
40
+ signature: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>>;
42
+ }, z.core.$strict>>;
43
+ }, z.core.$strict>;
44
+ /** Validate declarations without invoking a handler, schedule, route or probe. */
45
+ export declare function declarationIssues(input: ReadinessInput): ReadinessIssue[];
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ const name = z.string().min(1).max(240).regex(/^[a-zA-Z0-9@_./:-]+$/);
3
+ const version = z.string().regex(/^0\.\d+\.\d+$/);
4
+ const list = z.array(name).refine((items) => new Set(items).size === items.length);
5
+ const kind = z.enum(["kernel", "rail", "capability", "implementation"]);
6
+ export const schemaObjectShape = z.object({ kind: z.enum(["table", "column", "function"]), name,
7
+ signature: z.string().max(1000).optional() }).strict().refine((o) => o.name.split(".").length === (o.kind === "column" ? 3 : 2) && (o.kind !== "function" || o.signature !== undefined));
8
+ export const manifestShape = z.object({ name: name.regex(/^@openlup\/[a-z0-9-]+$/), version, kind,
9
+ emits: list, handles: list, schedules: z.array(z.object({ id: name, cadenceSeconds: z.number().finite().positive() }).strict()),
10
+ routes: list, requiredPorts: list, env: list,
11
+ requiredSchema: z.object({ version: name, migration: name, objects: z.array(schemaObjectShape) }).strict().optional(),
12
+ }).strict();
13
+ /** Validate declarations without invoking a handler, schedule, route or probe. */
14
+ export function declarationIssues(input) {
15
+ const issues = [];
16
+ const issue = (subject, fix, code = "OPENLUP_E_SET_MISMATCH") => issues.push({ code, package: "@openlup/core", subject, fix });
17
+ if (!input || typeof input !== "object") {
18
+ issue("input", "Supply complete candidate observations.");
19
+ return issues;
20
+ }
21
+ if (!input.candidate || ![input.candidate.artifact, input.candidate.configuration, input.candidate.environment, input.candidate.database, input.candidate.schema].every((v) => typeof v === "string" && /^[a-zA-Z0-9_.:/-]{1,240}$/.test(v)))
22
+ issue("candidate", "Supply sanitized immutable artifact, configuration, environment and database identities.");
23
+ if (input.inventoryComplete !== true)
24
+ issue("inventory", "Observe the complete installed package inventory, including core.");
25
+ if (!Array.isArray(input.loadedPackages) || !Array.isArray(input.contributions)) {
26
+ issue("inventory", "Supply loaded packages and contributions as arrays.");
27
+ return issues;
28
+ }
29
+ for (const pkg of input.loadedPackages)
30
+ if (!z.object({ name, version, kind }).strict().safeParse(pkg).success)
31
+ issue("loaded-package", "Supply a valid observed package name, version and kind.");
32
+ for (const c of input.contributions) {
33
+ if (!manifestShape.safeParse(c?.manifest).success) {
34
+ issue("manifest", "Supply a valid complete manifest with unique declarations.");
35
+ continue;
36
+ }
37
+ if (!Array.isArray(c.handlers) || c.handlers.some((h) => !name.safeParse(h?.eventType).success || !Number.isFinite(h?.timeoutMs) || h?.timeoutMs <= 0))
38
+ issue(c.manifest.name, "Supply valid shared handler descriptors.", "OPENLUP_E_EVENT_UNHANDLED");
39
+ if (!Array.isArray(c.schedules) || c.schedules.some((s) => !name.safeParse(s?.id).success || !Number.isFinite(s?.cadenceSeconds) || s?.cadenceSeconds <= 0 || typeof s?.run !== "function"))
40
+ issue(c.manifest.name, "Supply actual runnable schedules.", "OPENLUP_E_SCHEDULE_UNBOUND");
41
+ if (!Array.isArray(c.routes) || c.routes.some((r) => !name.safeParse(r?.id).success || typeof r?.handle !== "function"))
42
+ issue(c.manifest.name, "Supply actual route registrations.", "OPENLUP_E_PORT_MISSING");
43
+ const objects = c.manifest.requiredSchema?.objects ?? [];
44
+ if (new Set(objects.map((o) => `${o.kind}:${o.name}:${o.signature ?? ""}`)).size !== objects.length)
45
+ issue(c.manifest.name, "Remove duplicate schema requirements.", "OPENLUP_E_SCHEMA_BEHIND");
46
+ }
47
+ if (!Array.isArray(input.vocabulary) || input.vocabulary.some((v) => !name.safeParse(v?.eventType).success || !name.safeParse(v?.owner).success))
48
+ issue("vocabulary", "Supply explicit vocabulary entries with owners.", "OPENLUP_E_EVENT_UNHANDLED");
49
+ if (!Array.isArray(input.exemptions) || input.exemptions.some((e) => !name.safeParse(e?.pattern).success || typeof e?.prefix !== "boolean" || !name.safeParse(e?.owner).success || typeof e?.reason !== "string" || !e?.reason.trim() || !["ignored", "dormant"].includes(e?.state)))
50
+ issue("exemptions", "Supply explicit owned exemptions with reasons.", "OPENLUP_E_EVENT_UNHANDLED");
51
+ if (!input.bindings || [input.bindings.ports, input.bindings.routes, input.bindings.triggers, input.bindings.env].some((v) => !v || typeof v !== "object" || Array.isArray(v)))
52
+ issue("bindings", "Supply actual host bindings.", "OPENLUP_E_PORT_MISSING");
53
+ if (!input.schemaProbe || typeof input.schemaProbe.observe !== "function")
54
+ issue("schemaProbe", "Bind a schema observation port.", "OPENLUP_E_SCHEMA_BEHIND");
55
+ if (input.probeTimeoutMs !== undefined && (!Number.isFinite(input.probeTimeoutMs) || input.probeTimeoutMs < 1 || input.probeTimeoutMs > 30000))
56
+ issue("probeTimeoutMs", "Use a bounded probe timeout from 1 to 30000 ms.", "OPENLUP_E_SCHEMA_BEHIND");
57
+ return issues;
58
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReadinessInput, ReadinessReport } from "./contracts.js";
2
+ export * from "./contracts.js";
3
+ export { schemaObjectKey, buildSchemaObjectProbe, createSchemaProbe } from "./schemaProbe.js";
4
+ /** @beta Evaluates supplied facts; never performs migration or effect work. */
5
+ export declare function checkReadiness(input: ReadinessInput): Promise<ReadinessReport>;