@mnmal-ai/cortext 1.0.0-alpha.11 → 1.0.0-alpha.13
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/substrate-contract.d.ts +1 -1
- package/dist/kernel-bootstrap.js +1 -1
- package/dist/kernel.js +1 -1
- package/dist/participants.d.ts +9 -1
- package/dist/{plugin-BtBVkZpZ.js → plugin-DaVJ8Fi4.js} +1 -1
- package/dist/{src-DJA6hyG-.js → src-n5tGIJUZ.js} +152 -141
- package/package.json +6 -6
package/dist/bootstrap.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as isMonotonicAuth, T as DEFAULT_AUTH, t as createCortextPlugin } from "./src-
|
|
2
|
-
import { A as CORTEXT_NAMESPACE, O as ARCHITECTURE_NAMESPACE } from "./plugin-
|
|
1
|
+
import { D as isMonotonicAuth, T as DEFAULT_AUTH, t as createCortextPlugin } from "./src-n5tGIJUZ.js";
|
|
2
|
+
import { A as CORTEXT_NAMESPACE, O as ARCHITECTURE_NAMESPACE } from "./plugin-DaVJ8Fi4.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 registerCompactRoute, D as isMonotonicAuth, E as buildAuthPolicies, S as buildClaudeNamespace, T as DEFAULT_AUTH, _ as TASK_STATUS_VOCAB, a as classSatisfies, b as TODO_STATUS_IMPLIES, 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_RESOLUTION_ALIASES, w as runCompact, x as buildArchitectureNamespace, y as TODO_STATUS_ALIASES } from "./src-
|
|
2
|
-
import { _ as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-
|
|
1
|
+
import { C as registerCompactRoute, D as isMonotonicAuth, E as buildAuthPolicies, S as buildClaudeNamespace, T as DEFAULT_AUTH, _ as TASK_STATUS_VOCAB, a as classSatisfies, b as TODO_STATUS_IMPLIES, 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_RESOLUTION_ALIASES, w as runCompact, x as buildArchitectureNamespace, y as TODO_STATUS_ALIASES } from "./src-n5tGIJUZ.js";
|
|
2
|
+
import { _ as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-DaVJ8Fi4.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, TASK_STATUS_VOCAB, TODO_RESOLUTION_ALIASES, TODO_STATUS_ALIASES, TODO_STATUS_IMPLIES, buildArchitectureNamespace, buildAuthPolicies, buildClaudeNamespace, buildReleaseSurface, bumpClass, classSatisfies, classifySurfaceChange, createCortextPlugin, inferTodoResolution, isMonotonicAuth, registerCompactRoute, registerOnboardRoute, runCompact };
|
|
@@ -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.17 <2.0.0";
|
package/dist/kernel-bootstrap.js
CHANGED
package/dist/kernel.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
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-
|
|
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-DaVJ8Fi4.js";
|
|
2
2
|
|
|
3
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
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
* An operator is an identity granted `admin` (or `*`) in this namespace.
|
|
12
12
|
*/
|
|
13
13
|
import type { NamespaceInstallContext, RegistryEntryResolver, RequestContext } from '@mnmal-ai/substrate/sdk';
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Whether `identity` is an operator: it holds `admin` (or `*`) in `namespace`,
|
|
16
|
+
* or the deployment admin capability (`SUBSTRATE_ADMIN_CAPABILITY`, default
|
|
17
|
+
* `admin`) in `_system`, as substrate's own admin gate reads it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isOperatorIdentity(identity: RequestContext['identity'], namespace: string, env?: {
|
|
20
|
+
SUBSTRATE_ADMIN_CAPABILITY?: string;
|
|
21
|
+
}): boolean;
|
|
22
|
+
/** Whether the caller is an operator in the namespace it is calling. */
|
|
15
23
|
export declare function isOperator(ctx: RequestContext): boolean;
|
|
16
24
|
/**
|
|
17
25
|
* AgentMessage update/delete: the sender, the recipient, or an operator.
|
|
@@ -797,7 +797,7 @@ async function repairIfRequested(ctx, warn = console.warn) {
|
|
|
797
797
|
* by reflex: widen it only after reading what the new major changed.
|
|
798
798
|
* `substrate-contract-parity.test.ts` holds the manifest to this literal.
|
|
799
799
|
*/
|
|
800
|
-
const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.
|
|
800
|
+
const SUBSTRATE_CONTRACT_RANGE = ">=1.0.0-alpha.17 <2.0.0";
|
|
801
801
|
|
|
802
802
|
//#endregion
|
|
803
803
|
//#region src/kernel/plugin.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
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-
|
|
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";
|
|
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-DaVJ8Fi4.js";
|
|
2
|
+
import { JSON_HEADERS, RESERVED_AUTOCRUD_FIELDS, ResolverFrameError, SYSTEM_NAMESPACE, 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
|
/**
|
|
@@ -40,6 +40,154 @@ function buildAuthPolicies(auth) {
|
|
|
40
40
|
return [];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/participants.ts
|
|
45
|
+
/**
|
|
46
|
+
* Whether `identity` is an operator: it holds `admin` (or `*`) in `namespace`,
|
|
47
|
+
* or the deployment admin capability (`SUBSTRATE_ADMIN_CAPABILITY`, default
|
|
48
|
+
* `admin`) in `_system`, as substrate's own admin gate reads it.
|
|
49
|
+
*/
|
|
50
|
+
function isOperatorIdentity(identity, namespace, env = process.env) {
|
|
51
|
+
const grant = grantOf(identity);
|
|
52
|
+
const caps = grantedIn(grant, namespace);
|
|
53
|
+
if (caps.includes("admin") || caps.includes("*")) return true;
|
|
54
|
+
const adminCap = env.SUBSTRATE_ADMIN_CAPABILITY || "admin";
|
|
55
|
+
const system = grantedIn(grant, SYSTEM_NAMESPACE);
|
|
56
|
+
return system.includes(adminCap) || system.includes("*");
|
|
57
|
+
}
|
|
58
|
+
/** Whether the caller is an operator in the namespace it is calling. */
|
|
59
|
+
function isOperator(ctx) {
|
|
60
|
+
return isOperatorIdentity(ctx.identity, ctx.namespace);
|
|
61
|
+
}
|
|
62
|
+
function rowId(args) {
|
|
63
|
+
const raw = args.params?.id;
|
|
64
|
+
return typeof raw === "string" || typeof raw === "number" ? raw : void 0;
|
|
65
|
+
}
|
|
66
|
+
function callerKid(ctx) {
|
|
67
|
+
const id = ctx.identity?.id;
|
|
68
|
+
return typeof id === "string" && id !== "" ? id : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* AgentMessage update/delete: the sender, the recipient, or an operator.
|
|
72
|
+
* The sender is the key that signed the send (`_metadata.createdBy`); the
|
|
73
|
+
* recipient is the kid on the addressed directory row. Archiving someone
|
|
74
|
+
* else's mail hides it from their inbox, and deleting it destroys it, so
|
|
75
|
+
* neither is a bystander's call.
|
|
76
|
+
*/
|
|
77
|
+
function messageParticipantsOnly(installCtx, name, inner) {
|
|
78
|
+
return async (ctx, args) => {
|
|
79
|
+
const id = rowId(args);
|
|
80
|
+
if (id === void 0 || isOperator(ctx)) return inner(ctx, args);
|
|
81
|
+
const ds = args.dataset ?? installCtx.dataset;
|
|
82
|
+
const message = await resolveEntityRow(ds, "AgentMessage", id);
|
|
83
|
+
if (message === null) return inner(ctx, args);
|
|
84
|
+
const caller = callerKid(ctx);
|
|
85
|
+
const sender = message._metadata?.createdBy?.id;
|
|
86
|
+
let recipient = null;
|
|
87
|
+
if (typeof message.recipient === "string" && message.recipient !== "") {
|
|
88
|
+
const { rows } = await ds.read("AgentIdentity", { id: message.recipient });
|
|
89
|
+
recipient = rows[0]?.kid ?? null;
|
|
90
|
+
}
|
|
91
|
+
if (caller !== null && (caller === sender || caller === recipient)) return inner(ctx, args);
|
|
92
|
+
throw new ResolverFrameError("not_participant", `${name}: only this message's sender ('${String(sender ?? "unknown")}') or recipient ('${String(recipient ?? "unknown")}'), or an operator, may change or delete it`, { id: message.id });
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether `host` names the machine in `kid`'s `@host` part, ignoring case.
|
|
97
|
+
* A kid with no `@` names no machine, so it may carry no host.
|
|
98
|
+
*/
|
|
99
|
+
function hostMatchesKid(host, kid) {
|
|
100
|
+
if (host === void 0 || host === null) return true;
|
|
101
|
+
if (typeof kid !== "string" || !kid.includes("@")) return false;
|
|
102
|
+
return String(host).toLowerCase() === kid.slice(kid.lastIndexOf("@") + 1).toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
function refuseForeignHost(name, host, kid) {
|
|
105
|
+
if (hostMatchesKid(host, kid)) return;
|
|
106
|
+
throw new ResolverFrameError("operator_only", `${name}: 'host' must be the machine in the kid ('${String(kid)}'); only an operator may set another`, { field: "host" });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* AgentIdentity create: a non-operator may not file a row whose `host` is a
|
|
110
|
+
* machine other than the one its kid names. The fleet audit trusts `host`
|
|
111
|
+
* (and `roaming` on that host) when it decides who may write a host's
|
|
112
|
+
* HostInstall rows.
|
|
113
|
+
*/
|
|
114
|
+
function identityCreateGuard(installCtx, name, inner) {
|
|
115
|
+
return async (ctx, args) => {
|
|
116
|
+
const params = args.params ?? {};
|
|
117
|
+
if (!isOperator(ctx)) refuseForeignHost(name, params.host, params.kid);
|
|
118
|
+
if (typeof params.kid === "string") {
|
|
119
|
+
const { rows } = await (args.dataset ?? installCtx.dataset).read("AgentIdentity", {
|
|
120
|
+
where: { kid: { eq: params.kid } },
|
|
121
|
+
limit: 1
|
|
122
|
+
});
|
|
123
|
+
if (rows.length > 0) throw new ResolverFrameError("duplicate_kid", `${name}: an AgentIdentity row for '${params.kid}' already exists; update it instead`, { field: "kid" });
|
|
124
|
+
}
|
|
125
|
+
return inner(ctx, args);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* AgentIdentity update: the identity itself (the caller's kid is the row's
|
|
130
|
+
* kid) or an operator. `kid` is the row's identity, so changing it is an
|
|
131
|
+
* operator act even on your own row: otherwise a row could be renamed onto a
|
|
132
|
+
* kid whose mail it would then receive. `host` must stay the kid's machine,
|
|
133
|
+
* and reactivating a retired row is an operator act.
|
|
134
|
+
*/
|
|
135
|
+
function identitySelfOnly(installCtx, name, inner) {
|
|
136
|
+
return async (ctx, args) => {
|
|
137
|
+
const id = rowId(args);
|
|
138
|
+
if (id === void 0 || isOperator(ctx)) return inner(ctx, args);
|
|
139
|
+
const ds = args.dataset ?? installCtx.dataset;
|
|
140
|
+
const row = await resolveEntityRow(ds, "AgentIdentity", id);
|
|
141
|
+
if (row === null) return inner(ctx, args);
|
|
142
|
+
const params = args.params ?? {};
|
|
143
|
+
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" });
|
|
144
|
+
if (callerKid(ctx) === row.kid) {
|
|
145
|
+
if ("host" in params || "roaming" in params || "project" in params) refuseForeignHost(name, "host" in params ? params.host : row.host, row.kid);
|
|
146
|
+
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" });
|
|
147
|
+
return inner(ctx, args);
|
|
148
|
+
}
|
|
149
|
+
throw new ResolverFrameError("not_self", `${name}: only '${String(row.kid)}' itself, or an operator, may edit this identity row`, { id: row.id });
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Todo create/update: `resolution` says why a CLOSED Todo stopped, so a live
|
|
154
|
+
* Todo carries none. A resolution with a live resulting status is refused,
|
|
155
|
+
* and moving a Todo back to a live status clears the one it had.
|
|
156
|
+
*/
|
|
157
|
+
function todoResolutionGuard(installCtx, name, inner, statusAliases) {
|
|
158
|
+
const canon = (v) => {
|
|
159
|
+
const s = String(v).trim().toLowerCase();
|
|
160
|
+
return statusAliases[s] ?? s;
|
|
161
|
+
};
|
|
162
|
+
return async (ctx, args) => {
|
|
163
|
+
const params = { ...args.params ?? {} };
|
|
164
|
+
const statusGiven = params.status !== void 0 && params.status !== null;
|
|
165
|
+
let resulting = statusGiven ? canon(params.status) : null;
|
|
166
|
+
let current = null;
|
|
167
|
+
const id = rowId(args);
|
|
168
|
+
if (id !== void 0) {
|
|
169
|
+
const ds = args.dataset ?? installCtx.dataset;
|
|
170
|
+
current = await resolveEntityRow(ds, "Todo", id);
|
|
171
|
+
if (resulting === null && current !== null) resulting = canon(current.status);
|
|
172
|
+
}
|
|
173
|
+
if (resulting === null) resulting = "open";
|
|
174
|
+
if (resulting === "closed") return inner(ctx, args);
|
|
175
|
+
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" });
|
|
176
|
+
if (statusGiven && current !== null && current.resolution != null && !("resolution" in params)) params.resolution = null;
|
|
177
|
+
return inner(ctx, {
|
|
178
|
+
...args,
|
|
179
|
+
params
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** AgentIdentity delete: operators only. Mail hangs off these rows. */
|
|
184
|
+
function operatorOnly(name, inner) {
|
|
185
|
+
return async (ctx, args) => {
|
|
186
|
+
if (isOperator(ctx)) return inner(ctx, args);
|
|
187
|
+
throw new ResolverFrameError("operator_only", `${name}: only an operator may delete an identity row; retire it with active: false instead`, {});
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
43
191
|
//#endregion
|
|
44
192
|
//#region src/compact.ts
|
|
45
193
|
/**
|
|
@@ -173,10 +321,9 @@ function parseBody(req) {
|
|
|
173
321
|
function registerCompactRoute(router, ctx, retention, namespace, auth = DEFAULT_AUTH) {
|
|
174
322
|
router.post("/compact", async (req) => {
|
|
175
323
|
if (auth.writes === "identity") {
|
|
176
|
-
|
|
177
|
-
if (!caps.includes("admin") && !caps.includes("*")) return jsonResponse(403, {
|
|
324
|
+
if (!isOperatorIdentity(req.identity ?? null, namespace)) return jsonResponse(403, {
|
|
178
325
|
code: "operator_only",
|
|
179
|
-
message: `POST /compact archives across every project and needs an operator (admin in '${namespace}')`
|
|
326
|
+
message: `POST /compact archives across every project and needs an operator (admin in '${namespace}', or the deployment admin)`
|
|
180
327
|
});
|
|
181
328
|
}
|
|
182
329
|
const parsed = parseBody(req);
|
|
@@ -258,142 +405,6 @@ function reportUncoveredNamespaces(cfg, metric, warn = console.warn) {
|
|
|
258
405
|
return uncovered;
|
|
259
406
|
}
|
|
260
407
|
|
|
261
|
-
//#endregion
|
|
262
|
-
//#region src/participants.ts
|
|
263
|
-
/** Whether the caller holds `admin` (or `*`) in the namespace it is calling. */
|
|
264
|
-
function isOperator(ctx) {
|
|
265
|
-
const caps = grantedIn(grantOf(ctx.identity), ctx.namespace);
|
|
266
|
-
return caps.includes("admin") || caps.includes("*");
|
|
267
|
-
}
|
|
268
|
-
function rowId(args) {
|
|
269
|
-
const raw = args.params?.id;
|
|
270
|
-
return typeof raw === "string" || typeof raw === "number" ? raw : void 0;
|
|
271
|
-
}
|
|
272
|
-
function callerKid(ctx) {
|
|
273
|
-
const id = ctx.identity?.id;
|
|
274
|
-
return typeof id === "string" && id !== "" ? id : null;
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* AgentMessage update/delete: the sender, the recipient, or an operator.
|
|
278
|
-
* The sender is the key that signed the send (`_metadata.createdBy`); the
|
|
279
|
-
* recipient is the kid on the addressed directory row. Archiving someone
|
|
280
|
-
* else's mail hides it from their inbox, and deleting it destroys it, so
|
|
281
|
-
* neither is a bystander's call.
|
|
282
|
-
*/
|
|
283
|
-
function messageParticipantsOnly(installCtx, name, inner) {
|
|
284
|
-
return async (ctx, args) => {
|
|
285
|
-
const id = rowId(args);
|
|
286
|
-
if (id === void 0 || isOperator(ctx)) return inner(ctx, args);
|
|
287
|
-
const ds = args.dataset ?? installCtx.dataset;
|
|
288
|
-
const message = await resolveEntityRow(ds, "AgentMessage", id);
|
|
289
|
-
if (message === null) return inner(ctx, args);
|
|
290
|
-
const caller = callerKid(ctx);
|
|
291
|
-
const sender = message._metadata?.createdBy?.id;
|
|
292
|
-
let recipient = null;
|
|
293
|
-
if (typeof message.recipient === "string" && message.recipient !== "") {
|
|
294
|
-
const { rows } = await ds.read("AgentIdentity", { id: message.recipient });
|
|
295
|
-
recipient = rows[0]?.kid ?? null;
|
|
296
|
-
}
|
|
297
|
-
if (caller !== null && (caller === sender || caller === recipient)) return inner(ctx, args);
|
|
298
|
-
throw new ResolverFrameError("not_participant", `${name}: only this message's sender ('${String(sender ?? "unknown")}') or recipient ('${String(recipient ?? "unknown")}'), or an operator, may change or delete it`, { id: message.id });
|
|
299
|
-
};
|
|
300
|
-
}
|
|
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
|
-
/**
|
|
335
|
-
* AgentIdentity update: the identity itself (the caller's kid is the row's
|
|
336
|
-
* kid) or an operator. `kid` is the row's identity, so changing it is an
|
|
337
|
-
* operator act even on your own row: otherwise a row could be renamed onto a
|
|
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.
|
|
340
|
-
*/
|
|
341
|
-
function identitySelfOnly(installCtx, name, inner) {
|
|
342
|
-
return async (ctx, args) => {
|
|
343
|
-
const id = rowId(args);
|
|
344
|
-
if (id === void 0 || isOperator(ctx)) return inner(ctx, args);
|
|
345
|
-
const ds = args.dataset ?? installCtx.dataset;
|
|
346
|
-
const row = await resolveEntityRow(ds, "AgentIdentity", id);
|
|
347
|
-
if (row === null) return inner(ctx, args);
|
|
348
|
-
const params = args.params ?? {};
|
|
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" });
|
|
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
|
-
}
|
|
355
|
-
throw new ResolverFrameError("not_self", `${name}: only '${String(row.kid)}' itself, or an operator, may edit this identity row`, { id: row.id });
|
|
356
|
-
};
|
|
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
|
-
}
|
|
389
|
-
/** AgentIdentity delete: operators only. Mail hangs off these rows. */
|
|
390
|
-
function operatorOnly(name, inner) {
|
|
391
|
-
return async (ctx, args) => {
|
|
392
|
-
if (isOperator(ctx)) return inner(ctx, args);
|
|
393
|
-
throw new ResolverFrameError("operator_only", `${name}: only an operator may delete an identity row; retire it with active: false instead`, {});
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
408
|
//#endregion
|
|
398
409
|
//#region src/mailbox.ts
|
|
399
410
|
const TYPE = "AgentMessage";
|
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.13",
|
|
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": {
|
|
@@ -58,21 +58,21 @@
|
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"postgres": "
|
|
62
|
-
"zod": "^4.6.
|
|
61
|
+
"postgres": "4.1.0",
|
|
62
|
+
"zod": "^4.6.7"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@mnmal-ai/substrate": ">=1.0.0-alpha.
|
|
65
|
+
"@mnmal-ai/substrate": ">=1.0.0-alpha.17 <2.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@mnmal-ai/substrate": "1.0.0-alpha.
|
|
68
|
+
"@mnmal-ai/substrate": "1.0.0-alpha.17",
|
|
69
69
|
"@types/node": "^26.6.4",
|
|
70
70
|
"tsdown": "^0.23.0",
|
|
71
71
|
"typescript": "^7.0.2",
|
|
72
72
|
"vitest": "^5.0.3"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
|
-
"node": ">=26.
|
|
75
|
+
"node": ">=26.13.1"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "tsdown && tsc -p tsconfig.dts.json",
|