@mnmal-ai/cortext 1.0.0-alpha.0 → 1.0.0-alpha.10
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/dist/bootstrap.js +2 -2
- package/dist/index.js +2 -2
- package/dist/kernel/cortext-namespace.d.ts +2 -2
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/invariant-audit.d.ts +4 -3
- package/dist/kernel/layout.d.ts +16 -0
- package/dist/kernel/migrations/0002_drop_engine_owned_indexes.d.ts +11 -0
- package/dist/kernel/rescope.d.ts +0 -8
- package/dist/kernel/substrate-contract.d.ts +1 -1
- package/dist/kernel-bootstrap.js +1 -1
- package/dist/kernel.js +2 -2
- package/dist/participants.d.ts +15 -1
- package/dist/{plugin-27UpMnoE.js → plugin-CujP_pvT.js} +62 -58
- package/dist/release-surface.d.ts +3 -4
- package/dist/{src-BKaGTnRw.js → src-DAeiNhDw.js} +215 -74
- package/package.json +3 -3
package/dist/bootstrap.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as isMonotonicAuth, t as createCortextPlugin, w as DEFAULT_AUTH } from "./src-
|
|
2
|
-
import {
|
|
1
|
+
import { E as isMonotonicAuth, t as createCortextPlugin, w as DEFAULT_AUTH } from "./src-DAeiNhDw.js";
|
|
2
|
+
import { A as CORTEXT_NAMESPACE, O as ARCHITECTURE_NAMESPACE } from "./plugin-CujP_pvT.js";
|
|
3
3
|
import "./kernel.js";
|
|
4
4
|
import { defineBootstrap } from "@mnmal-ai/substrate/sdk";
|
|
5
5
|
import { z } from "zod";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as runCompact, E as isMonotonicAuth, S as registerCompactRoute, T as buildAuthPolicies, _ as TODO_RESOLUTION_ALIASES, a as classSatisfies, b as buildArchitectureNamespace, c as AGENT_RULE_CATEGORY_VOCAB, d as ARCHITECTURE_TYPE_NAMES, f as CLAUDE_TYPE_NAMES, g as HOST_INSTALL_FRAGMENT_STATES, h as HOST_INSTALL_CHECK_STATES, i as bumpClass, l as AGENT_RULE_SEVERITY_ALIASES, m as DECISION_STATUS_VOCAB, n as inferTodoResolution, o as classifySurfaceChange, p as DECISION_STATUS_ALIASES, r as buildReleaseSurface, s as registerOnboardRoute, t as createCortextPlugin, u as AGENT_RULE_SEVERITY_VOCAB, v as TODO_STATUS_ALIASES, w as DEFAULT_AUTH, x as buildClaudeNamespace, y as TODO_STATUS_IMPLIES } from "./src-
|
|
2
|
-
import {
|
|
1
|
+
import { C as runCompact, E as isMonotonicAuth, S as registerCompactRoute, T as buildAuthPolicies, _ as TODO_RESOLUTION_ALIASES, a as classSatisfies, b as buildArchitectureNamespace, c as AGENT_RULE_CATEGORY_VOCAB, d as ARCHITECTURE_TYPE_NAMES, f as CLAUDE_TYPE_NAMES, g as HOST_INSTALL_FRAGMENT_STATES, h as HOST_INSTALL_CHECK_STATES, i as bumpClass, l as AGENT_RULE_SEVERITY_ALIASES, m as DECISION_STATUS_VOCAB, n as inferTodoResolution, o as classifySurfaceChange, p as DECISION_STATUS_ALIASES, r as buildReleaseSurface, s as registerOnboardRoute, t as createCortextPlugin, u as AGENT_RULE_SEVERITY_VOCAB, v as TODO_STATUS_ALIASES, w as DEFAULT_AUTH, x as buildClaudeNamespace, y as TODO_STATUS_IMPLIES } from "./src-DAeiNhDw.js";
|
|
2
|
+
import { _ as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-CujP_pvT.js";
|
|
3
3
|
import "./kernel.js";
|
|
4
4
|
|
|
5
5
|
export { AGENT_MESSAGE_STATUS_VOCAB, AGENT_RULE_CATEGORY_VOCAB, AGENT_RULE_SEVERITY_ALIASES, AGENT_RULE_SEVERITY_VOCAB, ARCHITECTURE_TYPE_NAMES, CLAUDE_TYPE_NAMES, DECISION_STATUS_ALIASES, DECISION_STATUS_VOCAB, DEFAULT_AUTH, HOST_INSTALL_CHECK_STATES, HOST_INSTALL_FRAGMENT_STATES, TODO_RESOLUTION_ALIASES, TODO_STATUS_ALIASES, TODO_STATUS_IMPLIES, buildArchitectureNamespace, buildAuthPolicies, buildClaudeNamespace, buildReleaseSurface, bumpClass, classSatisfies, classifySurfaceChange, createCortextPlugin, inferTodoResolution, isMonotonicAuth, registerCompactRoute, registerOnboardRoute, runCompact };
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
export declare const CORTEXT_NAMESPACE = "cortext";
|
|
46
46
|
/**
|
|
47
47
|
* The namespace the architecture-findings surface registers under, and which
|
|
48
|
-
*
|
|
48
|
+
* the pre-baseline body-normalization migration was scoped to.
|
|
49
49
|
*
|
|
50
50
|
* Separate from CORTEXT_NAMESPACE because the two have separate operator
|
|
51
51
|
* knobs and diverging lifecycle policies (compaction, retention, audit) — but
|
|
52
52
|
* subject to exactly the same gap, which is why it is stated here rather than
|
|
53
|
-
* left as a bare literal inside
|
|
53
|
+
* left as a bare literal inside that migration.
|
|
54
54
|
*/
|
|
55
55
|
export declare const ARCHITECTURE_NAMESPACE = "architecture";
|
|
56
56
|
/**
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
export { ARCHITECTURE_NAMESPACE, CORTEXT_COORDINATOR_NAME, CORTEXT_NAMESPACE } from './cortext-namespace.js';
|
|
9
9
|
export { auditNamespaceCoverage, auditObjects, type Finding, findingMessage, INVARIANT_OBJECTS, type InvariantObject, type PluginNamespace, reportInvariantAudit, resolveCortextNamespaces, scopedNamespace, type UntargetableReason } from './invariant-audit.js';
|
|
10
10
|
export { cortextBaselineMigration } from './migrations/0001_baseline.js';
|
|
11
|
+
export { cortextDropEngineOwnedIndexesMigration, ENGINE_OWNED_INDEXES } from './migrations/0002_drop_engine_owned_indexes.js';
|
|
11
12
|
export { createCortextKernelPlugin } from './plugin.js';
|
|
12
13
|
export { REPAIR_ENV_VAR, type RepairOutcome, repairIfRequested, repairInvariants, repairOutcomeMessage, repairRequested } from './repair.js';
|
|
13
|
-
export { countDuplicateHostInstallKeys, countDuplicateKids, rescopeHostInstallKeyIndex, rescopeKidUniqueIndex,
|
|
14
|
+
export { countDuplicateHostInstallKeys, countDuplicateKids, rescopeHostInstallKeyIndex, rescopeKidUniqueIndex, rescopeVocabConstraint, VOCAB_CONSTRAINTS, type VocabConstraint } from './rescope.js';
|
|
14
15
|
export { SUBSTRATE_CONTRACT_RANGE } from './substrate-contract.js';
|
|
15
16
|
export { AGENT_MESSAGE_STATUS_VOCAB, TODO_LIFECYCLE_VOCAB, TODO_PRIORITY_VOCAB, TODO_RESOLUTION_VOCAB } from './todo-vocab.js';
|
|
@@ -51,8 +51,8 @@ export interface InvariantObject {
|
|
|
51
51
|
* if this list and the migrations disagree — which is the check that actually
|
|
52
52
|
* holds them together.
|
|
53
53
|
*
|
|
54
|
-
* Note there is no `architecture/*` entry, and that is correct:
|
|
55
|
-
*
|
|
54
|
+
* Note there is no `architecture/*` entry, and that is correct: the
|
|
55
|
+
* pre-baseline architecture migration was a one-time body normalisation, not a standing invariant. A drifted
|
|
56
56
|
* architecture namespace means that normalisation never ran for those rows —
|
|
57
57
|
* real, but a different and lesser thing than "kid uniqueness is not held".
|
|
58
58
|
*/
|
|
@@ -89,6 +89,7 @@ export type Finding =
|
|
|
89
89
|
readonly kind: 'object_misscoped';
|
|
90
90
|
readonly object: InvariantObject;
|
|
91
91
|
readonly scopedTo: string | null;
|
|
92
|
+
readonly expected: string;
|
|
92
93
|
}
|
|
93
94
|
/** Present and correctly scoped, but NOT VALID — new writes only. */
|
|
94
95
|
| {
|
|
@@ -148,7 +149,7 @@ export declare function scopedNamespace(definition: string): string | null;
|
|
|
148
149
|
* gains a filter, an early return or a per-object skip, the completion metric
|
|
149
150
|
* reports what was actually inspected instead of a promise.
|
|
150
151
|
*/
|
|
151
|
-
export declare function auditObjects(sql: unknown): Promise<{
|
|
152
|
+
export declare function auditObjects(sql: unknown, target?: string): Promise<{
|
|
152
153
|
findings: Finding[];
|
|
153
154
|
checked: number;
|
|
154
155
|
}>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which storage layout `rows` has: one shared table (`legacy`) or a table
|
|
3
|
+
* partitioned by namespace and type (`partitioned`).
|
|
4
|
+
*
|
|
5
|
+
* On a partitioned `rows`, Postgres refuses a UNIQUE index that lacks the
|
|
6
|
+
* partition keys, and clones every parent-level index and CHECK onto every
|
|
7
|
+
* other plugin's partitions. So cortext's raw DDL runs on `legacy` only; on
|
|
8
|
+
* `partitioned` the engine builds cortext's declared and `indexed: true`
|
|
9
|
+
* indexes per partition.
|
|
10
|
+
*/
|
|
11
|
+
export type StoreLayout = 'legacy' | 'partitioned';
|
|
12
|
+
/**
|
|
13
|
+
* The layout as the catalog reports it. Migrations read `ctx.layout` instead;
|
|
14
|
+
* this is for boot-time code, whose lifecycle context does not carry it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function storeLayout(sql: unknown): Promise<StoreLayout>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drops the hand-made indexes 0001 used to build on a legacy `rows`, now that
|
|
3
|
+
* the engine serves the same reads: the derived recipient index for the
|
|
4
|
+
* inbox, and the `indexed: true` indexes on project and status.
|
|
5
|
+
*
|
|
6
|
+
* A partitioned `rows` never had them. Idempotent, like 0001: the runner does
|
|
7
|
+
* not wrap `apply` in a transaction.
|
|
8
|
+
*/
|
|
9
|
+
import type { KernelMigration } from '@mnmal-ai/substrate/sdk';
|
|
10
|
+
export declare const ENGINE_OWNED_INDEXES: readonly ['rows_agent_message_recipient_idx', 'rows_ns_type_project_idx', 'rows_ns_type_status_idx', 'rows_ns_type_priority_idx', 'rows_cold_start_idx'];
|
|
11
|
+
export declare const cortextDropEngineOwnedIndexesMigration: KernelMigration;
|
package/dist/kernel/rescope.d.ts
CHANGED
|
@@ -41,14 +41,6 @@ export declare const VOCAB_CONSTRAINTS: readonly VocabConstraint[];
|
|
|
41
41
|
* enough to accumulate bad rows.
|
|
42
42
|
*/
|
|
43
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
44
|
/**
|
|
53
45
|
* How many kids would collide if the unique index were scoped to `namespace`.
|
|
54
46
|
*
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
* by reflex: widen it only after reading what the new major changed.
|
|
17
17
|
* `substrate-contract-parity.test.ts` holds the manifest to this literal.
|
|
18
18
|
*/
|
|
19
|
-
export declare const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.
|
|
19
|
+
export declare const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.10 <2.0.0";
|
package/dist/kernel-bootstrap.js
CHANGED
package/dist/kernel.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as CORTEXT_NAMESPACE, C as auditObjects, D as scopedNamespace, E as resolveCortextNamespaces, O as ARCHITECTURE_NAMESPACE, S as auditNamespaceCoverage, T as reportInvariantAudit, _ as AGENT_MESSAGE_STATUS_VOCAB, a as repairInvariants, b as TODO_RESOLUTION_VOCAB, c as ENGINE_OWNED_INDEXES, d as VOCAB_CONSTRAINTS, f as countDuplicateHostInstallKeys, g as rescopeVocabConstraint, h as rescopeKidUniqueIndex, i as repairIfRequested, k as CORTEXT_COORDINATOR_NAME, l as cortextDropEngineOwnedIndexesMigration, m as rescopeHostInstallKeyIndex, n as SUBSTRATE_CONTRACT_RANGE, o as repairOutcomeMessage, p as countDuplicateKids, r as REPAIR_ENV_VAR, s as repairRequested, t as createCortextKernelPlugin, u as cortextBaselineMigration, v as TODO_LIFECYCLE_VOCAB, w as findingMessage, x as INVARIANT_OBJECTS, y as TODO_PRIORITY_VOCAB } from "./plugin-CujP_pvT.js";
|
|
2
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,
|
|
3
|
+
export { AGENT_MESSAGE_STATUS_VOCAB, ARCHITECTURE_NAMESPACE, CORTEXT_COORDINATOR_NAME, CORTEXT_NAMESPACE, ENGINE_OWNED_INDEXES, INVARIANT_OBJECTS, REPAIR_ENV_VAR, SUBSTRATE_CONTRACT_RANGE, TODO_LIFECYCLE_VOCAB, TODO_PRIORITY_VOCAB, TODO_RESOLUTION_VOCAB, VOCAB_CONSTRAINTS, auditNamespaceCoverage, auditObjects, cortextBaselineMigration, cortextDropEngineOwnedIndexesMigration, countDuplicateHostInstallKeys, countDuplicateKids, createCortextKernelPlugin, findingMessage, repairIfRequested, repairInvariants, repairOutcomeMessage, repairRequested, reportInvariantAudit, rescopeHostInstallKeyIndex, rescopeKidUniqueIndex, rescopeVocabConstraint, resolveCortextNamespaces, scopedNamespace };
|
package/dist/participants.d.ts
CHANGED
|
@@ -21,12 +21,26 @@ export declare function isOperator(ctx: RequestContext): boolean;
|
|
|
21
21
|
* neither is a bystander's call.
|
|
22
22
|
*/
|
|
23
23
|
export declare function messageParticipantsOnly(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
|
|
24
|
+
/**
|
|
25
|
+
* AgentIdentity create: a non-operator may not file a row whose `host` is a
|
|
26
|
+
* machine other than the one its kid names. The fleet audit trusts `host`
|
|
27
|
+
* (and `roaming` on that host) when it decides who may write a host's
|
|
28
|
+
* HostInstall rows.
|
|
29
|
+
*/
|
|
30
|
+
export declare function identityCreateGuard(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
|
|
24
31
|
/**
|
|
25
32
|
* AgentIdentity update: the identity itself (the caller's kid is the row's
|
|
26
33
|
* kid) or an operator. `kid` is the row's identity, so changing it is an
|
|
27
34
|
* operator act even on your own row: otherwise a row could be renamed onto a
|
|
28
|
-
* kid whose mail it would then receive.
|
|
35
|
+
* kid whose mail it would then receive. `host` must stay the kid's machine,
|
|
36
|
+
* and reactivating a retired row is an operator act.
|
|
29
37
|
*/
|
|
30
38
|
export declare function identitySelfOnly(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
|
|
39
|
+
/**
|
|
40
|
+
* Todo create/update: `resolution` says why a CLOSED Todo stopped, so a live
|
|
41
|
+
* Todo carries none. A resolution with a live resulting status is refused,
|
|
42
|
+
* and moving a Todo back to a live status clears the one it had.
|
|
43
|
+
*/
|
|
44
|
+
export declare function todoResolutionGuard(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver, statusAliases: Readonly<Record<string, string>>): RegistryEntryResolver;
|
|
31
45
|
/** AgentIdentity delete: operators only. Mail hangs off these rows. */
|
|
32
46
|
export declare function operatorOnly(name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
const CORTEXT_NAMESPACE = "cortext";
|
|
47
47
|
/**
|
|
48
48
|
* The namespace the architecture-findings surface registers under, and which
|
|
49
|
-
*
|
|
49
|
+
* the pre-baseline body-normalization migration was scoped to.
|
|
50
50
|
*
|
|
51
51
|
* Separate from CORTEXT_NAMESPACE because the two have separate operator
|
|
52
52
|
* knobs and diverging lifecycle policies (compaction, retention, audit) — but
|
|
53
53
|
* subject to exactly the same gap, which is why it is stated here rather than
|
|
54
|
-
* left as a bare literal inside
|
|
54
|
+
* left as a bare literal inside that migration.
|
|
55
55
|
*/
|
|
56
56
|
const ARCHITECTURE_NAMESPACE = "architecture";
|
|
57
57
|
/**
|
|
@@ -96,6 +96,19 @@ const CORTEXT_PROMOTED_COLUMNS = {
|
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/kernel/layout.ts
|
|
101
|
+
/**
|
|
102
|
+
* The layout as the catalog reports it. Migrations read `ctx.layout` instead;
|
|
103
|
+
* this is for boot-time code, whose lifecycle context does not carry it.
|
|
104
|
+
*/
|
|
105
|
+
async function storeLayout(sql) {
|
|
106
|
+
const [row] = await sql`
|
|
107
|
+
SELECT c.relkind AS kind FROM pg_class c WHERE c.oid = to_regclass('rows')
|
|
108
|
+
`;
|
|
109
|
+
return row?.kind === "p" ? "partitioned" : "legacy";
|
|
110
|
+
}
|
|
111
|
+
|
|
99
112
|
//#endregion
|
|
100
113
|
//#region src/kernel/invariant-audit.ts
|
|
101
114
|
/**
|
|
@@ -141,8 +154,8 @@ const CORTEXT_PROMOTED_COLUMNS = {
|
|
|
141
154
|
* if this list and the migrations disagree — which is the check that actually
|
|
142
155
|
* holds them together.
|
|
143
156
|
*
|
|
144
|
-
* Note there is no `architecture/*` entry, and that is correct:
|
|
145
|
-
*
|
|
157
|
+
* Note there is no `architecture/*` entry, and that is correct: the
|
|
158
|
+
* pre-baseline architecture migration was a one-time body normalisation, not a standing invariant. A drifted
|
|
146
159
|
* architecture namespace means that normalisation never ran for those rows —
|
|
147
160
|
* real, but a different and lesser thing than "kid uniqueness is not held".
|
|
148
161
|
*/
|
|
@@ -159,12 +172,6 @@ const INVARIANT_OBJECTS = [
|
|
|
159
172
|
migration: "0001_baseline",
|
|
160
173
|
guarantee: "Todo.priority is restricted to its controlled vocabulary"
|
|
161
174
|
},
|
|
162
|
-
{
|
|
163
|
-
name: "rows_agent_message_recipient_idx",
|
|
164
|
-
kind: "index",
|
|
165
|
-
migration: "0001_baseline",
|
|
166
|
-
guarantee: "mailbox inbox reads are index-backed rather than heap scans"
|
|
167
|
-
},
|
|
168
175
|
{
|
|
169
176
|
name: "rows_agent_identity_kid_unique",
|
|
170
177
|
kind: "index",
|
|
@@ -280,7 +287,7 @@ async function readCatalog(sql) {
|
|
|
280
287
|
* gains a filter, an early return or a per-object skip, the completion metric
|
|
281
288
|
* reports what was actually inspected instead of a promise.
|
|
282
289
|
*/
|
|
283
|
-
async function auditObjects(sql) {
|
|
290
|
+
async function auditObjects(sql, target = CORTEXT_NAMESPACE) {
|
|
284
291
|
const catalog = await readCatalog(sql);
|
|
285
292
|
const findings = [];
|
|
286
293
|
let checked = 0;
|
|
@@ -295,11 +302,12 @@ async function auditObjects(sql) {
|
|
|
295
302
|
continue;
|
|
296
303
|
}
|
|
297
304
|
const scope = scopedNamespace(row.definition);
|
|
298
|
-
if (scope !==
|
|
305
|
+
if (scope !== target) {
|
|
299
306
|
findings.push({
|
|
300
307
|
kind: "object_misscoped",
|
|
301
308
|
object,
|
|
302
|
-
scopedTo: scope
|
|
309
|
+
scopedTo: scope,
|
|
310
|
+
expected: target
|
|
303
311
|
});
|
|
304
312
|
continue;
|
|
305
313
|
}
|
|
@@ -320,7 +328,7 @@ function findingMessage(finding) {
|
|
|
320
328
|
case "namespace_drifted": return `${head} the cortext coordinator is configured for namespace '${finding.configured}', but every kernel constraint and index is scoped to '${finding.scopedTo}'. So all of them are INACTIVE for '${finding.configured}': Todo/AgentMessage vocabularies unenforced, the mailbox inbox index covering no rows, and AgentIdentity.kid uniqueness NOT held. Writes succeed and queries answer, so nothing else will report this. Use '${finding.scopedTo}', or accept the loss deliberately.`;
|
|
321
329
|
case "namespace_uncovered": return `${head} invariants are scoped to namespace '${finding.scopedTo}', which no loaded coordinator declares (declared: ${finding.declared.join(", ") || "none"}). Every kernel guarantee below is therefore INACTIVE, and nothing else will report it — a constraint scoped to a namespace with no rows passes trivially. ` + (finding.reason === "host_does_not_attribute" ? "This substrate does not attribute namespaces to their plugin (added in 5.1.0), so which namespace REPLACED it cannot be determined — upgrade the binary to get a named target rather than this list." : "This substrate does attribute, and the cortext coordinator declared no namespace at all — it is not loaded. The objects are orphaned rather than misscoped.") + " Set SYNOPTIC_CORTEXT_REPAIR=1 for one restart to re-scope.";
|
|
322
330
|
case "object_missing": return `${head} ${finding.object.kind} '${finding.object.name}' (${finding.object.migration}) is ABSENT. Lost guarantee: ${finding.object.guarantee}. Migrations are recorded once by id, so this will NOT be reinstalled by a restart.`;
|
|
323
|
-
case "object_misscoped": return `${head} ${finding.object.kind} '${finding.object.name}' is scoped to ${finding.scopedTo === null ? "no namespace" : `'${finding.scopedTo}'`}, not '${
|
|
331
|
+
case "object_misscoped": return `${head} ${finding.object.kind} '${finding.object.name}' is scoped to ${finding.scopedTo === null ? "no namespace" : `'${finding.scopedTo}'`}, not '${finding.expected}'. Lost guarantee: ${finding.object.guarantee}. It exists, so nothing errors; it simply matches no rows.`;
|
|
324
332
|
case "object_not_validated": return `${head} ${finding.object.kind} '${finding.object.name}' exists but is NOT VALID — enforced for new writes only, and existing rows were never checked against it. Guarantee held only going forward: ${finding.object.guarantee}.`;
|
|
325
333
|
}
|
|
326
334
|
}
|
|
@@ -352,11 +360,14 @@ function findingMessage(finding) {
|
|
|
352
360
|
* structured channel this belongs in.
|
|
353
361
|
*/
|
|
354
362
|
async function reportInvariantAudit(ctx, warn = console.warn) {
|
|
355
|
-
|
|
363
|
+
let findings = [...auditNamespaceCoverage(ctx.coordinatorNamespaces, ctx.coordinatorNamespacesByPlugin)];
|
|
364
|
+
const target = ctx.coordinatorNamespaces.includes("cortext") ? CORTEXT_NAMESPACE : resolveCortextNamespaces(ctx.coordinatorNamespacesByPlugin)?.[0] ?? "cortext";
|
|
356
365
|
const catalogRead = ctx.sql !== null && ctx.sql !== void 0;
|
|
357
366
|
let objectsChecked = 0;
|
|
358
|
-
|
|
359
|
-
|
|
367
|
+
const layout = catalogRead ? await storeLayout(ctx.sql).catch(() => "legacy") : null;
|
|
368
|
+
if (catalogRead && layout === "legacy") try {
|
|
369
|
+
const result = await auditObjects(ctx.sql, target);
|
|
370
|
+
if (target !== "cortext" && result.findings.length === 0) findings = findings.filter((f) => f.kind !== "namespace_drifted");
|
|
360
371
|
findings.push(...result.findings);
|
|
361
372
|
objectsChecked = result.checked;
|
|
362
373
|
} catch (error) {
|
|
@@ -378,7 +389,8 @@ async function reportInvariantAudit(ctx, warn = console.warn) {
|
|
|
378
389
|
ctx.metric("cortext_kernel.invariant_audit_complete", {
|
|
379
390
|
checked: objectsChecked,
|
|
380
391
|
findings: findings.length,
|
|
381
|
-
catalogRead
|
|
392
|
+
catalogRead,
|
|
393
|
+
layout
|
|
382
394
|
});
|
|
383
395
|
return findings;
|
|
384
396
|
}
|
|
@@ -505,24 +517,6 @@ async function rescopeVocabConstraint(s, metric, { constraint, type, column, voc
|
|
|
505
517
|
return violations;
|
|
506
518
|
}
|
|
507
519
|
/**
|
|
508
|
-
* Re-scope the mailbox inbox index (0003) to `namespace`.
|
|
509
|
-
*
|
|
510
|
-
* Dropped and rebuilt rather than left alongside a correctly-scoped sibling:
|
|
511
|
-
* two partial indexes over the same expression is one the planner can never
|
|
512
|
-
* use, plus write amplification on every AgentMessage insert.
|
|
513
|
-
*/
|
|
514
|
-
async function rescopeRecipientIndex(s, namespace) {
|
|
515
|
-
await s.unsafe("DROP INDEX IF EXISTS rows_agent_message_recipient_idx");
|
|
516
|
-
await s.unsafe(`
|
|
517
|
-
CREATE INDEX IF NOT EXISTS rows_agent_message_recipient_idx
|
|
518
|
-
ON rows ((body->>'recipient'), status)
|
|
519
|
-
WHERE namespace = '${namespace}'
|
|
520
|
-
AND type = 'AgentMessage'
|
|
521
|
-
AND deleted_at IS NULL
|
|
522
|
-
AND archived IS NOT TRUE
|
|
523
|
-
`);
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
520
|
* How many kids would collide if the unique index were scoped to `namespace`.
|
|
527
521
|
*
|
|
528
522
|
* Exposed separately because a UNIQUE index has no `NOT VALID` equivalent —
|
|
@@ -606,7 +600,8 @@ async function rescopeHostInstallKeyIndex(s, namespace) {
|
|
|
606
600
|
//#region src/kernel/migrations/0001_baseline.ts
|
|
607
601
|
const cortextBaselineMigration = {
|
|
608
602
|
id: "0001_baseline",
|
|
609
|
-
apply: async (
|
|
603
|
+
apply: async (ctx) => {
|
|
604
|
+
const { sql, metric } = ctx;
|
|
610
605
|
if (sql === null) return;
|
|
611
606
|
const s = sql;
|
|
612
607
|
await s.unsafe(`
|
|
@@ -616,22 +611,12 @@ const cortextBaselineMigration = {
|
|
|
616
611
|
ADD COLUMN IF NOT EXISTS status text NULL,
|
|
617
612
|
ADD COLUMN IF NOT EXISTS priority text NULL
|
|
618
613
|
`);
|
|
619
|
-
|
|
620
|
-
"archived",
|
|
621
|
-
"project",
|
|
622
|
-
"status",
|
|
623
|
-
"priority"
|
|
624
|
-
]) await s.unsafe(`
|
|
625
|
-
CREATE INDEX IF NOT EXISTS rows_ns_type_${column}_idx
|
|
626
|
-
ON rows (namespace, type, ${column}) WHERE deleted_at IS NULL
|
|
627
|
-
`);
|
|
614
|
+
if (ctx.layout === "partitioned") return;
|
|
628
615
|
await s.unsafe(`
|
|
629
|
-
CREATE INDEX IF NOT EXISTS
|
|
630
|
-
ON rows (namespace, type,
|
|
631
|
-
WHERE deleted_at IS NULL AND archived IS NOT TRUE
|
|
616
|
+
CREATE INDEX IF NOT EXISTS rows_ns_type_archived_idx
|
|
617
|
+
ON rows (namespace, type, archived) WHERE deleted_at IS NULL
|
|
632
618
|
`);
|
|
633
619
|
for (const spec of VOCAB_CONSTRAINTS) await rescopeVocabConstraint(s, metric, spec, CORTEXT_NAMESPACE);
|
|
634
|
-
await rescopeRecipientIndex(s, CORTEXT_NAMESPACE);
|
|
635
620
|
await rescopeKidUniqueIndex(s, CORTEXT_NAMESPACE);
|
|
636
621
|
await rescopeHostInstallKeyIndex(s, CORTEXT_NAMESPACE);
|
|
637
622
|
},
|
|
@@ -644,6 +629,24 @@ const cortextBaselineMigration = {
|
|
|
644
629
|
}
|
|
645
630
|
};
|
|
646
631
|
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/kernel/migrations/0002_drop_engine_owned_indexes.ts
|
|
634
|
+
const ENGINE_OWNED_INDEXES = [
|
|
635
|
+
"rows_agent_message_recipient_idx",
|
|
636
|
+
"rows_ns_type_project_idx",
|
|
637
|
+
"rows_ns_type_status_idx",
|
|
638
|
+
"rows_ns_type_priority_idx",
|
|
639
|
+
"rows_cold_start_idx"
|
|
640
|
+
];
|
|
641
|
+
const cortextDropEngineOwnedIndexesMigration = {
|
|
642
|
+
id: "0002_drop_engine_owned_indexes",
|
|
643
|
+
apply: async ({ sql, layout }) => {
|
|
644
|
+
if (sql === null || layout === "partitioned") return;
|
|
645
|
+
await sql.unsafe(`DROP INDEX IF EXISTS ${ENGINE_OWNED_INDEXES.join(", ")}`);
|
|
646
|
+
},
|
|
647
|
+
rollback: async () => {}
|
|
648
|
+
};
|
|
649
|
+
|
|
647
650
|
//#endregion
|
|
648
651
|
//#region src/kernel/repair.ts
|
|
649
652
|
/** The env var that opts a deployment in, for one restart. */
|
|
@@ -681,11 +684,6 @@ async function repairInvariants(sql, metric, namespace) {
|
|
|
681
684
|
object: spec.constraint
|
|
682
685
|
});
|
|
683
686
|
}
|
|
684
|
-
await rescopeRecipientIndex(s, namespace);
|
|
685
|
-
outcomes.push({
|
|
686
|
-
kind: "repaired",
|
|
687
|
-
object: "rows_agent_message_recipient_idx"
|
|
688
|
-
});
|
|
689
687
|
const duplicates = await countDuplicateKids(s, namespace);
|
|
690
688
|
if (duplicates > 0) outcomes.push({
|
|
691
689
|
kind: "skipped",
|
|
@@ -742,6 +740,11 @@ async function repairIfRequested(ctx, warn = console.warn) {
|
|
|
742
740
|
ctx.metric("cortext_kernel.repair_declined", { reason: "target_unknown" });
|
|
743
741
|
return null;
|
|
744
742
|
}
|
|
743
|
+
if (await storeLayout(ctx.sql).catch(() => "legacy") === "partitioned") {
|
|
744
|
+
warn(`[cortext-kernel] ${REPAIR_ENV_VAR} is set, but rows is partitioned: the engine owns cortext's indexes there and there are no parent-level objects to repair. Nothing to do.`);
|
|
745
|
+
ctx.metric("cortext_kernel.repair_declined", { reason: "partitioned" });
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
745
748
|
warn(`[cortext-kernel] ${REPAIR_ENV_VAR} is set — re-scoping kernel invariants to '${ctx.namespace}'. This takes ACCESS EXCLUSIVE on rows and briefly blocks writes.` + (ctx.namespace === "cortext" ? " Target matches the built-in scope literal, so this is a no-op rebuild." : ""));
|
|
746
749
|
try {
|
|
747
750
|
const outcomes = await repairInvariants(ctx.sql, ctx.metric, ctx.namespace);
|
|
@@ -786,7 +789,7 @@ async function repairIfRequested(ctx, warn = console.warn) {
|
|
|
786
789
|
* by reflex: widen it only after reading what the new major changed.
|
|
787
790
|
* `substrate-contract-parity.test.ts` holds the manifest to this literal.
|
|
788
791
|
*/
|
|
789
|
-
const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.
|
|
792
|
+
const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.10 <2.0.0";
|
|
790
793
|
|
|
791
794
|
//#endregion
|
|
792
795
|
//#region src/kernel/plugin.ts
|
|
@@ -796,6 +799,7 @@ function createCortextKernelPlugin() {
|
|
|
796
799
|
contractVersion: SUBSTRATE_CONTRACT_RANGE,
|
|
797
800
|
onKernelReady: async (ctx) => {
|
|
798
801
|
await reportInvariantAudit(ctx);
|
|
802
|
+
if (!ctx.background) return;
|
|
799
803
|
await repairIfRequested({
|
|
800
804
|
sql: ctx.sql,
|
|
801
805
|
namespace: resolveCortextNamespaces(ctx.coordinatorNamespacesByPlugin)?.[0] ?? null,
|
|
@@ -803,9 +807,9 @@ function createCortextKernelPlugin() {
|
|
|
803
807
|
metric: ctx.metric
|
|
804
808
|
});
|
|
805
809
|
},
|
|
806
|
-
migrations: [cortextBaselineMigration]
|
|
810
|
+
migrations: [cortextBaselineMigration, cortextDropEngineOwnedIndexesMigration]
|
|
807
811
|
};
|
|
808
812
|
}
|
|
809
813
|
|
|
810
814
|
//#endregion
|
|
811
|
-
export {
|
|
815
|
+
export { CORTEXT_NAMESPACE as A, auditObjects as C, scopedNamespace as D, resolveCortextNamespaces as E, ARCHITECTURE_NAMESPACE as O, auditNamespaceCoverage as S, reportInvariantAudit as T, AGENT_MESSAGE_STATUS_VOCAB as _, repairInvariants as a, TODO_RESOLUTION_VOCAB as b, ENGINE_OWNED_INDEXES as c, VOCAB_CONSTRAINTS as d, countDuplicateHostInstallKeys as f, rescopeVocabConstraint as g, rescopeKidUniqueIndex as h, repairIfRequested as i, CORTEXT_PROMOTED_COLUMNS as j, CORTEXT_COORDINATOR_NAME as k, cortextDropEngineOwnedIndexesMigration as l, rescopeHostInstallKeyIndex as m, SUBSTRATE_CONTRACT_RANGE as n, repairOutcomeMessage as o, countDuplicateKids as p, REPAIR_ENV_VAR as r, repairRequested as s, createCortextKernelPlugin as t, cortextBaselineMigration as u, TODO_LIFECYCLE_VOCAB as v, findingMessage as w, INVARIANT_OBJECTS as x, TODO_PRIORITY_VOCAB as y };
|
|
@@ -33,10 +33,9 @@
|
|
|
33
33
|
* enum, or moving the substrate contract range are all breaks for someone holding
|
|
34
34
|
* data or code against this schema.
|
|
35
35
|
*
|
|
36
|
-
* `indexed` is included
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* invisible to a version-bump decision.
|
|
36
|
+
* `indexed` is included as a review item, not a break: every field filters,
|
|
37
|
+
* so un-indexing one turns an index-served `where` into a scan of the type.
|
|
38
|
+
* The caller's query still works, but slower, and a human should weigh it.
|
|
40
39
|
*/
|
|
41
40
|
/** One field, reduced to the properties a consumer can break against. */
|
|
42
41
|
export interface SurfaceField {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { JSON_HEADERS, RESERVED_AUTOCRUD_FIELDS, ResolverFrameError, and,
|
|
1
|
+
import { A as CORTEXT_NAMESPACE, O as ARCHITECTURE_NAMESPACE, _ as AGENT_MESSAGE_STATUS_VOCAB, b as TODO_RESOLUTION_VOCAB, j as CORTEXT_PROMOTED_COLUMNS, k as CORTEXT_COORDINATOR_NAME, n as SUBSTRATE_CONTRACT_RANGE, v as TODO_LIFECYCLE_VOCAB, y as TODO_PRIORITY_VOCAB } from "./plugin-CujP_pvT.js";
|
|
2
|
+
import { JSON_HEADERS, RESERVED_AUTOCRUD_FIELDS, ResolverFrameError, and, createResolverWithEdges, defineNamespace, deleteResolver, denyAll, denyVerbs, field, formatQualifiedKey, getResolver, grantOf, grantedIn, jsonResponse, listResolver, makeCtx, or, queryAll, requireIdentity, resolveEntityRow, resolveIdentity, selectAll, updateResolver } from "@mnmal-ai/substrate/sdk";
|
|
3
3
|
|
|
4
4
|
//#region src/auth-policy.ts
|
|
5
5
|
/**
|
|
@@ -163,8 +163,12 @@ function parseBody(req) {
|
|
|
163
163
|
} catch (err) {
|
|
164
164
|
return { error: err instanceof Error ? err.message : "invalid JSON" };
|
|
165
165
|
}
|
|
166
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return { dryRun:
|
|
167
|
-
|
|
166
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return { error: "body must be a JSON object, e.g. {\"dryRun\": true}" };
|
|
167
|
+
const unknown = Object.keys(parsed).filter((k) => k !== "dryRun");
|
|
168
|
+
if (unknown.length > 0) return { error: `unknown field(s): ${unknown.join(", ")}` };
|
|
169
|
+
const dryRun = parsed.dryRun;
|
|
170
|
+
if (dryRun !== void 0 && typeof dryRun !== "boolean") return { error: "\"dryRun\" must be true or false" };
|
|
171
|
+
return { dryRun: dryRun === true };
|
|
168
172
|
}
|
|
169
173
|
function registerCompactRoute(router, ctx, retention, namespace, auth = DEFAULT_AUTH) {
|
|
170
174
|
router.post("/compact", async (req) => {
|
|
@@ -295,10 +299,44 @@ function messageParticipantsOnly(installCtx, name, inner) {
|
|
|
295
299
|
};
|
|
296
300
|
}
|
|
297
301
|
/**
|
|
302
|
+
* Whether `host` names the machine in `kid`'s `@host` part, ignoring case.
|
|
303
|
+
* A kid with no `@` names no machine, so it may carry no host.
|
|
304
|
+
*/
|
|
305
|
+
function hostMatchesKid(host, kid) {
|
|
306
|
+
if (host === void 0 || host === null) return true;
|
|
307
|
+
if (typeof kid !== "string" || !kid.includes("@")) return false;
|
|
308
|
+
return String(host).toLowerCase() === kid.slice(kid.lastIndexOf("@") + 1).toLowerCase();
|
|
309
|
+
}
|
|
310
|
+
function refuseForeignHost(name, host, kid) {
|
|
311
|
+
if (hostMatchesKid(host, kid)) return;
|
|
312
|
+
throw new ResolverFrameError("operator_only", `${name}: 'host' must be the machine in the kid ('${String(kid)}'); only an operator may set another`, { field: "host" });
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* AgentIdentity create: a non-operator may not file a row whose `host` is a
|
|
316
|
+
* machine other than the one its kid names. The fleet audit trusts `host`
|
|
317
|
+
* (and `roaming` on that host) when it decides who may write a host's
|
|
318
|
+
* HostInstall rows.
|
|
319
|
+
*/
|
|
320
|
+
function identityCreateGuard(installCtx, name, inner) {
|
|
321
|
+
return async (ctx, args) => {
|
|
322
|
+
const params = args.params ?? {};
|
|
323
|
+
if (!isOperator(ctx)) refuseForeignHost(name, params.host, params.kid);
|
|
324
|
+
if (typeof params.kid === "string") {
|
|
325
|
+
const { rows } = await (args.dataset ?? installCtx.dataset).read("AgentIdentity", {
|
|
326
|
+
where: { kid: { eq: params.kid } },
|
|
327
|
+
limit: 1
|
|
328
|
+
});
|
|
329
|
+
if (rows.length > 0) throw new ResolverFrameError("duplicate_kid", `${name}: an AgentIdentity row for '${params.kid}' already exists; update it instead`, { field: "kid" });
|
|
330
|
+
}
|
|
331
|
+
return inner(ctx, args);
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
298
335
|
* AgentIdentity update: the identity itself (the caller's kid is the row's
|
|
299
336
|
* kid) or an operator. `kid` is the row's identity, so changing it is an
|
|
300
337
|
* operator act even on your own row: otherwise a row could be renamed onto a
|
|
301
|
-
* kid whose mail it would then receive.
|
|
338
|
+
* kid whose mail it would then receive. `host` must stay the kid's machine,
|
|
339
|
+
* and reactivating a retired row is an operator act.
|
|
302
340
|
*/
|
|
303
341
|
function identitySelfOnly(installCtx, name, inner) {
|
|
304
342
|
return async (ctx, args) => {
|
|
@@ -309,10 +347,45 @@ function identitySelfOnly(installCtx, name, inner) {
|
|
|
309
347
|
if (row === null) return inner(ctx, args);
|
|
310
348
|
const params = args.params ?? {};
|
|
311
349
|
if ("kid" in params && params.kid !== row.kid) throw new ResolverFrameError("operator_only", `${name}: 'kid' is the row's identity and only an operator may change it`, { field: "kid" });
|
|
312
|
-
if (callerKid(ctx) === row.kid)
|
|
350
|
+
if (callerKid(ctx) === row.kid) {
|
|
351
|
+
if ("host" in params || "roaming" in params || "project" in params) refuseForeignHost(name, "host" in params ? params.host : row.host, row.kid);
|
|
352
|
+
if (params.active === true && row.active === false) throw new ResolverFrameError("operator_only", `${name}: a retired identity may only be reactivated by an operator`, { field: "active" });
|
|
353
|
+
return inner(ctx, args);
|
|
354
|
+
}
|
|
313
355
|
throw new ResolverFrameError("not_self", `${name}: only '${String(row.kid)}' itself, or an operator, may edit this identity row`, { id: row.id });
|
|
314
356
|
};
|
|
315
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Todo create/update: `resolution` says why a CLOSED Todo stopped, so a live
|
|
360
|
+
* Todo carries none. A resolution with a live resulting status is refused,
|
|
361
|
+
* and moving a Todo back to a live status clears the one it had.
|
|
362
|
+
*/
|
|
363
|
+
function todoResolutionGuard(installCtx, name, inner, statusAliases) {
|
|
364
|
+
const canon = (v) => {
|
|
365
|
+
const s = String(v).trim().toLowerCase();
|
|
366
|
+
return statusAliases[s] ?? s;
|
|
367
|
+
};
|
|
368
|
+
return async (ctx, args) => {
|
|
369
|
+
const params = { ...args.params ?? {} };
|
|
370
|
+
const statusGiven = params.status !== void 0 && params.status !== null;
|
|
371
|
+
let resulting = statusGiven ? canon(params.status) : null;
|
|
372
|
+
let current = null;
|
|
373
|
+
const id = rowId(args);
|
|
374
|
+
if (id !== void 0) {
|
|
375
|
+
const ds = args.dataset ?? installCtx.dataset;
|
|
376
|
+
current = await resolveEntityRow(ds, "Todo", id);
|
|
377
|
+
if (resulting === null && current !== null) resulting = canon(current.status);
|
|
378
|
+
}
|
|
379
|
+
if (resulting === null) resulting = "open";
|
|
380
|
+
if (resulting === "closed") return inner(ctx, args);
|
|
381
|
+
if (params.resolution !== void 0 && params.resolution !== null) throw new ResolverFrameError("resolution_on_live_todo", `${name}: 'resolution' is for closed Todos; this one would be '${resulting}'`, { field: "resolution" });
|
|
382
|
+
if (statusGiven && current !== null && current.resolution != null && !("resolution" in params)) params.resolution = null;
|
|
383
|
+
return inner(ctx, {
|
|
384
|
+
...args,
|
|
385
|
+
params
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
}
|
|
316
389
|
/** AgentIdentity delete: operators only. Mail hangs off these rows. */
|
|
317
390
|
function operatorOnly(name, inner) {
|
|
318
391
|
return async (ctx, args) => {
|
|
@@ -541,12 +614,27 @@ function transitionResolver(installCtx, target) {
|
|
|
541
614
|
if (identityRow === void 0) throw new ResolverFrameError("no_recipient", `${name}: the recipient identity row no longer exists`, { id: message.id });
|
|
542
615
|
const actor = resolveIdentity(ctx);
|
|
543
616
|
if (actor.id !== identityRow.kid) throw new ResolverFrameError("not_recipient", `${name}: only the recipient ('${String(identityRow.kid)}') may transition this message`, { recipient: identityRow.kid });
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
status:
|
|
548
|
-
|
|
549
|
-
|
|
617
|
+
let current = message;
|
|
618
|
+
for (let attempt = 0; attempt < 3 && current !== null; attempt++) {
|
|
619
|
+
if (isNoOp(current.status)) return current;
|
|
620
|
+
const { matched, row } = await ds.conditionalUpdate(TYPE, current.id, { status: current.status }, {
|
|
621
|
+
status: target,
|
|
622
|
+
...stamp(actor, ctx.now.toISOString())
|
|
623
|
+
}, actor, ctx.now);
|
|
624
|
+
if (matched && row !== null) {
|
|
625
|
+
args.onWrite?.({
|
|
626
|
+
ns: ds.namespace,
|
|
627
|
+
type: TYPE,
|
|
628
|
+
id: row.id,
|
|
629
|
+
operation: "update",
|
|
630
|
+
row
|
|
631
|
+
});
|
|
632
|
+
return row;
|
|
633
|
+
}
|
|
634
|
+
current = await resolveEntityRow(ds, TYPE, message.id);
|
|
635
|
+
}
|
|
636
|
+
if (current === null) return null;
|
|
637
|
+
throw new ResolverFrameError("conflict", `${name}: the message kept changing while it was being transitioned; retry`, { id: message.id });
|
|
550
638
|
};
|
|
551
639
|
}
|
|
552
640
|
/**
|
|
@@ -989,7 +1077,8 @@ function buildClaudeNamespace(ns) {
|
|
|
989
1077
|
area: field.string(),
|
|
990
1078
|
status: field.vocab(TODO_LIFECYCLE_VOCAB, {
|
|
991
1079
|
aliases: TODO_STATUS_ALIASES,
|
|
992
|
-
implies: TODO_STATUS_IMPLIES
|
|
1080
|
+
implies: TODO_STATUS_IMPLIES,
|
|
1081
|
+
indexed: true
|
|
993
1082
|
}),
|
|
994
1083
|
resolution: field.vocab(TODO_RESOLUTION_VOCAB, {
|
|
995
1084
|
aliases: TODO_RESOLUTION_ALIASES,
|
|
@@ -1026,7 +1115,10 @@ function buildClaudeNamespace(ns) {
|
|
|
1026
1115
|
nullable: true,
|
|
1027
1116
|
onDelete: "set_null"
|
|
1028
1117
|
}),
|
|
1029
|
-
project: field.string({
|
|
1118
|
+
project: field.string({
|
|
1119
|
+
nullable: true,
|
|
1120
|
+
indexed: true
|
|
1121
|
+
})
|
|
1030
1122
|
} },
|
|
1031
1123
|
AgentRule: {
|
|
1032
1124
|
fields: {
|
|
@@ -1045,7 +1137,10 @@ function buildClaudeNamespace(ns) {
|
|
|
1045
1137
|
onDelete: "set_null"
|
|
1046
1138
|
}),
|
|
1047
1139
|
archived: field.boolean({ default: false }),
|
|
1048
|
-
project: field.string({
|
|
1140
|
+
project: field.string({
|
|
1141
|
+
nullable: true,
|
|
1142
|
+
indexed: true
|
|
1143
|
+
})
|
|
1049
1144
|
},
|
|
1050
1145
|
embed: { source: (r) => {
|
|
1051
1146
|
return `${embedText(r.synopsis, typeof r.rule === "string" ? r.rule : "")}${typeof r.category === "string" && r.category.length > 0 ? ` [${r.category}]` : ""}`;
|
|
@@ -1098,7 +1193,10 @@ function buildClaudeNamespace(ns) {
|
|
|
1098
1193
|
body: field.string({ indexed: false }),
|
|
1099
1194
|
synopsis: field.string({ nullable: true }),
|
|
1100
1195
|
archived: field.boolean({ default: false }),
|
|
1101
|
-
project: field.string({
|
|
1196
|
+
project: field.string({
|
|
1197
|
+
nullable: true,
|
|
1198
|
+
indexed: true
|
|
1199
|
+
})
|
|
1102
1200
|
},
|
|
1103
1201
|
embed: { source: (r) => {
|
|
1104
1202
|
const desc = typeof r.description === "string" ? r.description : "";
|
|
@@ -1115,7 +1213,10 @@ function buildClaudeNamespace(ns) {
|
|
|
1115
1213
|
indexed: false
|
|
1116
1214
|
}),
|
|
1117
1215
|
authoredBy: field.struct({ kind: field.string() }),
|
|
1118
|
-
project: field.string({
|
|
1216
|
+
project: field.string({
|
|
1217
|
+
nullable: true,
|
|
1218
|
+
indexed: true
|
|
1219
|
+
}),
|
|
1119
1220
|
archived: field.boolean({ default: false })
|
|
1120
1221
|
},
|
|
1121
1222
|
embed: { source: (r) => embedText(r.synopsis, typeof r.body === "string" ? r.body : "") },
|
|
@@ -1157,19 +1258,27 @@ function buildClaudeNamespace(ns) {
|
|
|
1157
1258
|
version: field.string(),
|
|
1158
1259
|
active: field.boolean()
|
|
1159
1260
|
} },
|
|
1160
|
-
AgentIdentity: {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1261
|
+
AgentIdentity: {
|
|
1262
|
+
fields: {
|
|
1263
|
+
kid: field.string(),
|
|
1264
|
+
project: field.string({ nullable: true }),
|
|
1265
|
+
host: field.string(),
|
|
1266
|
+
displayName: field.string({ nullable: true }),
|
|
1267
|
+
description: field.string({
|
|
1268
|
+
nullable: true,
|
|
1269
|
+
indexed: false
|
|
1270
|
+
}),
|
|
1271
|
+
active: field.boolean({ default: true }),
|
|
1272
|
+
broadcastEligible: field.boolean({ default: true }),
|
|
1273
|
+
roaming: field.boolean({ default: false })
|
|
1274
|
+
},
|
|
1275
|
+
indexes: [{
|
|
1276
|
+
fields: ["kid"],
|
|
1277
|
+
unique: true,
|
|
1278
|
+
where: "live",
|
|
1279
|
+
name: "rows_agent_identity_kid_unique"
|
|
1280
|
+
}]
|
|
1281
|
+
},
|
|
1173
1282
|
AgentMessage: {
|
|
1174
1283
|
autoCrud: false,
|
|
1175
1284
|
fields: {
|
|
@@ -1199,40 +1308,48 @@ function buildClaudeNamespace(ns) {
|
|
|
1199
1308
|
archived: field.boolean({ default: false })
|
|
1200
1309
|
}
|
|
1201
1310
|
},
|
|
1202
|
-
HostInstall: {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1311
|
+
HostInstall: {
|
|
1312
|
+
fields: {
|
|
1313
|
+
key: field.string(),
|
|
1314
|
+
host: field.string(),
|
|
1315
|
+
hostReported: field.string(),
|
|
1316
|
+
project: field.string(),
|
|
1317
|
+
kid: field.string({ nullable: true }),
|
|
1318
|
+
harness: field.string(),
|
|
1319
|
+
pluginVersion: field.string({ nullable: true }),
|
|
1320
|
+
fragmentVersion: field.string({ nullable: true }),
|
|
1321
|
+
fragmentDigest: field.string({ nullable: true }),
|
|
1322
|
+
fragmentState: field.enum(HOST_INSTALL_FRAGMENT_STATES),
|
|
1323
|
+
refreshedAt: field.date(),
|
|
1324
|
+
pinState: field.enum(HOST_INSTALL_CHECK_STATES, { nullable: true }),
|
|
1325
|
+
pinNote: field.string({
|
|
1326
|
+
nullable: true,
|
|
1327
|
+
indexed: false
|
|
1328
|
+
}),
|
|
1329
|
+
mcpJsonState: field.enum(HOST_INSTALL_CHECK_STATES, { nullable: true }),
|
|
1330
|
+
mcpJsonNote: field.string({
|
|
1331
|
+
nullable: true,
|
|
1332
|
+
indexed: false
|
|
1333
|
+
}),
|
|
1334
|
+
authorityState: field.enum(HOST_INSTALL_CHECK_STATES, { nullable: true }),
|
|
1335
|
+
authorityNote: field.string({
|
|
1336
|
+
nullable: true,
|
|
1337
|
+
indexed: false
|
|
1338
|
+
}),
|
|
1339
|
+
settingsState: field.enum(HOST_INSTALL_CHECK_STATES, { nullable: true }),
|
|
1340
|
+
settingsNote: field.string({
|
|
1341
|
+
nullable: true,
|
|
1342
|
+
indexed: false
|
|
1343
|
+
}),
|
|
1344
|
+
settingsEnv: field.json({ nullable: true })
|
|
1345
|
+
},
|
|
1346
|
+
indexes: [{
|
|
1347
|
+
fields: ["key"],
|
|
1348
|
+
unique: true,
|
|
1349
|
+
where: "live",
|
|
1350
|
+
name: "rows_host_install_key_unique"
|
|
1351
|
+
}]
|
|
1352
|
+
}
|
|
1236
1353
|
};
|
|
1237
1354
|
const definition = defineNamespace(ns, types, {
|
|
1238
1355
|
annotations: { nl: CORTEXT_NL_RULINGS },
|
|
@@ -1244,7 +1361,13 @@ function buildClaudeNamespace(ns) {
|
|
|
1244
1361
|
install(ctx) {
|
|
1245
1362
|
baseInstall(ctx);
|
|
1246
1363
|
installMailboxMutations(ctx, definition.types.AgentMessage.fields);
|
|
1247
|
-
for (const [name, wrap] of [
|
|
1364
|
+
for (const [name, wrap] of [
|
|
1365
|
+
["createAgentIdentity", (r) => identityCreateGuard(ctx, "createAgentIdentity", r)],
|
|
1366
|
+
["updateAgentIdentity", (r) => identitySelfOnly(ctx, "updateAgentIdentity", r)],
|
|
1367
|
+
["deleteAgentIdentity", (r) => operatorOnly("deleteAgentIdentity", r)],
|
|
1368
|
+
["createTodo", (r) => todoResolutionGuard(ctx, "createTodo", r, TODO_STATUS_ALIASES)],
|
|
1369
|
+
["updateTodo", (r) => todoResolutionGuard(ctx, "updateTodo", r, TODO_STATUS_ALIASES)]
|
|
1370
|
+
]) {
|
|
1248
1371
|
const entry = ctx.registry.get(name);
|
|
1249
1372
|
if (entry === void 0) throw new Error(`cortext: autoCRUD did not register ${name}`);
|
|
1250
1373
|
ctx.registry.replace(name, {
|
|
@@ -1592,7 +1715,11 @@ function buildBody(namespace, retention) {
|
|
|
1592
1715
|
description: "Load recent context at the start of each session",
|
|
1593
1716
|
query: {
|
|
1594
1717
|
SessionLog: {
|
|
1595
|
-
params: {
|
|
1718
|
+
params: {
|
|
1719
|
+
where: { archived: { eq: false } },
|
|
1720
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1721
|
+
limit: 3
|
|
1722
|
+
},
|
|
1596
1723
|
fields: {
|
|
1597
1724
|
id: true,
|
|
1598
1725
|
branch: true,
|
|
@@ -1602,7 +1729,11 @@ function buildBody(namespace, retention) {
|
|
|
1602
1729
|
}
|
|
1603
1730
|
},
|
|
1604
1731
|
Decision: {
|
|
1605
|
-
params: {
|
|
1732
|
+
params: {
|
|
1733
|
+
where: { archived: { eq: false } },
|
|
1734
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1735
|
+
limit: 80
|
|
1736
|
+
},
|
|
1606
1737
|
fields: {
|
|
1607
1738
|
id: true,
|
|
1608
1739
|
topic: true,
|
|
@@ -1615,7 +1746,18 @@ function buildBody(namespace, retention) {
|
|
|
1615
1746
|
}
|
|
1616
1747
|
},
|
|
1617
1748
|
Todo: {
|
|
1618
|
-
params: {
|
|
1749
|
+
params: {
|
|
1750
|
+
where: {
|
|
1751
|
+
archived: { eq: false },
|
|
1752
|
+
status: { in: [
|
|
1753
|
+
"open",
|
|
1754
|
+
"in_progress",
|
|
1755
|
+
"blocked"
|
|
1756
|
+
] }
|
|
1757
|
+
},
|
|
1758
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1759
|
+
limit: 50
|
|
1760
|
+
},
|
|
1619
1761
|
fields: {
|
|
1620
1762
|
id: true,
|
|
1621
1763
|
title: true,
|
|
@@ -1631,7 +1773,7 @@ function buildBody(namespace, retention) {
|
|
|
1631
1773
|
surfaceToUser: [
|
|
1632
1774
|
"The 3 most recent non-archived SessionLog entries",
|
|
1633
1775
|
"Non-archived Decisions",
|
|
1634
|
-
"Todos with status 'open' or 'blocked'",
|
|
1776
|
+
"Todos with status 'open', 'in_progress' or 'blocked'",
|
|
1635
1777
|
"Skip archived records silently"
|
|
1636
1778
|
]
|
|
1637
1779
|
} },
|
|
@@ -1697,10 +1839,9 @@ function registerOnboardRoute(router, _ctx, retention, namespace) {
|
|
|
1697
1839
|
* enum, or moving the substrate contract range are all breaks for someone holding
|
|
1698
1840
|
* data or code against this schema.
|
|
1699
1841
|
*
|
|
1700
|
-
* `indexed` is included
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1703
|
-
* invisible to a version-bump decision.
|
|
1842
|
+
* `indexed` is included as a review item, not a break: every field filters,
|
|
1843
|
+
* so un-indexing one turns an index-served `where` into a scan of the type.
|
|
1844
|
+
* The caller's query still works, but slower, and a human should weigh it.
|
|
1704
1845
|
*/
|
|
1705
1846
|
function reduceField(raw) {
|
|
1706
1847
|
const nullable = raw.nullable === true;
|
|
@@ -1791,7 +1932,7 @@ function classifySurfaceChange(before, after) {
|
|
|
1791
1932
|
if (!bf.required && af.required) breaking.push(`field became REQUIRED: ${at}`);
|
|
1792
1933
|
if (bf.required && !af.required) additive.push(`field relaxed to optional: ${at}`);
|
|
1793
1934
|
if (bf.nullable && !af.nullable) breaking.push(`field no longer nullable: ${at}`);
|
|
1794
|
-
if (bf.indexed && !af.indexed)
|
|
1935
|
+
if (bf.indexed && !af.indexed) review.push(`field no longer indexed: ${at}`);
|
|
1795
1936
|
if (!bf.indexed && af.indexed) additive.push(`field now indexed: ${at}`);
|
|
1796
1937
|
const bEnum = bf.enum ?? null;
|
|
1797
1938
|
const aEnum = af.enum ?? null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnmal-ai/cortext",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "Cortext: the agent-neutral coordination context store for Substrate (Todos, Decisions, SessionLogs, AgentRules, Memory, Concepts, Councils, …) plus architecture findings. One package carrying two plugins: the coordinator that registers the namespaces, and the kernel plugin that owns their Postgres invariants.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"zod": "^4.6.5"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@mnmal-ai/substrate": ">=1.0.0-alpha.
|
|
65
|
+
"@mnmal-ai/substrate": ">=1.0.0-alpha.10 <2.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@mnmal-ai/substrate": "1.0.0-alpha.
|
|
68
|
+
"@mnmal-ai/substrate": "1.0.0-alpha.10",
|
|
69
69
|
"@types/node": "^26.6.4",
|
|
70
70
|
"tsdown": "^0.23.0",
|
|
71
71
|
"typescript": "^7.0.2",
|