@theronap/cortex-mcp 0.9.78 → 0.9.80

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/lib/server.mjs CHANGED
@@ -11,6 +11,7 @@ import { runSendImessage } from './imessage_send.mjs'
11
11
  import { formatGrepHits } from './grep_cli.mjs'
12
12
  import { renderTriage } from './red_link_triage.mjs'
13
13
  import { runCodeGraphQuery } from './code_graph_cli.mjs'
14
+ import { renderTimelineClaimReceipt } from './timeline_claim_receipt.mjs'
14
15
 
15
16
  // Reactive red-link triage (Mechanism 2). On a read_page miss, ask the server whether the name is a
16
17
  // tracked wanted page, whether a bare node exists for it, and whether it's a deliberately demoted page,
@@ -299,8 +300,12 @@ export async function runServer(version) {
299
300
  const body = await res.text()
300
301
  throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
301
302
  }
302
- const { threads, records } = await res.json()
303
- if (!threads?.length && !records?.length) return { content: [{ type: 'text', text: 'Nothing in the backlog — everything captured so far has been claimed.' }] }
303
+ const { threads, records, claim } = await res.json()
304
+ const receipt = renderTimelineClaimReceipt(claim)
305
+ if (!threads?.length && !records?.length) {
306
+ const empty = 'Nothing in the backlog — everything captured so far has been claimed.'
307
+ return { content: [{ type: 'text', text: receipt ? `${empty}\n\n${receipt}` : empty }] }
308
+ }
304
309
 
305
310
  const sections = []
306
311
 
@@ -329,10 +334,47 @@ export async function runServer(version) {
329
334
  sections.push(`Unclaimed records (${records.length}) from other sources — newest first:\n${lines.join('\n')}`)
330
335
  }
331
336
 
337
+ if (receipt) sections.push(receipt)
332
338
  return { content: [{ type: 'text', text: sections.join('\n\n') }] }
333
339
  },
334
340
  )
335
341
 
342
+ // Read-only evidence status for the Gate 4 live trial. This MUST remain an aggregate query: a
343
+ // monitor needs to know whether enough sessions have exercised the loop, not who they were or what
344
+ // work they handled. In particular, it must never call timeline_pull as a shortcut, since that would
345
+ // manufacture the evidence it claims to observe.
346
+ server.registerTool(
347
+ 'gate4_status',
348
+ {
349
+ title: 'Gate 4 live-evidence status',
350
+ description: 'Read the aggregate, rolling-window status of the Gate 4 trial. This does not claim, attribute, resolve, or expose captured work. It reports machine-observable session, attribution, and recovery counts; a human still confirms that the qualifying sessions were normal work.',
351
+ inputSchema: {
352
+ days: z.number().optional().describe('rolling observation window in days (default 14; capped at 30)'),
353
+ },
354
+ },
355
+ async ({ days }) => {
356
+ const qs = typeof days === 'number' ? `?days=${encodeURIComponent(String(days))}` : ''
357
+ const res = await fetchCortex(`${BASE}/api/timeline/observation${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
358
+ if (!res.ok) {
359
+ const body = await res.text()
360
+ throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
361
+ }
362
+ const s = await res.json()
363
+ const machine = s.machineEvidencePassed ? 'MET' : 'NOT YET MET'
364
+ return {
365
+ content: [{ type: 'text', text:
366
+ `Gate 4 evidence, last ${s.window?.days ?? 14} days\n` +
367
+ `- session claims: ${s.sessionCount ?? 0}/3\n` +
368
+ `- session-attributed completions: ${s.attributedDone ?? 0} (need 1+)\n` +
369
+ `- expiry recoveries: ${s.recoveredClaims ?? 0} (need 1+)\n` +
370
+ `- anonymous session claims: ${s.anonymousSessionClaims ?? 0} (need 0)\n` +
371
+ `- non-session rows in window (reported, excluded): ${s.nonSessionClaims ?? 0}\n\n` +
372
+ `Machine evidence: ${machine}. Human attestation remains required: qualifying sessions must be normal work, not a staged demo.`,
373
+ }],
374
+ }
375
+ },
376
+ )
377
+
336
378
  server.registerTool(
337
379
  'search_org',
338
380
  {
@@ -0,0 +1,25 @@
1
+ // Render the route's claim envelope rather than inferring success from the rows it returned. A pull
2
+ // without this receipt is the exact failure mode Gate 4 is trying to rule out: work can be leased (or
3
+ // not leased) invisibly, and a reader has no way to distinguish the two.
4
+ export function renderTimelineClaimReceipt(claim) {
5
+ if (!claim || typeof claim !== 'object' || Array.isArray(claim)) return ''
6
+
7
+ if (claim.active !== true) {
8
+ if (claim.reason === 'session_key_required') {
9
+ return 'Viewing only — no session key was supplied, so no work was leased or attributed.'
10
+ }
11
+ if (claim.reason === 'disabled') return 'Viewing only — claiming was disabled, so no work was leased or attributed.'
12
+ return ''
13
+ }
14
+
15
+ const leased = Number(claim.leased)
16
+ const count = Number.isFinite(leased) && leased >= 0 ? Math.trunc(leased) : null
17
+ const expiry = typeof claim.leasedUntil === 'string' && claim.leasedUntil ? claim.leasedUntil : null
18
+
19
+ if (count === null) return 'Claim receipt: this session requested work; the server did not return a valid lease count.'
20
+ if (count === 0) return 'Claim receipt: no work was leased to this session.'
21
+
22
+ const noun = count === 1 ? 'record' : 'records'
23
+ return `Claim receipt: ${count} ${noun} leased to this session${expiry ? ` until ${expiry}` : ''}. ` +
24
+ 'Attribute only email threads you genuinely recognize; leave unknown work unjudged.'
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.78",
3
+ "version": "0.9.80",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {