@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,96 @@
1
+ /**
2
+ * The namespace the kernel invariants are scoped to.
3
+ *
4
+ * Every CHECK constraint and partial index this plugin installs carries a
5
+ * `namespace = <literal>` predicate, so that a kernel-wide table
6
+ * (`rows`) can hold rows for unrelated namespaces without inheriting
7
+ * cortext's controlled vocabularies. That literal has to live in exactly one
8
+ * place, because getting it wrong fails SILENTLY: a constraint scoped to a
9
+ * namespace with no rows is not an error, it is a constraint that passes
10
+ * trivially, and an index whose predicate no query can satisfy is not an
11
+ * error either — it is a heap scan.
12
+ *
13
+ * Tests that seed rows under the namespace the migrations name cannot catch a
14
+ * mismatch with the one production uses, so this constant is the one copy.
15
+ *
16
+ * THE CONFIGURABILITY GAP, and what is actually done about it.
17
+ *
18
+ * The coordinator's namespaces are operator-configurable, while
19
+ * KernelMigrationContext exposes only `sql` and `metric` — a migration
20
+ * cannot read config. So these constants track the DEFAULTS, and a deployment
21
+ * that renames a namespace would get no kernel invariants at all.
22
+ *
23
+ * Parameterising the literals does not fix that. Migrations are recorded once
24
+ * by `id` in migrations and `apply` runs only when the id is absent, so
25
+ * a later rename can never retroactively re-scope objects already created. It
26
+ * would produce correct FRESH installs and silently-wrong RENAMED ones, which
27
+ * is worse than today because it reads as solved.
28
+ *
29
+ * The fix is reconciliation against the configured namespace. `onKernelReady`
30
+ * carries a `sql` handle and plugin-attributed coordinator namespaces, so it
31
+ * is possible, but it is opt-in (`./repair.ts`): a store that ran unscoped may
32
+ * hold rows violating the vocabulary, so `ADD CONSTRAINT` can fail on exactly
33
+ * the databases a repair exists for.
34
+ *
35
+ * So what ships is REPORTING, not reconciliation, in two places that answer
36
+ * different questions: the kernel's `./invariant-audit.ts` reads the catalog
37
+ * at boot (objects absent, misscoped, or NOT VALID), and the coordinator's
38
+ * `onStart` compares the CONFIGURED namespaces against these constants. See
39
+ * `packages/cortext/src/kernel-invariants.ts`.
40
+ *
41
+ * It warns rather than REFUSES. Per-cohort context stores are a designed
42
+ * capability that `bootstrap-config.test.ts` exercises with `codex`, so the
43
+ * override keeps working; only the silence goes.
44
+ */
45
+ export declare const CORTEXT_NAMESPACE = "cortext";
46
+ /**
47
+ * The namespace the architecture-findings surface registers under, and which
48
+ * migration 0002 scopes its body normalization to.
49
+ *
50
+ * Separate from CORTEXT_NAMESPACE because the two have separate operator
51
+ * knobs and diverging lifecycle policies (compaction, retention, audit) — but
52
+ * subject to exactly the same gap, which is why it is stated here rather than
53
+ * left as a bare literal inside 0002.
54
+ */
55
+ export declare const ARCHITECTURE_NAMESPACE = "architecture";
56
+ /**
57
+ * The `name` the cortext COORDINATOR plugin registers itself under.
58
+ *
59
+ * Lives here, beside the namespace literals, because it is load-bearing for
60
+ * exactly the same reason they are. The kernel resolves
61
+ * which namespace to target by filtering
62
+ * `KernelLifecycleContext.coordinatorNamespacesByPlugin` on
63
+ * `plugin === CORTEXT_COORDINATOR_NAME` — so if the coordinator is ever
64
+ * renamed and this literal is not, the filter matches nothing and the audit
65
+ * reports "cortext declares no namespace" on a perfectly healthy deployment.
66
+ *
67
+ * A predicate that silently matches nothing is not an error, and a confidently wrong answer is
68
+ * worse here than no answer, because "coordinator absent" reads as a real
69
+ * finding rather than as a broken check.
70
+ *
71
+ * `kernel-namespace-parity.test.ts` asserts the coordinator's actual
72
+ * `plugin.name` equals this. Do not inline the string on either side.
73
+ */
74
+ export declare const CORTEXT_COORDINATOR_NAME = "cortext";
75
+ /**
76
+ * Body keys cortext's namespaces mirror into `rows` columns, so the
77
+ * cold-start and status filters hit an index. `0001_baseline` creates them.
78
+ */
79
+ export declare const CORTEXT_PROMOTED_COLUMNS: {
80
+ readonly archived: {
81
+ readonly column: 'archived';
82
+ readonly kind: 'boolean';
83
+ };
84
+ readonly project: {
85
+ readonly column: 'project';
86
+ readonly kind: 'text';
87
+ };
88
+ readonly status: {
89
+ readonly column: 'status';
90
+ readonly kind: 'text';
91
+ };
92
+ readonly priority: {
93
+ readonly column: 'priority';
94
+ readonly kind: 'text';
95
+ };
96
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * cortext's kernel plugin public surface.
3
+ *
4
+ * Exported for direct embedding (the perf harness, test harnesses).
5
+ * Production deployments go through the manifest loader →
6
+ * `./bootstrap` entry.
7
+ */
8
+ export { ARCHITECTURE_NAMESPACE, CORTEXT_COORDINATOR_NAME, CORTEXT_NAMESPACE } from './cortext-namespace.js';
9
+ export { auditNamespaceCoverage, auditObjects, type Finding, findingMessage, INVARIANT_OBJECTS, type InvariantObject, type PluginNamespace, reportInvariantAudit, resolveCortextNamespaces, scopedNamespace, type UntargetableReason } from './invariant-audit.js';
10
+ export { cortextBaselineMigration } from './migrations/0001_baseline.js';
11
+ export { createCortextKernelPlugin } from './plugin.js';
12
+ export { REPAIR_ENV_VAR, type RepairOutcome, repairIfRequested, repairInvariants, repairOutcomeMessage, repairRequested } from './repair.js';
13
+ export { countDuplicateHostInstallKeys, countDuplicateKids, rescopeHostInstallKeyIndex, rescopeKidUniqueIndex, rescopeRecipientIndex, rescopeVocabConstraint, VOCAB_CONSTRAINTS, type VocabConstraint } from './rescope.js';
14
+ export { SUBSTRATE_CONTRACT_RANGE } from './substrate-contract.js';
15
+ export { AGENT_MESSAGE_STATUS_VOCAB, TODO_LIFECYCLE_VOCAB, TODO_PRIORITY_VOCAB, TODO_RESOLUTION_VOCAB } from './todo-vocab.js';
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Boot-time audit of the kernel invariants this plugin installs.
3
+ *
4
+ * ## What this is, and what it deliberately is not
5
+ *
6
+ * It VERIFIES. It does not repair. Every migration here installs a CHECK
7
+ * constraint or a partial index carrying a `namespace = <literal>` predicate,
8
+ * and a migration is recorded once by `id` — `apply` runs only when that id is
9
+ * absent — so nothing re-examines those objects after the first boot that
10
+ * created them. Between then and now they can be dropped by hand, left NOT
11
+ * VALID by a failed validation, or point at a namespace no coordinator uses.
12
+ * All three fail the same way: writes succeed, queries answer, and the
13
+ * guarantee is simply gone.
14
+ *
15
+ * AgentIdentity.kid uniqueness is what the mailbox's recipient-only trust
16
+ * model rests on. A constraint scoped to a namespace with no rows is not an
17
+ * error; it passes trivially.
18
+ *
19
+ * ## Why this reports rather than repairs
20
+ *
21
+ * Not because repair is impossible — `./repair.ts` does it, using the `sql`
22
+ * handle and the plugin-attributed namespaces, the latter being what makes a
23
+ * rename TARGETABLE rather than merely detectable.
24
+ *
25
+ * The split is deliberate and permanent. Auditing is a read, so it runs on
26
+ * every boot. Repairing is DDL taking ACCESS EXCLUSIVE on `rows`, so it
27
+ * runs only when an operator asks via `SYNOPTIC_CORTEXT_REPAIR`. Keeping them
28
+ * apart means the reporting path can never surprise anyone, and the repair path
29
+ * has exactly one trigger to reason about.
30
+ *
31
+ * The flat `coordinatorNamespaces` this function reads answers "is our literal
32
+ * declared?" but never "what replaced it". `resolveCortextNamespaces` reads the
33
+ * attributed list — and keeps `undefined` from collapsing into `[]`, which would
34
+ * make "too old to answer" read as "answered: none".
35
+ */
36
+ import { ARCHITECTURE_NAMESPACE, CORTEXT_NAMESPACE } from './cortext-namespace.js';
37
+ /** A catalog object one of the migrations installs, and what rests on it. */
38
+ export interface InvariantObject {
39
+ readonly name: string;
40
+ readonly kind: 'constraint' | 'index';
41
+ /** The migration that installs it, for the operator chasing this down. */
42
+ readonly migration: string;
43
+ /** Stated as the loss, not the mechanism — see `findingMessage`. */
44
+ readonly guarantee: string;
45
+ }
46
+ /**
47
+ * The five objects. Enumerated by hand rather than derived from the migration
48
+ * array, deliberately: a migration's `apply` is opaque SQL text, so anything
49
+ * "derived" would be a regex over it that agrees with reality only by luck.
50
+ * `invariant-audit.test.ts` greps the migration sources for the DDL and fails
51
+ * if this list and the migrations disagree — which is the check that actually
52
+ * holds them together.
53
+ *
54
+ * Note there is no `architecture/*` entry, and that is correct: migration 0002
55
+ * is a one-time body normalisation, not a standing invariant. A drifted
56
+ * architecture namespace means that normalisation never ran for those rows —
57
+ * real, but a different and lesser thing than "kid uniqueness is not held".
58
+ */
59
+ export declare const INVARIANT_OBJECTS: readonly InvariantObject[];
60
+ /**
61
+ * Why the audit could not say which namespace replaced ours.
62
+ *
63
+ * `host_does_not_attribute` is NOT the same as `coordinator_absent`, and
64
+ * collapsing them is the whole reason this type exists — see
65
+ * `resolveCortextNamespaces`.
66
+ */
67
+ export type UntargetableReason = 'host_does_not_attribute' | 'coordinator_absent';
68
+ export type Finding =
69
+ /** Our literal is undeclared, and we cannot say what replaced it. */
70
+ {
71
+ readonly kind: 'namespace_uncovered';
72
+ readonly scopedTo: string;
73
+ readonly declared: readonly string[];
74
+ readonly reason: UntargetableReason;
75
+ }
76
+ /** Our literal is undeclared, and the cortext coordinator names its replacement. */
77
+ | {
78
+ readonly kind: 'namespace_drifted';
79
+ readonly scopedTo: string;
80
+ readonly configured: string;
81
+ }
82
+ /** The object is not in the catalog at all. */
83
+ | {
84
+ readonly kind: 'object_missing';
85
+ readonly object: InvariantObject;
86
+ }
87
+ /** Present, but its predicate names a different namespace. */
88
+ | {
89
+ readonly kind: 'object_misscoped';
90
+ readonly object: InvariantObject;
91
+ readonly scopedTo: string | null;
92
+ }
93
+ /** Present and correctly scoped, but NOT VALID — new writes only. */
94
+ | {
95
+ readonly kind: 'object_not_validated';
96
+ readonly object: InvariantObject;
97
+ };
98
+ /** One entry of the host's plugin-attributed namespace list. */
99
+ export interface PluginNamespace {
100
+ readonly plugin: string;
101
+ readonly namespace: string;
102
+ }
103
+ /**
104
+ * The namespaces the cortext COORDINATOR declared, or `null` when the host
105
+ * cannot say.
106
+ *
107
+ * The three states are genuinely distinct and the middle one is easy to lose:
108
+ *
109
+ * - `undefined` in -> `null` out. The host predates
110
+ * `coordinatorNamespacesByPlugin`. It is not telling us
111
+ * "none"; it is not telling us anything.
112
+ * - `[]` in, or no rows for our plugin -> `[]` out. The host DID attribute,
113
+ * and cortext contributed nothing — it is not loaded.
114
+ * - rows for our plugin -> those namespaces, in declaration order.
115
+ *
116
+ * The host deliberately does not default `undefined` to `[]`, which would make
117
+ * "too old to answer" read identically to "answered: none". Returning `null`
118
+ * rather than an empty array preserves that distinction.
119
+ */
120
+ export declare function resolveCortextNamespaces(byPlugin: readonly PluginNamespace[] | undefined): readonly string[] | null;
121
+ /**
122
+ * Whether a coordinator declared the namespace our objects are scoped to, and
123
+ * — when the host attributes — which namespace replaced it.
124
+ *
125
+ * Pure, and the only half that works without a `sql` handle, so it is what runs
126
+ * against the in-memory dataset.
127
+ *
128
+ * Only the context-store literal is checked. `ARCHITECTURE_NAMESPACE` is
129
+ * exported for callers that want it but is not audited here, for the reason in
130
+ * `INVARIANT_OBJECTS`: no catalog object is scoped to it.
131
+ */
132
+ export declare function auditNamespaceCoverage(coordinatorNamespaces: readonly string[], byPlugin?: readonly PluginNamespace[]): Finding[];
133
+ /** The namespace a predicate is scoped to, or null if it names none. */
134
+ export declare function scopedNamespace(definition: string): string | null;
135
+ /**
136
+ * Audit every object's presence, scope and validity.
137
+ *
138
+ * `sql` is typed `unknown` on the lifecycle context (substrate keeps the driver out
139
+ * of its type surface), so the cast happens here, once, exactly as the
140
+ * migrations do it.
141
+ */
142
+ /**
143
+ * Audit the invariant objects.
144
+ *
145
+ * Returns the INSPECTED COUNT alongside the findings.
146
+ *
147
+ * A tally rather than `INVARIANT_OBJECTS.length`, so that if this loop ever
148
+ * gains a filter, an early return or a per-object skip, the completion metric
149
+ * reports what was actually inspected instead of a promise.
150
+ */
151
+ export declare function auditObjects(sql: unknown): Promise<{
152
+ findings: Finding[];
153
+ checked: number;
154
+ }>;
155
+ /** The operator-facing sentence for one finding. */
156
+ export declare function findingMessage(finding: Finding): string;
157
+ /**
158
+ * Run the audit and report. Returns the findings so a caller — or a test — can
159
+ * assert on them without capturing the console.
160
+ *
161
+ * Both sinks: `metric` is the structured
162
+ * channel an operator alerts on, and the console line is what actually appears
163
+ * in the server journal beside substrate's own loader warnings, which is
164
+ * where someone will see it.
165
+ *
166
+ * Never throws. A kernel plugin that aborts the boot over an OBSERVATION
167
+ * would take the service down for a condition the service has been running
168
+ * with, possibly for weeks — the whole point is that these failures are silent
169
+ * and survivable. A catalog read that itself fails is reported and swallowed.
170
+ *
171
+ * ## It always emits, including when it finds nothing
172
+ *
173
+ * Every OTHER metric here names a failure, so without a completion signal a
174
+ * clean audit and an audit that never ran would both be silence. A check whose
175
+ * PASS state is indistinguishable from its absence is not falsifiable.
176
+ *
177
+ * So `invariant_audit_complete` fires on every path that reaches the end,
178
+ * carrying `checked`. Silence now means the audit DID NOT RUN and is
179
+ * actionable, and a `checked` that drops from 5 to 4 is visible without
180
+ * anything having failed. Metric only, not a console line: a per-boot success
181
+ * message on every deployment is noise, and the metric sink is exactly the
182
+ * structured channel this belongs in.
183
+ */
184
+ export declare function reportInvariantAudit(ctx: {
185
+ readonly sql: unknown;
186
+ readonly coordinatorNamespaces: readonly string[];
187
+ readonly coordinatorNamespacesByPlugin?: readonly PluginNamespace[];
188
+ metric(name: string, payload?: unknown): void;
189
+ }, warn?: (message: string) => void): Promise<Finding[]>;
190
+ export { ARCHITECTURE_NAMESPACE, CORTEXT_NAMESPACE };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * cortext-kernel's schema on `rows`: the columns cortext promotes out of the
3
+ * body (see `CORTEXT_PROMOTED_COLUMNS`), their indexes, and the invariants
4
+ * on cortext's own types.
5
+ *
6
+ * The kernel runner does not wrap `apply` in a transaction, so each statement
7
+ * commits on its own and a run that dies part-way re-applies cleanly: every
8
+ * statement is idempotent.
9
+ */
10
+ import type { KernelMigration } from '@mnmal-ai/substrate/sdk';
11
+ export declare const cortextBaselineMigration: KernelMigration;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * cortext's kernel plugin — kernel-tier sibling of the
3
+ * `@mnmal-ai/cortext` coordinator.
4
+ *
5
+ * Owns the kernel-level schema/body invariants for the cortext
6
+ * coordination namespaces (`cortext/*` + `architecture/*`: vocabulary CHECK
7
+ * constraints, one-time body normalisations) so the generic
8
+ * pg dataset in substrate stays plugin-agnostic.
9
+ *
10
+ * Migration order is the array order below. See `./cortext-namespace.ts` for
11
+ * why the namespace literal has exactly one home.
12
+ *
13
+ * The plugin contributes `migrations` and one `onKernelReady` handler. No
14
+ * filter operators, no kernel routes, no MCP tools — if a future schema-level
15
+ * need arises (e.g. column promotion for a new controlled vocabulary), it
16
+ * lands as another `KernelMigration` in the `migrations` array.
17
+ *
18
+ * `onKernelReady` AUDITS on every boot and REPAIRS only when asked.
19
+ * Migrations run once and are recorded by id, so nothing re-examines
20
+ * what they installed; the handler reads the catalog and reports objects that
21
+ * are absent, scoped to a namespace no coordinator declares, or left NOT VALID.
22
+ *
23
+ * The `sql` handle makes the audit possible, and plugin-attributed namespaces
24
+ * make repair targetable. Repair is gated on `SYNOPTIC_CORTEXT_REPAIR` rather than
25
+ * automatic — see `./repair.ts` for why, and for why it declines rather than
26
+ * guessing when the target namespace is unknown.
27
+ */
28
+ import type { KernelPlugin } from '@mnmal-ai/substrate/sdk';
29
+ export declare function createCortextKernelPlugin(): KernelPlugin;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * OPT-IN repair for drifted kernel invariants.
3
+ *
4
+ * ## Opt-in, and why it is not automatic
5
+ *
6
+ * Repair is DDL — `DROP CONSTRAINT` / `ADD CONSTRAINT`, `DROP INDEX` /
7
+ * `CREATE INDEX` — taking ACCESS EXCLUSIVE on `rows` and briefly blocking
8
+ * writes. Doing that unbidden on every boot, for a condition the deployment has
9
+ * been tolerating for weeks, is disproportionate to the problem: the invariants
10
+ * being inactive is bad, but it is not an outage, and a self-healing pass that
11
+ * surprises an operator mid-incident is.
12
+ *
13
+ * The same reasoning keeps a renamed namespace bootable: per-cohort context
14
+ * stores are a designed capability, so the kernel reports and never refuses.
15
+ *
16
+ * So the audit reports on every boot and this runs only when asked:
17
+ *
18
+ * SYNOPTIC_CORTEXT_REPAIR=1
19
+ *
20
+ * An ENV VAR rather than a config field, deliberately. A config field persists,
21
+ * which makes repair run on every boot forever — that is "automatic" wearing an
22
+ * opt-in label. An env var is set for one maintenance restart and then removed,
23
+ * which is what opt-in actually means. It also keeps `config.schema.json`
24
+ * unchanged, and that file is compared byte-for-byte by `schema-parity.test.ts`.
25
+ *
26
+ * ## What it will not do
27
+ *
28
+ * Repair needs a TARGET namespace, and it will only use one the host told it:
29
+ * `coordinatorNamespacesByPlugin`, filtered to the cortext
30
+ * coordinator. Against an older host that field is `undefined` and the answer
31
+ * is "cannot target" — so repair declines rather than guessing. Guessing which
32
+ * of `codex, perf, review` replaced our literal is the failure mode this whole
33
+ * lineage exists to remove, and it would be re-scoping a production schema on
34
+ * the strength of it.
35
+ *
36
+ * It also never aborts the boot. A duplicate kid makes the unique index
37
+ * unbuildable; that is reported and skipped, not thrown. An operator who set an
38
+ * env var for one restart should not be able to brick the service with it.
39
+ */
40
+ type Metric = (name: string, payload?: unknown) => void;
41
+ /** The env var that opts a deployment in, for one restart. */
42
+ export declare const REPAIR_ENV_VAR = "SYNOPTIC_CORTEXT_REPAIR";
43
+ /**
44
+ * Whether repair was asked for.
45
+ *
46
+ * Accepts only the affirmative spellings an operator would plausibly type, and
47
+ * treats everything else — including `0` and `false` — as off. Deliberately not
48
+ * a truthiness check on the raw string: `SYNOPTIC_CORTEXT_REPAIR=false` is
49
+ * unambiguously a request NOT to repair, and a bare presence check would read
50
+ * it as a yes and re-scope a production schema.
51
+ */
52
+ export declare function repairRequested(env: Record<string, string | undefined>): boolean;
53
+ export type RepairOutcome =
54
+ /** Every object now scoped to the target and fully validated. */
55
+ {
56
+ readonly kind: 'repaired';
57
+ readonly object: string;
58
+ }
59
+ /** Re-scoped, but existing rows violate it — enforced for new writes only. */
60
+ | {
61
+ readonly kind: 'repaired_not_validated';
62
+ readonly object: string;
63
+ readonly violations: number;
64
+ }
65
+ /** Could not be rebuilt; the old object is left in place untouched. */
66
+ | {
67
+ readonly kind: 'skipped';
68
+ readonly object: string;
69
+ readonly reason: string;
70
+ };
71
+ /**
72
+ * Re-scope every invariant object to `namespace`.
73
+ *
74
+ * Constraints first, then indexes — the constraints are
75
+ * the correctness-bearing half and should land even if an index cannot.
76
+ */
77
+ export declare function repairInvariants(sql: unknown, metric: Metric, namespace: string): Promise<RepairOutcome[]>;
78
+ /** The operator-facing sentence for one outcome. */
79
+ export declare function repairOutcomeMessage(outcome: RepairOutcome, namespace: string): string;
80
+ /**
81
+ * Run repair if asked, and report. Returns null when it did not run, so a
82
+ * caller can tell "declined" from "ran and found nothing to do".
83
+ *
84
+ * Never throws, for the same reason the audit does not: this is invoked during
85
+ * boot, and a failure here must degrade to a report rather than take down a
86
+ * service that was running fine — badly, but fine — a moment earlier.
87
+ */
88
+ export declare function repairIfRequested(ctx: {
89
+ readonly sql: unknown;
90
+ readonly namespace: string | null;
91
+ readonly env: Record<string, string | undefined>;
92
+ metric(name: string, payload?: unknown): void;
93
+ }, warn?: (message: string) => void): Promise<RepairOutcome[] | null>;
94
+ export {};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The re-scoping primitives, parameterised by target namespace.
3
+ *
4
+ * The baseline migration calls these with `CORTEXT_NAMESPACE`; the opt-in
5
+ * repair path (`./repair.ts`) calls them with the namespace the coordinator is
6
+ * actually configured for.
7
+ *
8
+ * One copy is the point. A repair path that re-declared
9
+ * this DDL would be a SECOND source of truth for what these objects are, and
10
+ * the two would drift the moment a vocabulary changed — reinstalling the exact
11
+ * class of bug that the repair exists to fix, one layer up. The
12
+ * vocabularies themselves come from the kernel vocab constants rather than
13
+ * being restated here, for the same reason.
14
+ *
15
+ * Everything here is idempotent DROP-then-CREATE. None of it decides POLICY:
16
+ * whether a duplicate kid should abort the migration or be skipped
17
+ * and reported (repair's) is the caller's call, which is why the duplicate
18
+ * count is exposed separately rather than being handled in here.
19
+ */
20
+ import type postgres from 'postgres';
21
+ type Sql = ReturnType<typeof postgres>;
22
+ type Metric = (name: string, payload?: unknown) => void;
23
+ export interface VocabConstraint {
24
+ readonly constraint: string;
25
+ readonly type: string;
26
+ readonly column: 'status' | 'priority';
27
+ readonly vocab: readonly string[];
28
+ }
29
+ export declare const VOCAB_CONSTRAINTS: readonly VocabConstraint[];
30
+ /**
31
+ * Re-scope one vocabulary CHECK to `namespace`.
32
+ *
33
+ * Returns the number of existing rows that violate it — 0 means VALIDATE
34
+ * succeeded and the constraint is fully enforced; >0 means it was left NOT
35
+ * VALID (enforcing new writes only) and an operator has rows to reconcile.
36
+ *
37
+ * The NOT VALID -> validate-inside-a-handler shape is what makes this safe to
38
+ * run against a store that has been writing UNCONSTRAINED. A plain
39
+ * `ADD CONSTRAINT ... CHECK` validates immediately and therefore fails on
40
+ * exactly the databases a repair exists for — the ones that drifted long
41
+ * enough to accumulate bad rows.
42
+ */
43
+ export declare function rescopeVocabConstraint(s: Sql, metric: Metric, { constraint, type, column, vocab }: VocabConstraint, namespace: string): Promise<number>;
44
+ /**
45
+ * Re-scope the mailbox inbox index (0003) to `namespace`.
46
+ *
47
+ * Dropped and rebuilt rather than left alongside a correctly-scoped sibling:
48
+ * two partial indexes over the same expression is one the planner can never
49
+ * use, plus write amplification on every AgentMessage insert.
50
+ */
51
+ export declare function rescopeRecipientIndex(s: Sql, namespace: string): Promise<void>;
52
+ /**
53
+ * How many kids would collide if the unique index were scoped to `namespace`.
54
+ *
55
+ * Exposed separately because a UNIQUE index has no `NOT VALID` equivalent —
56
+ * there is no "enforce for new writes only" state for an index. `CREATE UNIQUE
57
+ * INDEX` either succeeds over the whole predicate or fails outright, so the
58
+ * only way to avoid failing is to ask first. That asymmetry with the CHECK
59
+ * constraints is why repair treats the two differently.
60
+ *
61
+ * The predicate matches `rescopeKidUniqueIndex`'s exactly, including the
62
+ * tombstone exemption — an archived duplicate is still a conflict, and hard-
63
+ * deleting a retired identity frees its kid.
64
+ */
65
+ export declare function countDuplicateKids(s: Sql, namespace: string): Promise<number>;
66
+ /**
67
+ * Re-scope the AgentIdentity.kid unique index (0004) to `namespace`.
68
+ *
69
+ * THROWS on duplicate kids, and that is deliberate — it preserves 0004's
70
+ * fail-loud stance, because recipient resolution and the mailbox's
71
+ * recipient-only enforcement are both ambiguous until an operator decides
72
+ * which row owns the identity. Callers that must not abort should call
73
+ * `countDuplicateKids` first; the repair path does.
74
+ */
75
+ export declare function rescopeKidUniqueIndex(s: Sql, namespace: string): Promise<void>;
76
+ /**
77
+ * How many HostInstall keys would collide if the unique index were scoped to
78
+ * `namespace`. Asked first for the same reason as `countDuplicateKids`.
79
+ */
80
+ export declare function countDuplicateHostInstallKeys(s: Sql, namespace: string): Promise<number>;
81
+ /**
82
+ * Re-scope the HostInstall.key unique index (0008) to `namespace`. Throws on
83
+ * duplicate keys; callers that must not abort call
84
+ * `countDuplicateHostInstallKeys` first.
85
+ */
86
+ export declare function rescopeHostInstallKeyIndex(s: Sql, namespace: string): Promise<void>;
87
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The range of substrate versions both cortext plugins are built against.
3
+ *
4
+ * ONE literal, used in two places that must never disagree:
5
+ *
6
+ * 1. `contractVersion` on both plugin objects, checked by the substrate
7
+ * loader at boot, which refuses a plugin outside it.
8
+ * 2. The `@mnmal-ai/substrate` peer range in the manifest, checked by the
9
+ * package manager at install.
10
+ *
11
+ * Both exist because they guard different paths: `SUBSTRATE_PLUGIN_DIR` scans
12
+ * a directory, so a plugin dropped in by hand never meets a package manager,
13
+ * and the loader check is all that is left.
14
+ *
15
+ * The ceiling tracks actual plugin-facing compatibility, not the next major
16
+ * by reflex: widen it only after reading what the new major changed.
17
+ * `substrate-contract-parity.test.ts` holds the manifest to this literal.
18
+ */
19
+ export declare const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.0 <2.0.0";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Todo lifecycle and outcome, split. A single `status` doing both jobs makes
3
+ * `done` vs `closed` a grey distinction nobody can apply consistently.
4
+ *
5
+ * LIFECYCLE (`status`): is it live? OUTCOME (`resolution`): why did it stop?
6
+ * The same split GitHub (state + state_reason) and Jira (status + resolution)
7
+ * make.
8
+ *
9
+ * The pg CHECK admits exactly the lifecycle set; `done` / `not_started` are
10
+ * aliases the schema canonicalises on write.
11
+ */
12
+ export declare const TODO_LIFECYCLE_VOCAB: readonly ['open', 'in_progress', 'blocked', 'deferred', 'closed'];
13
+ /** Why a closed Todo stopped. `unspecified` is for migrated rows with no clear cue. */
14
+ export declare const TODO_RESOLUTION_VOCAB: readonly ['completed', 'wont_do', 'superseded', 'duplicate', 'obsolete', 'unspecified'];
15
+ export declare const TODO_PRIORITY_VOCAB: readonly ['low', 'medium', 'high'];
16
+ /** AgentMessage delivery state; the pg CHECK and the field enum share it. */
17
+ export declare const AGENT_MESSAGE_STATUS_VOCAB: readonly ['unread', 'read', 'acked'];
@@ -0,0 +1,21 @@
1
+ import { t as createCortextKernelPlugin } from "./plugin-27UpMnoE.js";
2
+ import { defineBootstrap } from "@mnmal-ai/substrate/sdk";
3
+ import { z } from "zod";
4
+
5
+ //#region src/kernel/bootstrap.ts
6
+ /**
7
+ * Structured-config bootstrap for cortext's kernel plugin
8
+ * (mirrors the recall + plane bootstrap pattern).
9
+ *
10
+ * No operator-overridable surface today — the plugin contributes a
11
+ * static migration list and nothing else. Reserved for future schema-
12
+ * level controls if any emerge.
13
+ */
14
+ const CortextKernelConfigSchema = z.object({}).strict();
15
+ const bootstrap = defineBootstrap.kernel({
16
+ schema: CortextKernelConfigSchema,
17
+ build: () => createCortextKernelPlugin()
18
+ });
19
+
20
+ //#endregion
21
+ export { bootstrap, bootstrap as default };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Boot-time reporting for the namespaces the kernel cannot follow.
3
+ *
4
+ * The cortext kernel plugin scopes every CHECK constraint and partial index
5
+ * with a `namespace = <literal>` predicate, baked in at authoring time because
6
+ * a migration cannot read config (`KernelMigrationContext` is `{ sql, metric }`).
7
+ * So a deployment that renames a namespace gets NO invariants, silently: a
8
+ * constraint scoped to a namespace with no rows passes trivially, and an index
9
+ * whose predicate no query can satisfy is just a heap scan.
10
+ *
11
+ * This warns rather than refuses. Per-cohort context stores are a designed
12
+ * capability (`bootstrap-config.test.ts` names `codex` as an override target),
13
+ * so the knob keeps working and the consequence is stated at boot.
14
+ *
15
+ * The kernel's own `invariant-audit.ts` reads the catalog too, but answers a
16
+ * different question. HERE (coordinator): "the namespace I am CONFIGURED for
17
+ * is not the one the kernel scopes to"; only the coordinator knows
18
+ * `cfg.namespace`. THERE (kernel): "my scope literal is declared by no
19
+ * coordinator", plus objects dropped, left NOT VALID, or re-scoped.
20
+ *
21
+ * Neither re-scopes automatically; that is the opt-in `./repair.ts`, because a
22
+ * store that ran unscoped may hold rows violating the vocabulary, so
23
+ * `ADD CONSTRAINT` can fail on exactly the databases a repair exists for.
24
+ * Parameterising the literals alone would not help either: migrations are
25
+ * recorded once by `id`, so a later rename never re-scopes existing objects.
26
+ */
27
+ /** One configured namespace the kernel's baked-in literals do not cover. */
28
+ export interface UncoveredNamespace {
29
+ /** Config key the operator set. */
30
+ readonly field: 'namespace' | 'architectureNamespace';
31
+ /** What they set it to. */
32
+ readonly configured: string;
33
+ /** The literal the kernel actually scopes its invariants to. */
34
+ readonly scopedTo: string;
35
+ }
36
+ /**
37
+ * Which configured namespaces the kernel does not cover. Empty is the
38
+ * healthy case.
39
+ *
40
+ * Pure and separately exported so the condition can be tested directly rather
41
+ * than through a lifecycle harness — the reporting below is a thin shell over
42
+ * it.
43
+ */
44
+ export declare function uncoveredNamespaces(cfg: {
45
+ namespace: string;
46
+ architectureNamespace: string;
47
+ }): UncoveredNamespace[];
48
+ /** The operator-facing sentence for one uncovered namespace. */
49
+ export declare function uncoveredNamespaceMessage({ field, configured, scopedTo }: UncoveredNamespace): string;
50
+ /**
51
+ * Emit the warnings at plugin start. Returns what it reported, so a caller (or
52
+ * a test) can assert on it without capturing the console.
53
+ *
54
+ * Both sinks on purpose: `metric` is the
55
+ * structured channel an operator can alert on, and the console line is what
56
+ * actually appears in the server journal next to substrate's own loader
57
+ * warnings, where someone will see it.
58
+ */
59
+ export declare function reportUncoveredNamespaces(cfg: {
60
+ namespace: string;
61
+ architectureNamespace: string;
62
+ }, metric: (name: string, payload?: unknown) => void, warn?: (message: string) => void): UncoveredNamespace[];
package/dist/kernel.js ADDED
@@ -0,0 +1,3 @@
1
+ import { C as findingMessage, D as ARCHITECTURE_NAMESPACE, E as scopedNamespace, O as CORTEXT_COORDINATOR_NAME, S as auditObjects, T as resolveCortextNamespaces, _ as TODO_LIFECYCLE_VOCAB, a as repairInvariants, b as INVARIANT_OBJECTS, c as cortextBaselineMigration, d as countDuplicateKids, f as rescopeHostInstallKeyIndex, g as AGENT_MESSAGE_STATUS_VOCAB, h as rescopeVocabConstraint, i as repairIfRequested, k as CORTEXT_NAMESPACE, l as VOCAB_CONSTRAINTS, m as rescopeRecipientIndex, n as SUBSTRATE_CONTRACT_RANGE, o as repairOutcomeMessage, p as rescopeKidUniqueIndex, r as REPAIR_ENV_VAR, s as repairRequested, t as createCortextKernelPlugin, u as countDuplicateHostInstallKeys, v as TODO_PRIORITY_VOCAB, w as reportInvariantAudit, x as auditNamespaceCoverage, y as TODO_RESOLUTION_VOCAB } from "./plugin-27UpMnoE.js";
2
+
3
+ export { AGENT_MESSAGE_STATUS_VOCAB, ARCHITECTURE_NAMESPACE, CORTEXT_COORDINATOR_NAME, CORTEXT_NAMESPACE, INVARIANT_OBJECTS, REPAIR_ENV_VAR, SUBSTRATE_CONTRACT_RANGE, TODO_LIFECYCLE_VOCAB, TODO_PRIORITY_VOCAB, TODO_RESOLUTION_VOCAB, VOCAB_CONSTRAINTS, auditNamespaceCoverage, auditObjects, cortextBaselineMigration, countDuplicateHostInstallKeys, countDuplicateKids, createCortextKernelPlugin, findingMessage, repairIfRequested, repairInvariants, repairOutcomeMessage, repairRequested, reportInvariantAudit, rescopeHostInstallKeyIndex, rescopeKidUniqueIndex, rescopeRecipientIndex, rescopeVocabConstraint, resolveCortextNamespaces, scopedNamespace };