@mnmal-ai/cortext 0.0.0 → 1.0.0-alpha.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.
@@ -0,0 +1,206 @@
1
+ /**
2
+ * The plugin-facing surface, snapshotted so a release can be SIZED rather than
3
+ * judged.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * A release-class guard that defines the surface as a list of paths passes a
8
+ * major as a patch with a green check the moment a type moves out of those
9
+ * paths: `git diff` on the stale path is empty, so it approves.
10
+ *
11
+ * cortext's propagation is heavy — a
12
+ * `packages/cortext/src` change reaches consumers as an npm tarball loaded by
13
+ * the substrate runtime from `SUBSTRATE_PLUGIN_DIR`, needing a publish **and** a
14
+ * runtime deploy. npm publishes are immutable, so a mis-sized release cannot be
15
+ * withdrawn, only superseded.
16
+ *
17
+ * ## Why it resolves through the code, not through paths
18
+ *
19
+ * A path list rots silently. This calls
20
+ * `buildClaudeNamespace()` and walks what it returns, so the surface is
21
+ * DERIVED from the thing it describes. A type or field cannot move out from
22
+ * under it, because there is no "under it" — moving a declaration between files
23
+ * changes nothing here, and that is the point.
24
+ *
25
+ * The committed `release-surface.json` is kept honest by
26
+ * `test/release-surface.test.ts`, exactly as `config.schema.json` is kept
27
+ * honest by `schema-parity.test.ts`. So the release script never has to execute
28
+ * TypeScript or parse a declaration: it diffs two committed JSON snapshots.
29
+ *
30
+ * ## What counts as breaking, and why `indexed` is in here
31
+ *
32
+ * Removing a type or field, making an optional field REQUIRED, narrowing an
33
+ * enum, or moving the substrate contract range are all breaks for someone holding
34
+ * data or code against this schema.
35
+ *
36
+ * `indexed` is included and it is not cosmetic: un-indexing a field turns a
37
+ * working `where` filter into an `unindexed_field` rejection. That is a
38
+ * runtime break for a caller who changed nothing, and it would otherwise be
39
+ * invisible to a version-bump decision.
40
+ */
41
+ /** One field, reduced to the properties a consumer can break against. */
42
+ export interface SurfaceField {
43
+ kind: string;
44
+ type?: string;
45
+ /** crossRef target type. */
46
+ target?: string;
47
+ nullable: boolean;
48
+ hasDefault: boolean;
49
+ readonly: boolean;
50
+ serverSet: boolean;
51
+ indexed: boolean;
52
+ enum?: string[];
53
+ /** Not (nullable | default | readonly | serverSet) — the write contract. */
54
+ required: boolean;
55
+ }
56
+ /**
57
+ * The published TypeScript API of one package.
58
+ *
59
+ * Two signals with deliberately different precision, because one alone is not
60
+ * enough and the gap between them is where a real break just hid:
61
+ *
62
+ * - `exports` is PRECISE. A name that disappears is a hard break; a name that
63
+ * appears is additive. No ambiguity either way.
64
+ * - `dtsDigest` NEVER MISSES. It hashes every emitted declaration, so any
65
+ * signature change anywhere moves it — but it cannot say WHICH export moved.
66
+ *
67
+ * The digest exists because of a live miss: `auditObjects` kept its name and
68
+ * changed its return type from `Finding[]` to `{ findings, checked }`. Names
69
+ * alone saw nothing. A schema-only surface saw nothing. It is exported from
70
+ * the kernel plugin's entry point, so it is a published break, and it was
71
+ * about to ship under a patch.
72
+ *
73
+ * A digest change therefore raises the floor to MINOR and is reported for a
74
+ * human to read — never auto-major, because it cannot tell a widened return
75
+ * type from a narrowed one.
76
+ */
77
+ export interface PackageApi {
78
+ /**
79
+ * Exported names qualified by the subpath that publishes them, as
80
+ * `<subpath>:<name>` — e.g. `.:buildReleaseSurface`, `./bootstrap:bootstrap`.
81
+ *
82
+ * Qualified rather than a bare union because the union cannot see a MOVE.
83
+ * A name relocated from `.` to `./bootstrap` breaks every existing importer
84
+ * while leaving a union identical; qualified, it reads as one removal and one
85
+ * addition, which is exactly what it is. The set diff below needs no change
86
+ * to get that right — the qualification does all the work.
87
+ */
88
+ exports: string[];
89
+ dtsDigest: string;
90
+ }
91
+ /**
92
+ * Format marker for the `api` section.
93
+ *
94
+ * Bumped when the SHAPE of `api` changes rather than its content. The exports
95
+ * list moved from bare names to subpath-qualified ones, which would otherwise
96
+ * read as every export being removed and re-added — a fake MAJOR generated by a
97
+ * tooling change that touched no published API.
98
+ *
99
+ * `classifySurfaceChange` treats a format mismatch as NOT COMPARED, the same
100
+ * way it already treats an absent `api`. That is the honest reading and it is
101
+ * an existing precedent in this file rather than a special case invented for
102
+ * this bump.
103
+ */
104
+ export declare const SURFACE_FORMAT = 2;
105
+ export interface ReleaseSurface {
106
+ contractVersion: string;
107
+ types: Record<string, Record<string, SurfaceField>>;
108
+ api?: Record<string, PackageApi>;
109
+ /** Absent means format 1 — bare, root-entry-only export names. */
110
+ surfaceFormat?: number;
111
+ }
112
+ /** One published entry point: the subpath a consumer imports, and its `.d.ts`. */
113
+ export interface SurfaceEntry {
114
+ subpath: string;
115
+ dts: string;
116
+ }
117
+ /**
118
+ * Resolve the `.d.ts` each published subpath points at, from a package's
119
+ * `exports` map.
120
+ *
121
+ * ## Why this exists
122
+ *
123
+ * The previous version assumed one entry per package and read `dist/index.d.ts`.
124
+ * Every publishable package here is multi-entry, so `./bootstrap`'s three
125
+ * exports — `bootstrap`, `CortextBootstrapConfig`,
126
+ * `CortextBootstrapConfigSchema` — were never in the exports list at all.
127
+ * Measured against the real classifier before fixing:
128
+ *
129
+ * additive change in ./bootstrap guard MINOR correct MINOR right
130
+ * export REMOVED from ./bootstrap guard MINOR correct MAJOR UNDER-SIZED
131
+ * export removed from "." guard MAJOR correct MAJOR right
132
+ *
133
+ * The middle row is the defect. It survived because the `dtsDigest` half of the
134
+ * two-signal design DOES cover every emitted declaration, so the change was
135
+ * never invisible — it was merely floored at minor by a review item. A guard
136
+ * that is wrong in one direction only is the hard kind to notice.
137
+ *
138
+ * ## Returns nothing rather than guessing
139
+ *
140
+ * A package with no `exports` map gets the conventional single entry. A subpath
141
+ * publishing no `types` condition contributes nothing — it exposes no
142
+ * TypeScript surface to compare. Neither is an error.
143
+ *
144
+ * Missing FILES are an error, and that is deliberate: silently skipping a
145
+ * declared subpath whose layout does not match the assumption would recreate
146
+ * exactly the blind spot this function removes — an unread surface reporting
147
+ * clean. The caller supplies `exists` so this stays pure.
148
+ */
149
+ export declare function resolveSurfaceEntries(exportsMap: unknown, exists: (relPath: string) => boolean): Array<{
150
+ subpath: string;
151
+ dtsPath: string;
152
+ }>;
153
+ /**
154
+ * Build the canonical surface.
155
+ *
156
+ * Keys are sorted so the committed JSON is stable — an unstable snapshot would
157
+ * produce diff noise on every regeneration and train its reader to skim, which
158
+ * is the failure this whole file is guarding against one level down.
159
+ */
160
+ export declare function buildReleaseSurface(namespace?: string): ReleaseSurface;
161
+ /**
162
+ * Extract the public export NAMES from an entry `.d.ts`.
163
+ *
164
+ * Handles the two forms tsdown emits: re-export lists (`export { a, type B }
165
+ * from './x.js'`) and direct declarations (`export declare function f`). The
166
+ * `type` modifier and `as` aliases are normalised to the exported name, since
167
+ * that is what a consumer writes.
168
+ */
169
+ export declare function parseDtsExports(dts: string): string[];
170
+ /**
171
+ * Build the published-API section.
172
+ *
173
+ * File reading is the CALLER's job — this stays pure so the rules are testable
174
+ * without a build directory, matching every other classifier in this file.
175
+ *
176
+ * @param entries One per published package: EVERY published entry point (see
177
+ * `resolveSurfaceEntries`), plus the text of every emitted declaration file
178
+ * for the digest.
179
+ * @param digest A hashing function; injected so this module needs no node
180
+ * built-ins and can run anywhere the rest of it can.
181
+ */
182
+ export declare function buildApiSurface(entries: ReadonlyArray<{
183
+ name: string;
184
+ entries: ReadonlyArray<SurfaceEntry>;
185
+ allDts: ReadonlyArray<string>;
186
+ }>, digest: (input: string) => string): Record<string, PackageApi>;
187
+ export type ReleaseClass = 'major' | 'minor' | 'patch';
188
+ export interface SurfaceChange {
189
+ class: ReleaseClass;
190
+ breaking: string[];
191
+ additive: string[];
192
+ /** Changes a human must read: real, but not classifiable from a digest. */
193
+ review: string[];
194
+ }
195
+ /**
196
+ * Classify the difference between two surfaces.
197
+ *
198
+ * Reports EVERY reason rather than the first, because a release usually carries
199
+ * several and a guard that stops at one teaches its reader to fix-and-rerun
200
+ * instead of reading.
201
+ */
202
+ export declare function classifySurfaceChange(before: ReleaseSurface, after: ReleaseSurface): SurfaceChange;
203
+ /** Is `proposed` at least as large as `required`? */
204
+ export declare function classSatisfies(proposed: ReleaseClass, required: ReleaseClass): boolean;
205
+ /** The class implied by a version bump. Returns null when it is not a bump. */
206
+ export declare function bumpClass(from: string, to: string): ReleaseClass | null;