@mnmal-ai/cortext 0.0.0 → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * claude/AgentMessage mutation family — the mailbox write + transition surface.
3
+ *
4
+ * Three custom mutations replace the autoCRUD write paths for AgentMessage:
5
+ *
6
+ * - `sendAgentMessage` — kid-addressed send with fan-out. `to` accepts a
7
+ * single kid, a list of kids, or `'*'` (every active AgentIdentity except
8
+ * the sender). One row per recipient; `recipient: null` broadcasts are
9
+ * retired (cohort-global read-state made the first reader hide an
10
+ * announcement from everyone else).
11
+ * - `markAgentMessageRead` / `ackAgentMessage` — recipient-only status
12
+ * transitions. Ack stamps `ackedBy` / `ackedAt` server-side so attribution
13
+ * survives later row edits (`updatedBy` is too coarse: any field update
14
+ * smudges it).
15
+ *
16
+ * Direct `createAgentMessage` is not registered at all — AgentMessage declares
17
+ * `autoCrud: false`, so the published schema cannot advertise a mutation the
18
+ * route only denies (a caller reading the schema built a shape that could never
19
+ * work, and the refusal looked like a grant problem). `mailboxWriteGuard` still
20
+ * denies transition-field patches through `updateAgentMessage`, and keeps its
21
+ * create branch as the second line of defence for any path that re-registers
22
+ * the verb — the mutation family remains the only write path, which is what
23
+ * enforces kid addressing rather than documenting it.
24
+ *
25
+ * The sender remains `_metadata.createdBy` (server-stamped from the signed
26
+ * principal; spec §3) — fan-out does not change the trust model.
27
+ */
28
+ import type { FieldDef, NamespaceInstallContext, Policy } from '@mnmal-ai/substrate/sdk';
29
+ /**
30
+ * Policy over AgentMessage's hand-registered surface — `installMailboxMutations`
31
+ * composes it with the plugin policy, the way defineNamespace composes type
32
+ * policies for autoCRUD types. Retires the direct write paths:
33
+ * - create → always denied. The verb is not registered (autoCrud:false), so
34
+ * this branch only fires if something re-registers it; the schema never
35
+ * advertises it.
36
+ * - update → only archived and tags may change; a transition field
37
+ * or anything fixed at send is denied, each with its own reason.
38
+ */
39
+ export declare const mailboxWriteGuard: Policy;
40
+ /**
41
+ * Resolve the `to` param into directory rows, validate-first so a bad address
42
+ * rejects before any row is written:
43
+ * - wildcard (`['*']` canonical; bare `'*'` accepted as sugar) → every
44
+ * active identity except the sender; empty → no_recipients. Mixing the
45
+ * wildcard with explicit kids is ambiguous → wildcard_mixed.
46
+ * - kid / kid[] → deduped; unknown kid → unknown_recipient; inactive →
47
+ * inactive_recipient.
48
+ */
49
+ /**
50
+ * Whether `to:'*'` should fan a copy out to this identity.
51
+ *
52
+ * ABSENT AND NULL BOTH MEAN ELIGIBLE, and that is the whole reason this is a
53
+ * predicate rather than a `where` clause. `broadcastEligible` is declared
54
+ * `default: true`, but a default is applied on WRITE — every AgentIdentity row
55
+ * created before the field existed carries no such key and reads back as
56
+ * `null`.
57
+ *
58
+ * So `where: { broadcastEligible: { eq: true } }` would match NONE of them
59
+ * (and `eq: null` matches nothing either, under SQL three-valued logic),
60
+ * turning every broadcast into `no_recipients`. Opting OUT has to be an
61
+ * explicit `false`.
62
+ *
63
+ * Exported because it cannot be exercised through `createAgentIdentity`: that
64
+ * path applies the declared default, so a row written through the schema always
65
+ * carries the key. The pre-field shape is only reachable directly, which is
66
+ * what `mailbox-broadcast-eligibility.test.ts` does.
67
+ */
68
+ export declare function isBroadcastEligible(row: Record<string, unknown>): boolean;
69
+ /**
70
+ * Register AgentMessage's whole surface. The type declares `autoCrud: false`,
71
+ * so defineNamespace installed nothing for it and this function owns the lot:
72
+ * the type entry, get/update/delete — same resolvers and same params/returns
73
+ * installAutoCrud would have emitted — plus the custom family (sendAgentMessage,
74
+ * markAgentMessageRead, ackAgentMessage). What is deliberately absent is
75
+ * `createAgentMessage`: an advertised mutation the route only denies teaches
76
+ * callers to build a shape that cannot work, and the refusal reads like a
77
+ * grant problem. Field defs arrive pre-augmentation; the reserved engine
78
+ * fields are added here, the way defineNamespace adds them for autoCRUD types.
79
+ */
80
+ export declare function installMailboxMutations(ctx: NamespaceInstallContext, messageFields: Record<string, FieldDef>): void;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * cortext namespace definition. Spec: §5.11, §6.1.
3
+ *
4
+ * The context-store types (SessionLog, Decision, Todo, AgentRule,
5
+ * CouncilSession, CouncilAdvisor, Memory, AnchorIntent, Concept, ConceptLink,
6
+ * Skill) on the `defineNamespace` helper, plus the agent-to-agent messaging
7
+ * surface (AgentIdentity directory + AgentMessage mailbox). Notes:
8
+ * - There is no `{ upsert: true }` namespace option — `write` is upsert by
9
+ * default.
10
+ * - There is no per-type `deletable: true` — autoCRUD always installs
11
+ * `delete<T>`.
12
+ * - FK-shaped fields are first-class `field.crossRef` edges with explicit
13
+ * onDelete policies.
14
+ *
15
+ * Embeddable types declare `embed.source` metadata; substrate's recall plugin
16
+ * walks this at compose time and chains an embedding postWrite per match.
17
+ */
18
+ import type { NamespaceDefinition } from '@mnmal-ai/substrate/sdk';
19
+ /**
20
+ * Decision lifecycle states this vocabulary DOCUMENTS. ADVISORY, not enforced,
21
+ * like `AgentRule.category`. This is a list, not a census: nothing reconciles
22
+ * it against the store, so it never claims what is "in use".
23
+ *
24
+ * A free string is the honest encoding of a taxonomy nobody designed. An enum
25
+ * freezes it, and agents then either invent undeclared synonyms or pick the
26
+ * nearest legal word. `open` names a decision recorded before it was made.
27
+ *
28
+ * Nothing branches on `status` today (cold-start projects and renders it), but
29
+ * that is not the argument: `status` is a conventionally mechanical name, so a
30
+ * future consumer is likely to branch on it.
31
+ *
32
+ * THE DIRECTION MATTERS. Widening cannot invalidate stored data; it only
33
+ * surprises readers. Narrowing STRANDS rows — readable, not round-trippable,
34
+ * and selectively so, since a partial update avoiding the field still succeeds
35
+ * while a read-modify-write client gets `invalid_enum_value` on a row it never
36
+ * touched. So re-adding `field.enum` here later is not a symmetric undo. Do
37
+ * not do it.
38
+ *
39
+ * Add to this list when a genuinely NEW state earns its place; it feeds the
40
+ * onboarding contract, so what is here is what an agent is taught. `accepted`
41
+ * is deliberately an alias of `active` rather than a state of its own.
42
+ */
43
+ export declare const DECISION_STATUS_VOCAB: readonly ['open', 'active', 'superseded', 'deferred'];
44
+ /**
45
+ * Synonyms the server normalises on every write and filter. `field.vocab` with
46
+ * `strict: false` is NOT the enum warned against above: unknown values are
47
+ * still admitted, so nothing narrows and nothing strands. It only maps the
48
+ * words agents reach for onto the state they mean. `proposed` is a decision
49
+ * recorded before it was made, which is what `open` documents.
50
+ */
51
+ export declare const DECISION_STATUS_ALIASES: {
52
+ readonly accepted: 'active';
53
+ readonly adopted: 'active';
54
+ readonly decided: 'active';
55
+ readonly proposed: 'open';
56
+ };
57
+ /**
58
+ * Words agents reach for when a Todo stops, mapped onto lifecycle + outcome.
59
+ * Each closing word sets `status: closed` AND the resolution it
60
+ * means, so `status: done` still works and now records WHY. A caller that
61
+ * also sends a contradicting `resolution` is refused (`conflicting_implication`).
62
+ */
63
+ export declare const TODO_STATUS_ALIASES: {
64
+ readonly done: 'closed';
65
+ readonly finished: 'closed';
66
+ readonly completed: 'closed';
67
+ readonly shipped: 'closed';
68
+ readonly cancelled: 'closed';
69
+ readonly dropped: 'closed';
70
+ readonly superseded: 'closed';
71
+ readonly not_started: 'open';
72
+ };
73
+ export declare const TODO_STATUS_IMPLIES: {
74
+ readonly done: {
75
+ readonly resolution: 'completed';
76
+ };
77
+ readonly finished: {
78
+ readonly resolution: 'completed';
79
+ };
80
+ readonly completed: {
81
+ readonly resolution: 'completed';
82
+ };
83
+ readonly shipped: {
84
+ readonly resolution: 'completed';
85
+ };
86
+ readonly cancelled: {
87
+ readonly resolution: 'wont_do';
88
+ };
89
+ readonly dropped: {
90
+ readonly resolution: 'wont_do';
91
+ };
92
+ readonly superseded: {
93
+ readonly resolution: 'superseded';
94
+ };
95
+ };
96
+ export declare const TODO_RESOLUTION_ALIASES: {
97
+ readonly done: 'completed';
98
+ readonly shipped: 'completed';
99
+ readonly wontfix: 'wont_do';
100
+ readonly declined: 'wont_do';
101
+ readonly dup: 'duplicate';
102
+ readonly moot: 'obsolete';
103
+ };
104
+ /**
105
+ * AgentRule severity — CLOSED, unlike `category`. Cold-start branches on it:
106
+ * `must` rules render in the binding brief. `high` and `medium` are aliases
107
+ * rather than stranded values.
108
+ */
109
+ export declare const AGENT_RULE_SEVERITY_VOCAB: readonly ['must', 'should'];
110
+ export declare const AGENT_RULE_SEVERITY_ALIASES: {
111
+ readonly high: 'must';
112
+ readonly medium: 'should';
113
+ };
114
+ /**
115
+ * AgentRule categories. ADVISORY, not enforced.
116
+ *
117
+ * An enum here arrives narrower than the data and freezes the taxonomy. The
118
+ * damage is not a write rejection anyone notices but SILENT SUBSTITUTION:
119
+ * agents pick the nearest legal word and move on, and the taxonomy flattens
120
+ * toward whatever is allowed. Nothing consumes `category` mechanically —
121
+ * cold-start projects it and renders it, never groups or filters on it — and
122
+ * `ArchitectureConcern.category` in this same file is a free string too.
123
+ * Widening an enum would only move the wall to the next category.
124
+ *
125
+ * Add to this list when a genuinely new category earns its place; it feeds the
126
+ * onboarding contract so agents see the real taxonomy. Do NOT re-add
127
+ * `field.enum` over it.
128
+ */
129
+ export declare const AGENT_RULE_CATEGORY_VOCAB: readonly ['git', 'testing', 'style', 'process', 'tooling', 'communication', 'investigation', 'migration-patterns', 'permissions', 'scope-discipline', 'side-findings', 'verification'];
130
+ /**
131
+ * AgentMessage.status vocabulary single-sources from the kernel sibling
132
+ * — the same constant drives both the field.enum guard here and
133
+ * the kernel CHECK constraint, matching the TODO_*_VOCAB pattern. Re-exported
134
+ * so this module's surface carries it.
135
+ */
136
+ export { AGENT_MESSAGE_STATUS_VOCAB } from './kernel/index.js';
137
+ /**
138
+ * HostInstall.fragmentState: the cold-start fragment drift check's own verdict,
139
+ * plus `source` for the repo that ships the fragment.
140
+ */
141
+ export declare const HOST_INSTALL_FRAGMENT_STATES: readonly ['current', 'stale', 'missing', 'undigested', 'uncomparable', 'source', 'absent'];
142
+ /** HostInstall's per-check verdicts: the worst finding each check produced. */
143
+ export declare const HOST_INSTALL_CHECK_STATES: readonly ['ok', 'warn', 'unknown'];
144
+ export declare function buildClaudeNamespace(ns: string): NamespaceDefinition;
145
+ /** The canonical ordered list of type names registered by this namespace. */
146
+ export declare const CLAUDE_TYPE_NAMES: readonly ['SessionLog', 'Decision', 'Todo', 'AgentRule', 'CouncilSession', 'CouncilAdvisor', 'Memory', 'AnchorIntent', 'Concept', 'ConceptLink', 'Skill', 'AgentIdentity', 'AgentMessage', 'HostInstall'];
147
+ /**
148
+ * Architecture namespace — Claude-lineage code-review findings. Kept separate
149
+ * from the claude context namespace so lifecycle (compaction, retention,
150
+ * audit) differs: architectural findings outlive session-scoped rationale.
151
+ *
152
+ * Single type today (ArchitectureConcern). Notes:
153
+ * - `archived` is `field.boolean()` to match every other type that carries
154
+ * archived state.
155
+ * - `supersedes` is a self-ref `field.crossRef` edge with `set_null` —
156
+ * a concern survives the one it superseded going away.
157
+ */
158
+ export declare function buildArchitectureNamespace(ns: string): NamespaceDefinition;
159
+ export declare const ARCHITECTURE_TYPE_NAMES: readonly ['ArchitectureConcern'];
@@ -0,0 +1,76 @@
1
+ /**
2
+ * How people talk about cortext, for substrate's NL parser and judge. Curated
3
+ * operator rulings, kept here so the eval and production read one copy, owned
4
+ * by the namespace. Declared via `defineNamespace` annotations; substrate's NL
5
+ * layer validates them and the runtime can override per key.
6
+ */
7
+ export declare const CORTEXT_NL_RULINGS: {
8
+ unscopedTypes: string[];
9
+ liveStatus: {
10
+ Todo: string;
11
+ };
12
+ timeField: {
13
+ SessionLog: string;
14
+ };
15
+ typeAliases: {
16
+ inbox: string;
17
+ mail: string;
18
+ agents: string;
19
+ agent: string;
20
+ session: string;
21
+ sessions: string;
22
+ anchor: string;
23
+ rule: string;
24
+ rules: string;
25
+ task: string;
26
+ tasks: string;
27
+ decide: string;
28
+ decided: string;
29
+ };
30
+ fieldAliases: {
31
+ repo: string;
32
+ repository: string;
33
+ };
34
+ valueAliases: {
35
+ 'in force': {
36
+ type: string;
37
+ field: string;
38
+ eq: string;
39
+ }[];
40
+ 'signal-gated': {
41
+ type: string;
42
+ field: string;
43
+ startsWith: string;
44
+ }[];
45
+ ship: {
46
+ type: string;
47
+ field: string;
48
+ eq: string;
49
+ }[];
50
+ shipped: {
51
+ type: string;
52
+ field: string;
53
+ eq: string;
54
+ }[];
55
+ drop: {
56
+ type: string;
57
+ field: string;
58
+ in: string[];
59
+ }[];
60
+ dropped: {
61
+ type: string;
62
+ field: string;
63
+ in: string[];
64
+ }[];
65
+ };
66
+ typeNotes: {
67
+ SessionLog: string;
68
+ Decision: string;
69
+ Todo: string;
70
+ Memory: string;
71
+ AgentMessage: string;
72
+ AgentIdentity: string;
73
+ Concept: string;
74
+ AgentRule: string;
75
+ };
76
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Onboard route — returns a static JSON body describing the claude namespace
3
+ * surface. Mounted at `/substrate/v1/plugins/cortext/onboard` (the router is already
4
+ * prefixed by the server's `createPluginRouter`).
5
+ *
6
+ * Endpoint paths in the body:
7
+ * - Core query/mutate live at the unqualified `/substrate/v1/query` + `/substrate/v1/mutate`
8
+ * (§4.7). The namespace now rides inside the frame key (`<ns>/<name>`),
9
+ * not the URL.
10
+ * - Compact moves under the plugin prefix (`/substrate/v1/plugins/cortext/compact`).
11
+ * - WS endpoint still takes `namespace` as a companion field for agents
12
+ * that pre-bake their frame-key prefix.
13
+ */
14
+ import type { PluginRouteContext, PluginRouter } from '@mnmal-ai/substrate/sdk';
15
+ export declare function registerOnboardRoute(router: PluginRouter, _ctx: PluginRouteContext, retention: number, namespace: string): void;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * "Only the parties involved, or an operator" — the row-level authority the
3
+ * mailbox and the identity directory share.
4
+ *
5
+ * A Policy is synchronous and sees the caller, never the row, so it can say
6
+ * WHICH fields a write may touch but not WHOSE row it is. These wrappers sit
7
+ * on the resolver instead: they read the row, decide, and only then hand the
8
+ * call to the resolver they wrap. An id that matches no row falls through to
9
+ * the wrapped resolver, which owns the not-found contract.
10
+ *
11
+ * An operator is an identity granted `admin` (or `*`) in this namespace.
12
+ */
13
+ import type { NamespaceInstallContext, RegistryEntryResolver, RequestContext } from '@mnmal-ai/substrate/sdk';
14
+ /** Whether the caller holds `admin` (or `*`) in the namespace it is calling. */
15
+ export declare function isOperator(ctx: RequestContext): boolean;
16
+ /**
17
+ * AgentMessage update/delete: the sender, the recipient, or an operator.
18
+ * The sender is the key that signed the send (`_metadata.createdBy`); the
19
+ * recipient is the kid on the addressed directory row. Archiving someone
20
+ * else's mail hides it from their inbox, and deleting it destroys it, so
21
+ * neither is a bystander's call.
22
+ */
23
+ export declare function messageParticipantsOnly(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
24
+ /**
25
+ * AgentIdentity update: the identity itself (the caller's kid is the row's
26
+ * kid) or an operator. `kid` is the row's identity, so changing it is an
27
+ * operator act even on your own row: otherwise a row could be renamed onto a
28
+ * kid whose mail it would then receive.
29
+ */
30
+ export declare function identitySelfOnly(installCtx: NamespaceInstallContext, name: string, inner: RegistryEntryResolver): RegistryEntryResolver;
31
+ /** AgentIdentity delete: operators only. Mail hangs off these rows. */
32
+ export declare function operatorOnly(name: string, inner: RegistryEntryResolver): RegistryEntryResolver;