-
Notifications
You must be signed in to change notification settings - Fork 0
feat(triage): wire contextual (digested comms) into ChittyTriage intake spine #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| -- 0019_contextual_provenance.sql | ||
| -- | ||
| -- Provenance + sensitivity columns for the contextual (digested comms) upstream. | ||
| -- | ||
| -- ChittyTriage intake spine (W4c): contextual → chittyrouter classify → | ||
| -- chittycommand. Every row inferred from the contextual store is AI-derived | ||
| -- and MUST be distinguishable from human/source-backed records: | ||
| -- - source = 'contextual' | ||
| -- - source_ref = the contextual message id (ctx-msg-<id>) / ChittyID | ||
| -- - confidence = classifier confidence (0..1) | ||
| -- - status = 'candidate' (NOT verified — AI/inference alone) | ||
| -- - sensitivity = 'business' | 'legalink' (per-edge Two-Space gate) | ||
| -- | ||
| -- @canon: chittycanon://gov/governance#classification-axes STATUS:PENDING | ||
| -- @canon: chittycanon://core/services/chittycommand/contextual-ingest | ||
| -- | ||
| -- Spine rule: a claim is `verified` only when >=1 NON-AI source backs it; AI / | ||
| -- inference alone => `candidate`. Conflicts never auto-resolve — they raise a | ||
| -- chittyagent-tasks item. See src/lib/contextual-ingest.ts. | ||
|
|
||
| -- ── cc_obligations: inferred bills/debts carry full provenance ────────────── | ||
| ALTER TABLE cc_obligations ADD COLUMN IF NOT EXISTS source text; | ||
| ALTER TABLE cc_obligations ADD COLUMN IF NOT EXISTS source_ref text; | ||
| ALTER TABLE cc_obligations ADD COLUMN IF NOT EXISTS confidence numeric; | ||
| ALTER TABLE cc_obligations ADD COLUMN IF NOT EXISTS sensitivity text NOT NULL DEFAULT 'business'; | ||
|
|
||
| -- ── cc_recommendations: provenance so triage output is traceable to comms ── | ||
| ALTER TABLE cc_recommendations ADD COLUMN IF NOT EXISTS source text; | ||
| ALTER TABLE cc_recommendations ADD COLUMN IF NOT EXISTS source_ref text; | ||
| ALTER TABLE cc_recommendations ADD COLUMN IF NOT EXISTS sensitivity text NOT NULL DEFAULT 'business'; | ||
|
|
||
| -- ── Dedup guard: one inferred obligation per contextual source_ref ───────── | ||
| -- Partial so it only constrains contextual-sourced rows; other upstreams | ||
| -- (quo, email_bills, manual) are unaffected. | ||
| CREATE UNIQUE INDEX IF NOT EXISTS cc_obligations_contextual_source_ref_uidx | ||
| ON cc_obligations (source_ref) | ||
| WHERE source = 'contextual' AND source_ref IS NOT NULL; | ||
|
|
||
| -- ── Idempotency for contextual_ingest intents (mirrors 0017 roux_ingest) ─── | ||
| -- Backs INSERT ... ON CONFLICT DO NOTHING so concurrent ingest runs of the | ||
| -- same contextual message collapse to one intent. | ||
| CREATE UNIQUE INDEX IF NOT EXISTS cc_intents_contextual_ingest_message_id_uidx | ||
| ON cc_intents ((payload->'source'->>'message_id')) | ||
| WHERE intent_type = 'contextual_ingest' | ||
| AND payload->'source'->>'message_id' IS NOT NULL; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_cc_obligations_source ON cc_obligations (source, status); | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,109 @@ | ||||||||
| /** | ||||||||
| * Real verification harness for the contextual → triage ingest. | ||||||||
| * | ||||||||
| * Runs the ACTUAL module (src/lib/contextual-ingest.ts) against: | ||||||||
| * - the contextual store (Neon delicate-moon, read) via CONTEXTUAL_DATABASE_URL | ||||||||
| * - a cool-bar-13270800 BRANCH (write) via DATABASE_URL | ||||||||
| * | ||||||||
| * The chittyagent-tasks prod token is gated (POLICY_BLOCKED_CHITTYCONNECT_ | ||||||||
| * UNAVAILABLE), so for the conflict→task proof we stand up a LOCAL instance of | ||||||||
| * the real /api/v1/tasks endpoint, backed by the SAME branch DB | ||||||||
| * (agent_tasks.tasks — chittyagent-tasks shares cool-bar's Hyperdrive). The | ||||||||
| * integrations.tasksClient HTTP call therefore exercises the real client and | ||||||||
| * writes a REAL row into the real agent_tasks.tasks schema on the branch. | ||||||||
| * | ||||||||
| * No mocks: real SQL, real schema, real classifier path (router /process is | ||||||||
| * 404 in prod → deterministic feature fallback, which is real inference, not a | ||||||||
| * stub). env.AI is absent outside the Worker runtime, so the AI branch is | ||||||||
| * skipped and the deterministic fallback is used — recorded in classifier_via. | ||||||||
| * | ||||||||
| * Usage: node scripts/verify-contextual-ingest.mjs | ||||||||
| * requires env: DATABASE_URL, CONTEXTUAL_DATABASE_URL | ||||||||
| */ | ||||||||
| import http from 'node:http'; | ||||||||
| import { neon } from '@neondatabase/serverless'; | ||||||||
| import { ingestContextual } from '../src/lib/contextual-ingest.ts'; | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Check how this script is expected to run and whether TS runtime support is configured.
node -v
rg -n -C3 "verify-contextual-ingest|tsx|ts-node|--loader|--import|experimental-strip-types" package.json || true
rg -n -C3 "Usage: node scripts/verify-contextual-ingest\\.mjs|import \\{ ingestContextual \\} from '../src/lib/contextual-ingest\\.ts'" scripts/verify-contextual-ingest.mjsRepository: chittyos/chittycommand Length of output: 628 🏁 Script executed: cat package.json | head -100Repository: chittyos/chittycommand Length of output: 1420 🏁 Script executed: find . -maxdepth 2 -name "tsconfig.json" -o -name "tsconfig*.json" | head -20Repository: chittyos/chittycommand Length of output: 101 🏁 Script executed: rg -n "build|compile|scripts" package.json -A 5 -B 1 | head -50Repository: chittyos/chittycommand Length of output: 863 🏁 Script executed: ls -la src/lib/contextual-ingest.* 2>/dev/null || echo "No file found"Repository: chittyos/chittycommand Length of output: 145 🏁 Script executed: find . -maxdepth 2 -name "wrangler.toml" -o -name "wrangler.json" | xargs cat 2>/dev/null | head -50Repository: chittyos/chittycommand Length of output: 158 🏁 Script executed: ls -la dist/ 2>/dev/null || echo "No dist directory"Repository: chittyos/chittycommand Length of output: 84 🏁 Script executed: ls -la build/ 2>/dev/null || echo "No build directory"Repository: chittyos/chittycommand Length of output: 85 🏁 Script executed: cat tsconfig.json | head -50Repository: chittyos/chittycommand Length of output: 636 🏁 Script executed: cat -n scripts/verify-contextual-ingest.mjs | head -40Repository: chittyos/chittycommand Length of output: 2260 🏁 Script executed: grep -r "verify-contextual-ingest" . --include="*.md" --include="*.json" --include="*.sh" 2>/dev/null | head -20Repository: chittyos/chittycommand Length of output: 48 🏁 Script executed: # Check if there's a compiled JavaScript version of the TypeScript file anywhere
find . -name "contextual-ingest.js" -o -name "contextual-ingest.mjs" 2>/dev/nullRepository: chittyos/chittycommand Length of output: 48 🏁 Script executed: # Check if there's any build process that might compile scripts directory
rg -n "scripts/" package.json wrangler.toml 2>/dev/null || trueRepository: chittyos/chittycommand Length of output: 145 🏁 Script executed: # Check if there's any loader or flag configuration in wrangler.toml or environment
cat wrangler.tomlRepository: chittyos/chittycommand Length of output: 112 🏁 Script executed: # Check if there's a compiled version in the scripts directory or elsewhere
find . -type f -name "*.mjs" -o -name "*.js" | grep -E "(scripts|src/lib)" | head -20Repository: chittyos/chittycommand Length of output: 105 🏁 Script executed: # Check what Node.js experimental features might be enabled
grep -r "experimental" . --include="package.json" --include="wrangler.toml" --include=".npmrc" 2>/dev/null || echo "No experimental config found"Repository: chittyos/chittycommand Length of output: 95 🏁 Script executed: # Verify if this script is actually meant to be run with node or with a different tool
file scripts/verify-contextual-ingest.mjsRepository: chittyos/chittycommand Length of output: 111 Importing Line 25 imports
Running the script with plain 🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| const BRANCH_URL = process.env.DATABASE_URL; | ||||||||
| const CTX_URL = process.env.CONTEXTUAL_DATABASE_URL; | ||||||||
| if (!BRANCH_URL || !CTX_URL) { | ||||||||
| console.error('Set DATABASE_URL (branch) and CONTEXTUAL_DATABASE_URL (contextual)'); | ||||||||
| process.exit(1); | ||||||||
| } | ||||||||
|
|
||||||||
| const LOCAL_TASKS_TOKEN = 'verify-harness-local-token'; | ||||||||
| const sql = neon(BRANCH_URL); | ||||||||
|
|
||||||||
| // Local stand-in for chittyagent-tasks POST /api/v1/tasks, backed by the SAME | ||||||||
| // branch DB (real agent_tasks.tasks schema). This is the real createTask INSERT | ||||||||
| // from chittyentity/workers/shared/agent-tasks.ts. | ||||||||
| const server = http.createServer((req, res) => { | ||||||||
| if (req.method !== 'POST' || !req.url.endsWith('/api/v1/tasks')) { | ||||||||
| res.writeHead(404); return res.end('not found'); | ||||||||
| } | ||||||||
| if (req.headers['authorization'] !== `Bearer ${LOCAL_TASKS_TOKEN}`) { | ||||||||
| res.writeHead(403); return res.end(JSON.stringify({ success: false, error: 'Invalid token' })); | ||||||||
| } | ||||||||
| let body = ''; | ||||||||
| req.on('data', (c) => (body += c)); | ||||||||
| req.on('end', async () => { | ||||||||
| try { | ||||||||
| const i = JSON.parse(body); | ||||||||
| const [row] = await sql` | ||||||||
| INSERT INTO agent_tasks.tasks | ||||||||
| (title, description, task_type, assigned_agent, source_agent, status, priority, | ||||||||
| payload, depends_on, triage_class, urgency, needs_nick, notify_policy) | ||||||||
| VALUES | ||||||||
| (${i.title}, ${i.description ?? null}, ${i.task_type}, ${i.assigned_agent}, | ||||||||
| ${i.source_agent ?? 'chittycommand'}, 'pending', ${i.priority ?? 5}, | ||||||||
| ${JSON.stringify(i.payload ?? {})}, ${[]}, ${i.triage_class ?? null}, | ||||||||
| ${i.urgency ?? null}, ${i.needs_nick ?? false}, 'done_only') | ||||||||
| RETURNING id, title, assigned_agent, status`; | ||||||||
| res.writeHead(201, { 'content-type': 'application/json' }); | ||||||||
| res.end(JSON.stringify({ success: true, data: row })); | ||||||||
| } catch (err) { | ||||||||
| console.error('[local-tasks] insert failed:', err); | ||||||||
| res.writeHead(500); res.end(JSON.stringify({ success: false, error: String(err) })); | ||||||||
Check warningCode scanning / CodeQL Exception text reinterpreted as HTML Medium Exception text Error loading related location Loading Check warningCode scanning / CodeQL Information exposure through a stack trace Medium
This information exposed to the user depends on
stack trace information Error loading related location Loading |
||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid returning raw exception text in HTTP error responses. Line 66 exposes 🔒 Suggested fix- res.writeHead(500); res.end(JSON.stringify({ success: false, error: String(err) }));
+ res.writeHead(500, { 'content-type': 'application/json' });
+ res.end(JSON.stringify({ success: false, error: 'internal_error' }));📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: CodeQL[warning] 66-66: Exception text reinterpreted as HTML [warning] 66-66: Information exposure through a stack trace 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||
| } | ||||||||
| }); | ||||||||
| }); | ||||||||
|
|
||||||||
| await new Promise((r) => server.listen(0, r)); | ||||||||
| const port = server.address().port; | ||||||||
|
|
||||||||
| const env = { | ||||||||
| DATABASE_URL: BRANCH_URL, | ||||||||
| CONTEXTUAL_DATABASE_URL: CTX_URL, | ||||||||
| // router /process is 404 in prod; classifier falls through to deterministic | ||||||||
| // feature path (real). Set the real URL so the attempt is genuinely made. | ||||||||
| CHITTYROUTER_URL: 'https://router.chitty.cc', | ||||||||
| CHITTYAGENT_TASKS_URL: `http://127.0.0.1:${port}`, | ||||||||
| CHITTYAGENT_TASKS_TOKEN: LOCAL_TASKS_TOKEN, | ||||||||
| COMMAND_KV: { get: async () => null, put: async () => {}, delete: async () => {} }, | ||||||||
| // no AI binding outside the worker runtime | ||||||||
| }; | ||||||||
|
|
||||||||
| const before = (await sql`SELECT | ||||||||
| (SELECT count(*) FROM cc_intents WHERE intent_type='contextual_ingest') intents, | ||||||||
| (SELECT count(*) FROM cc_obligations WHERE source='contextual') obligations, | ||||||||
| (SELECT count(*) FROM cc_recommendations WHERE source='contextual') recs, | ||||||||
| (SELECT count(*) FROM agent_tasks.tasks WHERE source_agent='chittycommand' AND task_type='reconciliation') tasks`)[0]; | ||||||||
|
|
||||||||
| const result = await ingestContextual(env, sql, { limit: 50 }); | ||||||||
|
|
||||||||
| const after = (await sql`SELECT | ||||||||
| (SELECT count(*) FROM cc_intents WHERE intent_type='contextual_ingest') intents, | ||||||||
| (SELECT count(*) FROM cc_obligations WHERE source='contextual') obligations, | ||||||||
| (SELECT count(*) FROM cc_recommendations WHERE source='contextual') recs, | ||||||||
| (SELECT count(*) FROM agent_tasks.tasks WHERE source_agent='chittycommand' AND task_type='reconciliation') tasks`)[0]; | ||||||||
|
|
||||||||
| console.log('=== ingest result ==='); | ||||||||
| console.log(JSON.stringify(result, null, 2)); | ||||||||
| console.log('=== before → after ==='); | ||||||||
| console.log('cc_intents(contextual_ingest):', before.intents, '→', after.intents); | ||||||||
| console.log('cc_obligations(contextual): ', before.obligations, '→', after.obligations); | ||||||||
| console.log('cc_recommendations(contextual):', before.recs, '→', after.recs); | ||||||||
| console.log('agent_tasks.tasks(reconciliation/chittycommand):', before.tasks, '→', after.tasks); | ||||||||
|
|
||||||||
| server.close(); | ||||||||
| process.exit(0); | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce DB-level domain constraints for
confidenceandsensitivity.confidenceis documented as0..1andsensitivitydrives business vs privileged gating, but both columns currently accept invalid values. Add CHECK constraints to keep persisted state safe and enforceable at the schema boundary.Suggested migration patch
🤖 Prompt for AI Agents