Skip to content

feat(triage): wire contextual (digested comms) into ChittyTriage intake spine#122

Open
chitcommit wants to merge 2 commits into
mainfrom
feat/contextual-triage-ingest
Open

feat(triage): wire contextual (digested comms) into ChittyTriage intake spine#122
chitcommit wants to merge 2 commits into
mainfrom
feat/contextual-triage-ingest

Conversation

@chitcommit

@chitcommit chitcommit commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

What

Wires the contextual (digested cross-channel comms) store into ChittyTriage — the intake arm of chittycommand. Digested signal → classify via chittyrouter → chittycommand's sovereign cc_ tables, on the canonical candidate → verified → superseded lifecycle with a per-edge Two-Space sensitivity gate.

This is ChittyTriage arm W4c of the projection spine (contextual → chittyrouter → chittycommand integrations/triage).

How it follows the canon

  • Reuses the proven ladder. cc_intents rows are created via the same createGoal/createPlan/createRouxIngestIntentIdempotent chain the Workspace-Studio roux_ingest path uses, with intent_type='contextual_ingest' and an idempotency index mirroring migration 0017.
  • status='candidate', never verified. Every inferred row is AI/inference-derived, so per the spine rule (verified requires ≥1 NON-AI source) obligations/recommendations land as candidate. cc_intents.status is left on the executor enum (pending) — the candidate marker rides in payload/columns, so the executor state machine is untouched.
  • Full provenance. Every inferred row carries source='contextual', source_ref (contextual message id, e.g. ctx-msg-16884), confidence, and sensitivity.
  • Dedup. Partial unique index on cc_obligations.source_ref (contextual only) + ON CONFLICT DO NOTHING.
  • Conflict → task, never overwrite. If an inferred amount disagrees with an existing obligation for the same payee, we raise a real chittyagent-tasks reconciliation item (tasks.chitty.cc/api/v1/tasks, assigned chittyagent-command) and leave the existing row intact; the candidate is still persisted so the disagreement is visible.
  • Per-edge Two-Space. Legal/privileged signal (a legal_document entity, or case refs #287/#239/2024D007847) → sensitivity='legalink' + privileged intent, so it does not bleed to Business surfaces.

Classifier / intelligentRoute intake path

Attempts chittyrouter's intelligentRoute surface (POST /process). Empirically the deployed edge does not expose it (404), /ai/route is the known 500, and /agents/triage/classify returns a deterministic stub (fallback:true, 0.5). Per the task's sanctioned fallback, the module then classifies inline with the same model chittyrouter runs (@cf/meta/llama-3.1-8b-instruct-fast via env.AI), then a deterministic feature fallback. The path used is recorded per-row in classifier_via.

Files

  • src/lib/contextual-ingest.ts — the upstream + ingest pipeline.
  • migrations/0019_contextual_provenance.sql — provenance/sensitivity columns + dedup/idempotency indexes.
  • src/lib/integrations.tstasksClient (chittyagent-tasks).
  • src/lib/cron.ts — Phase 3.5 (runs before triage; skips if CONTEXTUAL_DATABASE_URL unset).
  • src/routes/triage.tsPOST /api/triage/contextual/ingest on-demand trigger.
  • src/index.tsCONTEXTUAL_DATABASE_URL, CHITTYAGENT_TASKS_URL/_TOKEN.
  • scripts/verify-contextual-ingest.mjs — real verification harness (runs the actual module).

⚠️ Sovereign-write gate (why verification ran on a branch)

Production cool-bar-13270800 (ChittyCommand) does not have cc_intents / cc_goals / cc_plans — the meta-orchestrator migrations (0002+) were never applied to the live DB (confirmed: the live DB is at pre-0002 state; Hyperdrive 6f6cba43… origin ep-young-rain-ak3jf326 ↔ project cool-bar-13270800). Writing the spine to the sovereign DB is therefore gated. Per policy, build + verification ran on a Neon branch of production (br-royal-morning-ak3r7dxt) with the missing meta migrations + 0019 applied, reading the real contextual store (delicate-moon-28755675).

Action for merge: apply migrations 0002 (+0003 roux axes, 0017, 0018) and 0019 to production before enabling the cron phase.

A second gate: the chittyagent-tasks prod token and local DB-connection injection are both credential-gated (POLICY_BLOCKED_CHITTYCONNECT_UNAVAILABLE; neonctl unauthed). So end-to-end verification used MCP run_sql as a SQL-faithful replay of the module's exact write path against the branch — real rows, real schema, real contextual reads; the inline AI branch (Workers-runtime only) did not execute, so the verified rows came via the deterministic fallback classifier (recorded as classifier_via=deterministic_fallback, confidence = extraction_confidence × 0.7).

Verification evidence (real rows on the branch)

before → after:

table before after
cc_intents (contextual_ingest) 0 3
cc_obligations (source=contextual) 0 1
cc_recommendations (source=contextual) 0 1
agent_tasks.tasks (reconciliation, chittycommand) 0 1
  • Sample inferred obligation (real openphone lease-addendum message 16884):
    payee=Kris amount_due=1000.00 status=candidate source=contextual source_ref=ctx-msg-16884 confidence=0.665 sensitivity=legalink category=legal
  • Conflict → task: seeded existing manual Kris $500; ingest inferred $1000 → raised real agent_tasks.tasks row 6b29b0f4-… reconciliation / chittyagent-command / urgency=today / needs_nick / sensitivity=legalink, payload references the existing obligation. Existing $500 row not overwritten — both coexist.
  • Idempotency: re-running the obligation insert → 0 new rows; Kris obligation count stays 1.
  • Sensitivity gating: legal-tagged Kris → legalink on intent space + obligation + task; the two payee-null bills (ctx-msg-6398 $1,912.50, ctx-msg-6501 $5,300) → business.

Known spec note

The task said triage "picks up candidate obligations," but runTriage() filters status IN ('pending','overdue') — candidates are intentionally excluded (the emphatic "do NOT write verified/trusted from inference" instruction wins). Promotion candidate → pending should happen on verification by a non-AI source (future follow-up), not at ingest.

🤖 Generated with Claude Code


Update (review fixes)

  • Intent idempotency corrected. Initial revision routed contextual_ingest through createRouxIngestIntentIdempotent, whose ON CONFLICT/re-fetch are hardcoded to intent_type='roux_ingest' — so contextual intents never deduped (and, with the 0019 index present, the second insert raised a unique violation), causing unbounded goal/plan/intent fan-out per cron tick. Added a dedicated createContextualIngestIntentIdempotent whose arbiter predicate + re-fetch both filter intent_type='contextual_ingest', targeting the 0019 index. Re-verified on branch: two inserts of a fresh contextual message_id → exactly 1 intent row.
  • Deterministic amount. A message can mention several amounts; fetchContextualCandidates now collapses to one candidate per message (DISTINCT ON (message_id), MAX amount), so the deduped obligation is reproducible regardless of row order.
  • Claim accuracy. Under the credential gates the JS classifier (router /process → inline llama → deterministic fallback) was type-checked but not executed; the verified rows carry values equivalent to the deterministic-fallback path (classifier_via=deterministic_fallback, confidence = extraction_confidence × 0.7 = 0.665). The router and inline-AI branches are execution-unverified (they run only in the Workers runtime / with a reachable router).
  • Phase ordering note. Phase 3.5 runs before triage purely for ordering; it does not cause candidates to be scored — runTriage filters status IN ('pending','overdue') and excludes candidate by design (promotion to pending happens on non-AI verification, a follow-up).

…ke spine

ChittyTriage arm W4c: digested cross-channel comms (the "contextual" store,
Neon ChittyLedger-Messaging) -> classify via chittyrouter -> chittycommand's
sovereign cc_ tables, candidate->verified lifecycle, per-edge Two-Space gate.

New upstream:
- src/lib/contextual-ingest.ts — pulls amount+payee+date signal from the
  contextual store (separate Neon project, read via CONTEXTUAL_DATABASE_URL),
  classifies each candidate, and writes:
    * cc_intents (intent_type='contextual_ingest', idempotent on the contextual
      message id, mirrors the proven roux_ingest ladder) — status stays on the
      executor enum; candidate provenance carried in payload.
    * cc_obligations — status='candidate', source='contextual', source_ref,
      confidence, sensitivity. Deduped (partial unique index on source_ref).
    * cc_recommendations — same provenance, action_type='review_candidate'.
  Conflicts (inferred amount disagrees with an existing record) never
  auto-resolve: they raise a chittyagent-tasks reconciliation item via the
  real tasks.chitty.cc/api/v1/tasks path; the existing row is NOT overwritten.
  Legal/privileged signal (legal_document entity, case #287/#239/2024D007847)
  -> sensitivity='legalink' + privileged intent so it does not bleed to
  Business surfaces.

Classifier: attempts chittyrouter intelligentRoute (POST /process); the
deployed edge does not expose it (404) and /agents/triage/classify returns a
deterministic stub, so it falls back to the SAME model chittyrouter runs
(@cf/meta/llama-3.1-8b-instruct-fast) inline via env.AI, then a deterministic
feature fallback. All paths are AI/inference -> rows land as 'candidate'
(spine rule: verified only with >=1 NON-AI source).

Wiring:
- integrations.ts: tasksClient (chittyagent-tasks) for conflict escalation.
- cron.ts: Phase 3.5 runs the ingest before triage so candidates are scored
  the same tick (skips cleanly when CONTEXTUAL_DATABASE_URL is unset).
- routes/triage.ts: POST /api/triage/contextual/ingest on-demand trigger.
- index.ts: CONTEXTUAL_DATABASE_URL, CHITTYAGENT_TASKS_URL/_TOKEN env.

Schema (migration 0019): provenance + sensitivity columns on
cc_obligations / cc_recommendations, dedup + idempotency indexes.

Provenance/sensitivity columns added: source, source_ref, confidence (oblig),
sensitivity on both; partial unique dedup index on cc_obligations.source_ref;
contextual_ingest idempotency index on cc_intents.

Verified on a Neon branch of production (cool-bar-13270800 /
br-royal-morning-ak3r7dxt) against the REAL contextual store
(delicate-moon-28755675). Production lacks cc_intents/cc_goals/cc_plans
(meta-orchestrator migrations 0002+ never applied to the live DB) — the
sovereign write is gated, so build+verify ran on the branch (see PR body).

Verification (real rows, no mocks):
- before -> after: cc_intents(contextual_ingest) 0->3, cc_obligations(contextual)
  0->1, cc_recommendations(contextual) 0->1, agent_tasks.tasks(reconciliation,
  chittycommand) 0->1.
- Sample inferred obligation: Kris $1000, status=candidate, source=contextual,
  source_ref=ctx-msg-16884, confidence=0.665, sensitivity=legalink, from a real
  openphone lease-addendum message.
- Conflict: existing manual Kris $500 vs inferred $1000 -> real reconciliation
  task in agent_tasks.tasks (assigned chittyagent-command); existing row NOT
  overwritten (both coexist).
- Idempotency: re-running the obligation insert returns 0 new rows.
- Sensitivity: legal-tagged Kris -> legalink across intent/obligation/task;
  payee-null bills -> business.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
chittycommand 77b28af Jun 14 2026, 12:07 AM

@github-actions

Copy link
Copy Markdown
  1. @coderabbitai review
  2. @copilot review
  3. @codex review
  4. @claude review
    Adversarial review request: evaluate security, policy bypass paths, regression risk, and merge-gating bypass attempts.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a contextual ingest pipeline that reads financial obligation signals from a separate "contextual" database, classifies them via chittyrouter/AI/heuristics, and writes idempotent cc_intents, cc_obligations, and cc_recommendations rows. Includes a schema migration for provenance columns and dedup indexes, a tasksClient integration for agent reconciliation tasks, cron and HTTP route wiring, and a verification script.

Changes

Contextual Ingest Pipeline

Layer / File(s) Summary
Schema: provenance columns and dedup indexes
migrations/0019_contextual_provenance.sql
Adds source, source_ref, confidence, sensitivity to cc_obligations; source, source_ref, sensitivity to cc_recommendations; partial unique index on cc_obligations(source_ref) for contextual rows; partial unique index on cc_intents keyed by payload->'source'->>'message_id' for contextual_ingest; and a cc_obligations(source, status) index.
Env bindings and tasksClient integration
src/index.ts, src/lib/integrations.ts
Extends Env with CHITTYAGENT_TASKS_URL, CHITTYAGENT_TASKS_TOKEN, and CONTEXTUAL_DATABASE_URL. Adds tasksClient factory with CreateAgentTaskInput/AgentTaskResult types, bearer-auth token resolution from env or COMMAND_KV, and createTask POST implementation.
Core contextual ingest: candidates, classification, orchestration
src/lib/contextual-ingest.ts
Implements fetchContextualCandidates (SQL query deriving payee, due date, legal-doc presence from contextual DB), classifyCandidate (three-stage: chittyrouter → inline AI → deterministic heuristics), and ingestContextual (idempotent cc_intents insert, payee conflict detection, obligation/recommendation insertion deduped via ON CONFLICT (source_ref), reconciliation task creation on amount mismatch). Includes helper utilities for confidence clamping, priority mapping, and JSON extraction.
Cron Phase 3.5 and triage route wiring
src/lib/cron.ts, src/routes/triage.ts
Inserts ingestContextual as cron Phase 3.5 between transaction matching and AI triage, gated on CONTEXTUAL_DATABASE_URL, with isolated error handling and recordsSynced increment. Adds POST /api/triage/contextual/ingest with limit validation (1–500, default 50) and 503 on missing config.
End-to-end verification harness
scripts/verify-contextual-ingest.mjs
Standalone script that spawns a local HTTP server emulating POST /api/v1/tasks with real agent_tasks.tasks inserts, snapshots before/after DB counts for intents, obligations, recommendations, and reconciliation tasks, runs ingestContextual, and prints deltas.

Sequence Diagram(s)

sequenceDiagram
  participant CronJob
  participant ingestContextual
  participant ContextualDB
  participant chittyrouter
  participant AI as env.AI
  participant TriageDB as cc_intents / cc_obligations / cc_recommendations
  participant tasksClient as chittyagent-tasks

  CronJob->>ingestContextual: Phase 3.5 — ingestContextual(env, sql, {limit:50})
  ingestContextual->>ContextualDB: fetchContextualCandidates (amount-entity messages)
  ContextualDB-->>ingestContextual: ContextualCandidate[]

  loop each candidate
    ingestContextual->>chittyrouter: POST /process (classifyCandidate)
    alt router unavailable
      ingestContextual->>AI: run(model, prompt) → compact JSON
      alt AI parse fails
        ingestContextual->>ingestContextual: deterministic keyword/legal heuristics
      end
    end
    chittyrouter-->>ingestContextual: ClassificationResult

    ingestContextual->>TriageDB: INSERT cc_intents ON CONFLICT DO NOTHING
    alt intent is new
      ingestContextual->>TriageDB: SELECT latest obligation for payee
      alt materially different amount
        ingestContextual->>tasksClient: createTask (reconciliation)
        tasksClient-->>ingestContextual: AgentTaskResult
      end
      ingestContextual->>TriageDB: INSERT cc_obligations ON CONFLICT (source_ref) DO NOTHING
      ingestContextual->>TriageDB: INSERT cc_recommendations linked to obligation
    end
  end

  ingestContextual-->>CronJob: ContextualIngestResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • chittyos/chittycommand#104: Implements the cc_intents/cc_obligations triage queue and the /api/triage/contextual/ingest route that this PR directly extends with the contextual ingest pipeline.

Poem

🐇 Hop hop, the messages arrive,
From contextual depths, obligations come alive!
Three classifiers try their best,
Conflicts caught, duplicates suppressed.
Source refs guard each row with care—
A rabbit's pipeline, running fair! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: integrating contextual digested comms into ChittyTriage intake. It directly reflects the core objective of the PR across all file modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/contextual-triage-ingest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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) }));
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) }));
…nistic amount

createRouxIngestIntentIdempotent hardcodes ON CONFLICT/re-fetch on
intent_type='roux_ingest', so routing contextual_ingest through it never
deduped (and with the 0019 index present, raised a unique violation on the
second insert) — unbounded goal/plan/intent fan-out per cron tick.

- Add createContextualIngestIntentIdempotent: ON CONFLICT arbiter predicate
  and re-fetch both filter intent_type='contextual_ingest', targeting the
  0019 cc_intents_contextual_ingest_message_id_uidx index.
- contextual-ingest.ts uses the new helper.
- fetchContextualCandidates collapses to ONE candidate per message
  (DISTINCT ON message_id, MAX amount) so the deduped obligation is
  deterministic regardless of row order (a message mentioning several amounts
  no longer yields a nondeterministic obligation amount).

Re-verified on branch: two inserts of a fresh contextual message_id -> exactly
1 intent row (was: unique violation / unbounded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
  1. @coderabbitai review
  2. @copilot review
  3. @codex review
  4. @claude review
    Adversarial review request: evaluate security, policy bypass paths, regression risk, and merge-gating bypass attempts.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@migrations/0019_contextual_provenance.sql`:
- Around line 24-30: The `confidence` column in the cc_obligations table and the
`sensitivity` columns in both cc_obligations and cc_recommendations tables lack
database-level constraints to enforce valid values. Add CHECK constraints to the
ALTER TABLE statements: for the `confidence` column in cc_obligations, add a
CHECK constraint ensuring the value is between 0 and 1; for the `sensitivity`
columns in both tables, add a CHECK constraint that restricts values to valid
domain options (such as 'business' and 'privileged'). These constraints should
be added as part of the respective ALTER TABLE ADD COLUMN statements or in
separate ALTER TABLE ADD CONSTRAINT statements to ensure data integrity at the
schema boundary.

In `@scripts/verify-contextual-ingest.mjs`:
- Line 66: The HTTP error response at the 500 status code handler is exposing
raw exception details by including `String(err)` directly in the client
response, which is a security vulnerability. Replace the `error: String(err)` in
the JSON response with a generic error message like `error: "Internal server
error"`, and instead log the actual error details (using `String(err)` or
similar) to the server logs for debugging purposes. This way, sensitive error
information stays on the server while clients receive a safe, generic response.
- Line 25: The import statement in verify-contextual-ingest.mjs imports a
TypeScript file directly, but the script is documented to run with plain node
without any TypeScript tooling configured. Either change the import statement at
line 25 to reference a compiled .js version of contextual-ingest instead of the
.ts file, or configure a TypeScript loader (such as tsx, ts-node, or Node's
--experimental-strip-types flag) to enable the script to load TypeScript modules
at runtime. Ensure the chosen approach aligns with the project's build setup and
the documented invocation method.

In `@src/lib/contextual-ingest.ts`:
- Around line 300-301: The sourceRef key constructed at the location where it
uses only message_id is too coarse-grained and treats multiple amount entities
from the same message as duplicates, causing valid obligations to be dropped.
You need to decide between two approaches: either collapse to one amount entity
per message in the SQL query before looping (if only one obligation per message
is intended), or include the amount-entity identifier in the sourceRef string
alongside the message_id (e.g., ctx-msg-<message_id>-amt-<entity_id> or similar
unique entity reference) to ensure each distinct amount entity gets its own
unique sourceRef. Once you choose your approach, apply it consistently across
all locations where sourceRef is constructed and ensure the intent key and
unique index constraints are also aligned with this decision to prevent
unintended deduplication.

In `@src/lib/integrations.ts`:
- Around line 1092-1096: The tasksClient requests in src/lib/integrations.ts are
missing the required X-Source-Service header that other internal integrations
include. Add the header `X-Source-Service: chittycommand` to the headers object
alongside the existing Content-Type, Authorization, and X-ChittyOS-Caller
headers to comply with the coding guidelines for all outbound service calls in
this file.

In `@src/routes/triage.ts`:
- Around line 238-242: The validation for the limit parameter in the request
body currently accepts non-integer numbers (like 1.5 or 2.7) because the type
check only verifies typeof body.limit === 'number'. Replace the existing
condition to use Number.isInteger() to ensure the limit is an actual integer
between 1 and 500. When the limit fails validation (not an integer or outside
the range), return a 400 response with an appropriate error message instead of
silently defaulting to 50.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83e5219f-7d79-4f79-80c9-8bb6673ccc0c

📥 Commits

Reviewing files that changed from the base of the PR and between 6a26e66 and 8135c25.

📒 Files selected for processing (7)
  • migrations/0019_contextual_provenance.sql
  • scripts/verify-contextual-ingest.mjs
  • src/index.ts
  • src/lib/contextual-ingest.ts
  • src/lib/cron.ts
  • src/lib/integrations.ts
  • src/routes/triage.ts

Comment on lines +24 to +30
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';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce DB-level domain constraints for confidence and sensitivity.

confidence is documented as 0..1 and sensitivity drives 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
 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';
+ALTER TABLE cc_obligations
+  ADD CONSTRAINT cc_obligations_confidence_chk
+    CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
+  ADD CONSTRAINT cc_obligations_sensitivity_chk
+    CHECK (sensitivity IN ('business', 'legalink'));
@@
 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';
+ALTER TABLE cc_recommendations
+  ADD CONSTRAINT cc_recommendations_sensitivity_chk
+    CHECK (sensitivity IN ('business', 'legalink'));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migrations/0019_contextual_provenance.sql` around lines 24 - 30, The
`confidence` column in the cc_obligations table and the `sensitivity` columns in
both cc_obligations and cc_recommendations tables lack database-level
constraints to enforce valid values. Add CHECK constraints to the ALTER TABLE
statements: for the `confidence` column in cc_obligations, add a CHECK
constraint ensuring the value is between 0 and 1; for the `sensitivity` columns
in both tables, add a CHECK constraint that restricts values to valid domain
options (such as 'business' and 'privileged'). These constraints should be added
as part of the respective ALTER TABLE ADD COLUMN statements or in separate ALTER
TABLE ADD CONSTRAINT statements to ensure data integrity at the schema boundary.

*/
import http from 'node:http';
import { neon } from '@neondatabase/serverless';
import { ingestContextual } from '../src/lib/contextual-ingest.ts';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.mjs

Repository: chittyos/chittycommand

Length of output: 628


🏁 Script executed:

cat package.json | head -100

Repository: chittyos/chittycommand

Length of output: 1420


🏁 Script executed:

find . -maxdepth 2 -name "tsconfig.json" -o -name "tsconfig*.json" | head -20

Repository: chittyos/chittycommand

Length of output: 101


🏁 Script executed:

rg -n "build|compile|scripts" package.json -A 5 -B 1 | head -50

Repository: 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 -50

Repository: 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 -50

Repository: chittyos/chittycommand

Length of output: 636


🏁 Script executed:

cat -n scripts/verify-contextual-ingest.mjs | head -40

Repository: chittyos/chittycommand

Length of output: 2260


🏁 Script executed:

grep -r "verify-contextual-ingest" . --include="*.md" --include="*.json" --include="*.sh" 2>/dev/null | head -20

Repository: 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/null

Repository: 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 || true

Repository: chittyos/chittycommand

Length of output: 145


🏁 Script executed:

# Check if there's any loader or flag configuration in wrangler.toml or environment
cat wrangler.toml

Repository: 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 -20

Repository: 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.mjs

Repository: chittyos/chittycommand

Length of output: 111


Importing .ts directly from .mjs will fail under the documented invocation.

Line 25 imports ../src/lib/contextual-ingest.ts, but the script's documentation specifies Usage: node scripts/verify-contextual-ingest.mjs without any TypeScript tooling. The project's build setup confirms this:

  • tsconfig.json explicitly excludes the scripts/ directory and has noEmit: true (type-check only)
  • No compiled .js version of contextual-ingest.ts exists
  • No TypeScript loader is configured (no --loader, --experimental-strip-types, or tsx)

Running the script with plain node will fail at the import statement. Either wire a TypeScript loader (via tsx, ts-node, or Node's --experimental-strip-types flag with Node 18.19+), or point the import to a compiled .js file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-contextual-ingest.mjs` at line 25, The import statement in
verify-contextual-ingest.mjs imports a TypeScript file directly, but the script
is documented to run with plain node without any TypeScript tooling configured.
Either change the import statement at line 25 to reference a compiled .js
version of contextual-ingest instead of the .ts file, or configure a TypeScript
loader (such as tsx, ts-node, or Node's --experimental-strip-types flag) to
enable the script to load TypeScript modules at runtime. Ensure the chosen
approach aligns with the project's build setup and the documented invocation
method.

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) }));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid returning raw exception text in HTTP error responses.

Line 66 exposes String(err) to the client response. Keep details in server logs and return a generic error payload instead.

🔒 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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' }));
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 66-66: Exception text reinterpreted as HTML
Exception text is reinterpreted as HTML without escaping meta-characters.


[warning] 66-66: Information exposure through a stack trace
This information exposed to the user depends on stack trace information.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-contextual-ingest.mjs` at line 66, The HTTP error response at
the 500 status code handler is exposing raw exception details by including
`String(err)` directly in the client response, which is a security
vulnerability. Replace the `error: String(err)` in the JSON response with a
generic error message like `error: "Internal server error"`, and instead log the
actual error details (using `String(err)` or similar) to the server logs for
debugging purposes. This way, sensitive error information stays on the server
while clients receive a safe, generic response.

Source: Linters/SAST tools

Comment thread src/lib/contextual-ingest.ts
Comment thread src/lib/integrations.ts
Comment on lines +1092 to +1096
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-ChittyOS-Caller': 'chittycommand',
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add X-Source-Service: chittycommand to tasksClient requests.

tasksClient currently omits the required source-service header used by other internal integrations.

Suggested patch
         const res = await fetch(`${baseUrl}/api/v1/tasks`, {
           method: 'POST',
           headers: {
             'Content-Type': 'application/json',
             'Authorization': `Bearer ${token}`,
+            'X-Source-Service': 'chittycommand',
             'X-ChittyOS-Caller': 'chittycommand',
           },

As per coding guidelines, all outbound service calls in src/lib/integrations.ts must include X-Source-Service: chittycommand.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-ChittyOS-Caller': 'chittycommand',
},
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
'X-Source-Service': 'chittycommand',
'X-ChittyOS-Caller': 'chittycommand',
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/integrations.ts` around lines 1092 - 1096, The tasksClient requests
in src/lib/integrations.ts are missing the required X-Source-Service header that
other internal integrations include. Add the header `X-Source-Service:
chittycommand` to the headers object alongside the existing Content-Type,
Authorization, and X-ChittyOS-Caller headers to comply with the coding
guidelines for all outbound service calls in this file.

Source: Coding guidelines

Comment thread src/routes/triage.ts
Comment on lines +238 to +242
let limit = 50;
try {
const body = (await c.req.json().catch(() => ({}))) as { limit?: number };
if (typeof body.limit === 'number' && body.limit > 0 && body.limit <= 500) limit = body.limit;
} catch { /* default */ }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether this endpoint uses zValidator/Zod schema and whether a dedicated validator exists.
rg -n -C3 "triageRoutes\\.post\\('/contextual/ingest'|zValidator\\('json'|limit must be" src/routes/triage.ts
fd -i validators.ts
rg -n -C3 "contextual.*ingest|triage.*contextual|limit" src/lib/validators.ts || true

Repository: chittyos/chittycommand

Length of output: 1565


🏁 Script executed:

sed -n '230,250p' src/routes/triage.ts

Repository: chittyos/chittycommand

Length of output: 727


🏁 Script executed:

rg -n "zValidator|ingestContextual" src/routes/triage.ts | head -20

Repository: chittyos/chittycommand

Length of output: 198


🏁 Script executed:

rg -n "contextual.*Schema|ingest.*Schema" src/lib/validators.ts

Repository: chittyos/chittycommand

Length of output: 48


🏁 Script executed:

rg -n "export.*ingestContextual|function ingestContextual" src/lib/contextual-ingest.ts

Repository: chittyos/chittycommand

Length of output: 110


🏁 Script executed:

rg -n -A5 "triageRoutes\.post" src/routes/triage.ts | head -40

Repository: chittyos/chittycommand

Length of output: 1302


🏁 Script executed:

rg -n "zValidator" src/routes/

Repository: chittyos/chittycommand

Length of output: 48


🏁 Script executed:

rg -n "import.*zValidator" src/

Repository: chittyos/chittycommand

Length of output: 48


Use Number.isInteger() to validate limit and reject non-integers with a 400 response.

Line 238–242 currently accepts non-integer numbers (e.g., 1.5, 2.7) because the type check typeof body.limit === 'number' does not distinguish integers from floats. Invalid or missing payloads silently fall back to the default 50, weakening API correctness.

🔧 Suggested fix
   let limit = 50;
-  try {
-    const body = (await c.req.json().catch(() => ({}))) as { limit?: number };
-    if (typeof body.limit === 'number' && body.limit > 0 && body.limit <= 500) limit = body.limit;
-  } catch { /* default */ }
+  const body = (await c.req.json().catch(() => ({}))) as { limit?: unknown };
+  if (body.limit !== undefined) {
+    const parsed = Number(body.limit);
+    if (!Number.isInteger(parsed) || parsed < 1 || parsed > 500) {
+      return c.json({ error: 'limit must be an integer between 1 and 500' }, 400);
+    }
+    limit = parsed;
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/triage.ts` around lines 238 - 242, The validation for the limit
parameter in the request body currently accepts non-integer numbers (like 1.5 or
2.7) because the type check only verifies typeof body.limit === 'number'.
Replace the existing condition to use Number.isInteger() to ensure the limit is
an actual integer between 1 and 500. When the limit fails validation (not an
integer or outside the range), return a 400 response with an appropriate error
message instead of silently defaulting to 50.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants