@mnmal-ai/cortext 1.0.0-alpha.8 → 1.0.0-alpha.9
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/invariant-audit.d.ts +2 -1
- package/dist/kernel-bootstrap.js +1 -1
- package/dist/kernel.js +1 -1
- package/dist/participants.d.ts +15 -1
- package/dist/{plugin-27UpMnoE.js → plugin-oVE0E3so.js} +9 -6
- package/dist/{src-BKaGTnRw.js → src-DyQq4vqr.js} +130 -17
- package/package.json +1 -1
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 { D as ARCHITECTURE_NAMESPACE, k as CORTEXT_NAMESPACE } from "./plugin-
|
|
1
|
+
import { E as isMonotonicAuth, t as createCortextPlugin, w as DEFAULT_AUTH } from "./src-DyQq4vqr.js";
|
|
2
|
+
import { D as ARCHITECTURE_NAMESPACE, k as CORTEXT_NAMESPACE } from "./plugin-oVE0E3so.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 { g as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-
|
|
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-DyQq4vqr.js";
|
|
2
|
+
import { g as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-oVE0E3so.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 };
|
|
@@ -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
|
}>;
|
package/dist/kernel-bootstrap.js
CHANGED
package/dist/kernel.js
CHANGED
|
@@ -1,3 +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-
|
|
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-oVE0E3so.js";
|
|
2
2
|
|
|
3
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 };
|
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;
|
|
@@ -280,7 +280,7 @@ async function readCatalog(sql) {
|
|
|
280
280
|
* gains a filter, an early return or a per-object skip, the completion metric
|
|
281
281
|
* reports what was actually inspected instead of a promise.
|
|
282
282
|
*/
|
|
283
|
-
async function auditObjects(sql) {
|
|
283
|
+
async function auditObjects(sql, target = CORTEXT_NAMESPACE) {
|
|
284
284
|
const catalog = await readCatalog(sql);
|
|
285
285
|
const findings = [];
|
|
286
286
|
let checked = 0;
|
|
@@ -295,11 +295,12 @@ async function auditObjects(sql) {
|
|
|
295
295
|
continue;
|
|
296
296
|
}
|
|
297
297
|
const scope = scopedNamespace(row.definition);
|
|
298
|
-
if (scope !==
|
|
298
|
+
if (scope !== target) {
|
|
299
299
|
findings.push({
|
|
300
300
|
kind: "object_misscoped",
|
|
301
301
|
object,
|
|
302
|
-
scopedTo: scope
|
|
302
|
+
scopedTo: scope,
|
|
303
|
+
expected: target
|
|
303
304
|
});
|
|
304
305
|
continue;
|
|
305
306
|
}
|
|
@@ -320,7 +321,7 @@ function findingMessage(finding) {
|
|
|
320
321
|
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
322
|
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
323
|
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 '${
|
|
324
|
+
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
325
|
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
326
|
}
|
|
326
327
|
}
|
|
@@ -352,11 +353,13 @@ function findingMessage(finding) {
|
|
|
352
353
|
* structured channel this belongs in.
|
|
353
354
|
*/
|
|
354
355
|
async function reportInvariantAudit(ctx, warn = console.warn) {
|
|
355
|
-
|
|
356
|
+
let findings = [...auditNamespaceCoverage(ctx.coordinatorNamespaces, ctx.coordinatorNamespacesByPlugin)];
|
|
357
|
+
const target = ctx.coordinatorNamespaces.includes("cortext") ? CORTEXT_NAMESPACE : resolveCortextNamespaces(ctx.coordinatorNamespacesByPlugin)?.[0] ?? "cortext";
|
|
356
358
|
const catalogRead = ctx.sql !== null && ctx.sql !== void 0;
|
|
357
359
|
let objectsChecked = 0;
|
|
358
360
|
if (catalogRead) try {
|
|
359
|
-
const result = await auditObjects(ctx.sql);
|
|
361
|
+
const result = await auditObjects(ctx.sql, target);
|
|
362
|
+
if (target !== "cortext" && result.findings.length === 0) findings = findings.filter((f) => f.kind !== "namespace_drifted");
|
|
360
363
|
findings.push(...result.findings);
|
|
361
364
|
objectsChecked = result.checked;
|
|
362
365
|
} catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as CORTEXT_PROMOTED_COLUMNS, D as ARCHITECTURE_NAMESPACE, O as CORTEXT_COORDINATOR_NAME, _ as TODO_LIFECYCLE_VOCAB, g as AGENT_MESSAGE_STATUS_VOCAB, k as CORTEXT_NAMESPACE, n as SUBSTRATE_CONTRACT_RANGE, v as TODO_PRIORITY_VOCAB, y as TODO_RESOLUTION_VOCAB } from "./plugin-
|
|
2
|
-
import { JSON_HEADERS, RESERVED_AUTOCRUD_FIELDS, ResolverFrameError, and,
|
|
1
|
+
import { A as CORTEXT_PROMOTED_COLUMNS, D as ARCHITECTURE_NAMESPACE, O as CORTEXT_COORDINATOR_NAME, _ as TODO_LIFECYCLE_VOCAB, g as AGENT_MESSAGE_STATUS_VOCAB, k as CORTEXT_NAMESPACE, n as SUBSTRATE_CONTRACT_RANGE, v as TODO_PRIORITY_VOCAB, y as TODO_RESOLUTION_VOCAB } from "./plugin-oVE0E3so.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
|
/**
|
|
@@ -1244,7 +1332,13 @@ function buildClaudeNamespace(ns) {
|
|
|
1244
1332
|
install(ctx) {
|
|
1245
1333
|
baseInstall(ctx);
|
|
1246
1334
|
installMailboxMutations(ctx, definition.types.AgentMessage.fields);
|
|
1247
|
-
for (const [name, wrap] of [
|
|
1335
|
+
for (const [name, wrap] of [
|
|
1336
|
+
["createAgentIdentity", (r) => identityCreateGuard(ctx, "createAgentIdentity", r)],
|
|
1337
|
+
["updateAgentIdentity", (r) => identitySelfOnly(ctx, "updateAgentIdentity", r)],
|
|
1338
|
+
["deleteAgentIdentity", (r) => operatorOnly("deleteAgentIdentity", r)],
|
|
1339
|
+
["createTodo", (r) => todoResolutionGuard(ctx, "createTodo", r, TODO_STATUS_ALIASES)],
|
|
1340
|
+
["updateTodo", (r) => todoResolutionGuard(ctx, "updateTodo", r, TODO_STATUS_ALIASES)]
|
|
1341
|
+
]) {
|
|
1248
1342
|
const entry = ctx.registry.get(name);
|
|
1249
1343
|
if (entry === void 0) throw new Error(`cortext: autoCRUD did not register ${name}`);
|
|
1250
1344
|
ctx.registry.replace(name, {
|
|
@@ -1592,7 +1686,11 @@ function buildBody(namespace, retention) {
|
|
|
1592
1686
|
description: "Load recent context at the start of each session",
|
|
1593
1687
|
query: {
|
|
1594
1688
|
SessionLog: {
|
|
1595
|
-
params: {
|
|
1689
|
+
params: {
|
|
1690
|
+
where: { archived: { eq: false } },
|
|
1691
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1692
|
+
limit: 3
|
|
1693
|
+
},
|
|
1596
1694
|
fields: {
|
|
1597
1695
|
id: true,
|
|
1598
1696
|
branch: true,
|
|
@@ -1602,7 +1700,11 @@ function buildBody(namespace, retention) {
|
|
|
1602
1700
|
}
|
|
1603
1701
|
},
|
|
1604
1702
|
Decision: {
|
|
1605
|
-
params: {
|
|
1703
|
+
params: {
|
|
1704
|
+
where: { archived: { eq: false } },
|
|
1705
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1706
|
+
limit: 80
|
|
1707
|
+
},
|
|
1606
1708
|
fields: {
|
|
1607
1709
|
id: true,
|
|
1608
1710
|
topic: true,
|
|
@@ -1615,7 +1717,18 @@ function buildBody(namespace, retention) {
|
|
|
1615
1717
|
}
|
|
1616
1718
|
},
|
|
1617
1719
|
Todo: {
|
|
1618
|
-
params: {
|
|
1720
|
+
params: {
|
|
1721
|
+
where: {
|
|
1722
|
+
archived: { eq: false },
|
|
1723
|
+
status: { in: [
|
|
1724
|
+
"open",
|
|
1725
|
+
"in_progress",
|
|
1726
|
+
"blocked"
|
|
1727
|
+
] }
|
|
1728
|
+
},
|
|
1729
|
+
orderBy: [{ createdAt: "desc" }],
|
|
1730
|
+
limit: 50
|
|
1731
|
+
},
|
|
1619
1732
|
fields: {
|
|
1620
1733
|
id: true,
|
|
1621
1734
|
title: true,
|
|
@@ -1631,7 +1744,7 @@ function buildBody(namespace, retention) {
|
|
|
1631
1744
|
surfaceToUser: [
|
|
1632
1745
|
"The 3 most recent non-archived SessionLog entries",
|
|
1633
1746
|
"Non-archived Decisions",
|
|
1634
|
-
"Todos with status 'open' or 'blocked'",
|
|
1747
|
+
"Todos with status 'open', 'in_progress' or 'blocked'",
|
|
1635
1748
|
"Skip archived records silently"
|
|
1636
1749
|
]
|
|
1637
1750
|
} },
|
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.9",
|
|
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": {
|