@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.
- package/LICENSE +202 -0
- package/README.md +70 -1
- package/config.schema.json +62 -0
- package/default-config.json +10 -0
- package/dist/auth-policy.d.ts +44 -0
- package/dist/bootstrap.d.ts +36 -0
- package/dist/bootstrap.js +69 -0
- package/dist/compact.d.ts +37 -0
- package/dist/config.d.ts +40 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +5 -0
- package/dist/kernel/bootstrap.d.ts +11 -0
- package/dist/kernel/cortext-namespace.d.ts +96 -0
- package/dist/kernel/index.d.ts +15 -0
- package/dist/kernel/invariant-audit.d.ts +190 -0
- package/dist/kernel/migrations/0001_baseline.d.ts +11 -0
- package/dist/kernel/plugin.d.ts +29 -0
- package/dist/kernel/repair.d.ts +94 -0
- package/dist/kernel/rescope.d.ts +87 -0
- package/dist/kernel/substrate-contract.d.ts +19 -0
- package/dist/kernel/todo-vocab.d.ts +17 -0
- package/dist/kernel-bootstrap.js +21 -0
- package/dist/kernel-invariants.d.ts +62 -0
- package/dist/kernel.js +3 -0
- package/dist/mailbox.d.ts +80 -0
- package/dist/namespace.d.ts +159 -0
- package/dist/nl-rulings.d.ts +76 -0
- package/dist/onboard.d.ts +15 -0
- package/dist/participants.d.ts +32 -0
- package/dist/plugin-27UpMnoE.js +811 -0
- package/dist/release-surface.d.ts +206 -0
- package/dist/src-BKaGTnRw.js +1931 -0
- package/dist/todo-resolution.d.ts +18 -0
- package/kernel.config.schema.json +8 -0
- package/kernel.default-config.json +1 -0
- package/package.json +80 -4
|
@@ -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 };
|