@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 CHANGED
@@ -1,5 +1,5 @@
1
- import { E as isMonotonicAuth, t as createCortextPlugin, w as DEFAULT_AUTH } from "./src-BKaGTnRw.js";
2
- import { D as ARCHITECTURE_NAMESPACE, k as CORTEXT_NAMESPACE } from "./plugin-27UpMnoE.js";
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-BKaGTnRw.js";
2
- import { g as AGENT_MESSAGE_STATUS_VOCAB } from "./plugin-27UpMnoE.js";
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
  }>;
@@ -1,4 +1,4 @@
1
- import { t as createCortextKernelPlugin } from "./plugin-27UpMnoE.js";
1
+ import { t as createCortextKernelPlugin } from "./plugin-oVE0E3so.js";
2
2
  import { defineBootstrap } from "@mnmal-ai/substrate/sdk";
3
3
  import { z } from "zod";
4
4
 
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-27UpMnoE.js";
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 };
@@ -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 !== "cortext") {
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 '${CORTEXT_NAMESPACE}'. Lost guarantee: ${finding.object.guarantee}. It exists, so nothing errors; it simply matches no rows.`;
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
- const findings = [...auditNamespaceCoverage(ctx.coordinatorNamespaces, ctx.coordinatorNamespacesByPlugin)];
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-27UpMnoE.js";
2
- import { JSON_HEADERS, RESERVED_AUTOCRUD_FIELDS, ResolverFrameError, and, applyUpdatePatch, 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_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: false };
167
- return { dryRun: parsed.dryRun === true };
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) return inner(ctx, args);
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
- if (isNoOp(message.status)) return message;
545
- const at = ctx.now.toISOString();
546
- return applyUpdatePatch(ds, TYPE, message.id, {
547
- status: target,
548
- ...stamp(actor, at)
549
- }, ctx, args.onWrite);
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 [["updateAgentIdentity", (r) => identitySelfOnly(ctx, "updateAgentIdentity", r)], ["deleteAgentIdentity", (r) => operatorOnly("deleteAgentIdentity", r)]]) {
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: { limit: 3 },
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: { limit: 80 },
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: { limit: 50 },
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.8",
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": {