feat: ChittyTriage + ChittyRoux carry-through - #104
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
📝 WalkthroughWalkthroughThe PR implements the governance-ratified "Roux/Triage Carry-Through" feature, adding ChangesRoux/Triage Carry-Through Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
chittycommand | 9da3df6 | Jun 04 2026, 11:52 AM |
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cbae5da0c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1258ac1 to
5ce549f
Compare
cbae5da to
f3e9ec6
Compare
|
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3e9ec6621
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
|
To use Codex here, create a Codex account and connect to github. |
Migration 0004 adds privilege ∈ {privileged,pii,hoa_evidentiary,public} and
space ∈ {business,legalink} as first-class text columns on cc_intents and
cc_disputes, with composite indexes on (axis, status). CHECK constraints are
deferred — the Roux spec URI is STATUS:PENDING and enum is enforced in the
app layer.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Validated on Neon branch br-broad-mud-ak4k790p of project cool-bar-13270800:
ADD COLUMN + CREATE INDEX succeed on top of the post-0003 schema; defaults
applied to existing rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + claimNextIntent - Intent interface gains privilege/space (typed unions, sourced from canon). - CreateIntentInput accepts optional privilege/space; INSERT defaults to public/business when caller omits. - claimNextIntent grows null-passthrough filters for privilege, space, and priority_lte so a single prepared statement covers the autonomous-bucket and human-channel paths. - rowToIntent maps the new columns. - sovereignty.ts: add optional privilege field to IntentForSovereignty for audit-trail persistence. decide() is intentionally NOT modified — privilege is orthogonal to the trust-tier sensitivity axis the gate consumes. @canon: chittycanon://gov/governance#classification-axes STATUS:PENDING Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/triage — list pending intents, filter by privilege/space
POST /api/triage/:id/claim — atomic claim-by-ID, 409 if not pending
POST /api/triage/claim-next — bucket-ordered claim for autonomous agents
POST /api/triage/:id/complete — terminal transition (done|failed) with the
same status guards used in meta/intent.ts
Mounted after /api/tasks; covered by the existing global /api/* authMiddleware.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- triage_list_intents({privilege?, space?, limit?})
- triage_claim_intent({id})
- triage_claim_next({privilege?, space?, priority_lte?})
- triage_complete_intent({id, outcome, error?})
Each tool wraps the matching /api/triage route's semantics so Claude Code
sessions can drive the queue without HTTP plumbing. Validation reuses the
ratified privilege/space enums from meta/intent.ts.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…warn
- deriveRouxFromType(disputeType) maps dispute_type → ratified Roux defaults:
'legal' ⇒ {privilege:'privileged', space:'legalink'}
'insurance' ⇒ {privilege:'pii', space:'business'}
others ⇒ {privilege:'public', space:'business'}
- linkDisputeToNotion now gates: if effective privilege ∈ {privileged, pii}
OR space = 'legalink', the function logs the suppression and returns false
before touching Notion. Effective values resolve as explicit > derived.
- pushUnlinkedDisputesToNotion now SELECTs privilege/space and emits a
per-row warn when the row sits on the migration defaults — surfaces
legacy untagged disputes for explicit triage (Q2=(a) pass-through-warn).
- linkDisputeToNotion is now exported for direct integration testing.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the four ratified decisions from chittycanon-code-cardinal:
Q1 — privilege+space land as first-class columns on cc_intents AND
cc_disputes; CHECK deferred until Roux spec is CERTIFIED.
Q2 — pass-through-with-warn on legacy rows (column defaults).
Q3 — both specific-by-ID and bucket-ordered claim modes.
Q4 — privilege is orthogonal to sensitivity; decide() is untouched.
Also documents the Notion mirror gate and explicit > derived resolution.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tests/routes/triage-roux.spec.ts
- createIntent privilege+space round-trip
- claimNextIntent honors bucket filter (won't grab a higher-priority intent
in the wrong bucket)
- second atomic claim of the same intent returns 0 rows (the 409 source)
- bucket-scoped claim still respects priority within the bucket
tests/lib/dispute-sync-roux.spec.ts
- deriveRouxFromType: legal/insurance/property/vendor + unknown safe default
- linkDisputeToNotion suppresses on explicit privilege=privileged
- linkDisputeToNotion suppresses on explicit space=legalink
- linkDisputeToNotion suppresses 'legal' via derived defaults
- explicit (public, business) override beats derived (privileged, legalink)
for a 'legal' dispute_type — gate passes (Notion call then no-ops in test)
Skip pattern mirrors tests/meta/intent-lifecycle.spec.ts — disabled without
DATABASE_URL or with SKIP_INTEGRATION=1.
@canon: chittycanon://gov/governance#classification-axes STATUS:PENDING
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…init Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…egister the Roux columns Hand-written 0004_roux_privilege_space.sql was on disk but never registered in migrations/meta/_journal.json, so drizzle-kit migrate would silently skip it. Regenerated via `drizzle-kit generate` after rebasing onto the merged PR #103 baseline; auto-named 0004_premium_toad_men.sql now carries the same 4 ALTER COLUMN + 4 CREATE INDEX statements (cc_intents/cc_disputes privilege+space + composite indexes) and is properly journaled with a matching meta/0004_snapshot.json. Validated on disposable Neon branch br-green-mode-akxyxiz5 of project cool-bar-13270800: 0002 + 0003 + 0004 sequence applied cleanly; all 4 columns and 4 indexes verified via information_schema and pg_indexes. Rebase also dropped 5 commits (f4cb131, 7027daa, 117d77d, 001e157, 5ce549f) that were the pre-squash form of merged PR #103. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e0bd689 to
395af7a
Compare
|
|
To use Codex here, create a Codex account and connect to github. |
…fill Finding 6 (P1): deriveRouxFromType only matched exact strings, so free-text dispute_type values like "Legal" or "legal dispute" fell through to the default public/business mapping and leaked into the Notion business bucket. Lowercase + trim, then route any string containing "legal" to privileged/legalink and any containing "insurance" to pii/business. This is intentionally over-broad on the privileged side as a fail-safe. Finding 1 (P1): pushUnlinkedDisputesToNotion treated rows that landed on the column defaults (public/business) as explicit values, so a row with dispute_type='legal' but no explicit privilege/space was passed through the gate. Now, when both axes sit on defaults, re-derive from dispute_type for the gate decision (DB row is untouched — retag is a separate concern). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finding 2 (P2): claim-next called parsePrivilege/parseSpace and treated null as "no filter", so an unrecognized value (e.g. "pi" instead of "pii") would silently fall through and claim the highest-priority pending intent from any bucket — including privileged/legalink. Mirror the GET /api/triage pattern: when a filter is provided but doesn't parse, return 400 with the valid enum. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finding 5 (P2): list + claim-next correctly exclude future-scheduled intents, but the direct claim path (POST /api/triage/:id/claim and the triage_claim_intent MCP tool) only gated on status='pending'. A client with the ID could short-circuit the schedule and claim tomorrow's intent today. Add scheduled_for guard to both paths and distinguish 409 with the scheduled_for value in the response for client UX. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finding 4 (P2): the triage routes expose claim (pending→claimed) but no
explicit claimed→running transition, so an autonomous agent that did its
work and called complete always got 409. Extend completeIntent's WHERE to
accept status IN ('claimed', 'running'), matching failIntent's existing
guard. The P1-B execution-token gate still rejects stale completions when a
token is supplied — so concurrent leaders are still protected.
Update error messages in the HTTP and MCP wrappers to reflect the relaxed
guard. Add three lifecycle tests: claimed→done, claimed→failed, and the
token-gate rejection on a claimed (not running) intent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3438e6932
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Mounting /api/triage under the generic authMiddleware let any valid ChittyAuth user token list, claim, and complete privileged/legalink intents. Add requireTriageScope middleware that runs after authMiddleware and rejects callers without chittytriage:write, chittytriage:admin, admin, or wildcard scope. Also exports hasTriageScope() for reuse by the MCP dispatcher (next commit) since that path runs inside JSON-RPC, not Hono middleware. fixes codex-p2 PR#104 P1 — elevated scope on triage routes
The 4 triage_* MCP tools were reachable by any caller accepted by mcpAuthMiddleware with the broad 'mcp' scope or the legacy KV shared service token. That lets generic MCP integrations enumerate and mutate privileged/legalink orchestration intents. Two layers: - tools/list now filters triage_* tools out of the catalog unless the caller has chittytriage:write (don't advertise what they can't call). - tools/call enforces the same scope as a defense-in-depth gate so a caller that knows the tool name can't bypass the list filter. Uses hasTriageScope() helper exported from src/middleware/auth.ts. fixes codex-p2 PR#104 P1 — elevated scope on triage MCP tools
Codex flagged a possible race where a stale client A could complete a
re-claimed intent owned by client B with token A. After audit, the
existing token gate from PR#103 P1-B already prevents this:
meta/intent.ts:332-337,367-373 — completeIntent/failIntent UPDATE
predicate is `status IN ('claimed','running') AND dispatched_task_id
= $expectedDispatchedTaskId`, covering BOTH claimed and running.
meta/intent.ts:402 — reclaimStuckIntents sets dispatched_task_id =
NULL when resetting to pending, so any subsequent re-claim writes a
fresh token and the stale A token can never match.
Add a regression test that drives the exact race the reviewer
described (claim with tokenA → updated_at past reclaim window →
reclaimStuckIntents → re-claim with tokenB → tokenA tries complete +
fail) and asserts the row stays B's. No production-code change.
fixes codex-p2 PR#104 P2 — pin reclaim claim-token race
|
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9da3df616a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function hasTriageScope(scopes: string[] | undefined | null): boolean { | ||
| if (!scopes) return false; | ||
| return scopes.some( | ||
| (s) => s === 'chittytriage:write' || s === 'chittytriage:admin' || s === 'admin' || s === '*', |
There was a problem hiding this comment.
Grant triage scope during dev MCP bypass
When ENVIRONMENT !== 'production', mcpAuthMiddleware bypasses auth by setting scopes to ['mcp'], but this new allowlist does not treat that dev scope as elevated. As a result, local/test MCP clients cannot see or call any of the four triage tools even though dev auth is documented as bypassed; I verified npm test -- tests/mcp.test.ts now returns 50 tools instead of the updated expectation of 54. Either assign a triage scope in the dev bypass or make this helper aware of the non-production MCP scope.
Useful? React with 👍 / 👎.
…113) PR #104 round-3 (commit aa2f2d0) added scope-based filtering of triage_* MCP tools from tools/list when the caller lacks chittytriage:write. The test environment uses the mcpAuthMiddleware dev bypass which grants only scope ['mcp'], so the 4 triage tools are filtered out and tools.length is 50, not 54. The unconditional `expect(tools.length).toBe(54)` was failing on main and blocking PR #112's CI build. This commit: - Renames the existing assertion to "exposes 50 tools to unscoped callers (triage tools hidden)" and adds explicit assertions that the 4 triage_* tools are absent from the catalog. - Adds a second test "exposes all 54 tools to callers with triage scope" that injects scopes=['chittytriage:write'] directly via a custom middleware (the dev bypass cannot grant triage scope, and the production code path would require mocking ChittyAuth fetch — direct scope injection is the deterministic way to exercise the scoped branch of tools/list). Covers both code paths: hidden when unscoped, visible when scoped. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Carries through ChittyTriage queue + ChittyRoux classification axes
(
privilege,space) oncc_intentsandcc_disputes.Stacked PR. Base =
fix/codex-p2-comments(PR #103), which depends onPR #101. Merge order: #101 → #103 → this PR.
Ratified decisions (chittycanon-code-cardinal)
cc_intentsANDcc_disputes, with composite indexes on(axis, status). CHECK deferred (Roux spec isSTATUS:PENDING).public/business;pushUnlinkedDisputesToNotionemits a one-time per-row log when it sees those defaults.POST /api/triage/:id/claim, 409 if not pending) for humans, bucket-ordered (POST /api/triage/claim-next) for autonomous agents.privilege ∈ {privileged, pii, hoa_evidentiary, public}is the Roux class. Pre-existingsensitivity ∈ {low, normal, sensitive, critical}is the trust-tier axis the sovereignty gate consumes.decide()is NOT modified.Files changed (7 commits)
migrations/0004_roux_privilege_space.sql+src/db/schema.ts— columns + indexesmeta/intent.ts+meta/sovereignty.ts— types,createIntent,claimNextIntentfilters,rowToIntentsrc/routes/triage.ts(new) +src/index.ts— mount/api/triage/*src/routes/mcp.ts— 4 new tools (triage_list_intents,triage_claim_intent,triage_claim_next,triage_complete_intent)src/lib/dispute-sync.ts—deriveRouxFromType, Notion suppression gate, backfill warndocs/architecture/ADR-001-meta-orchestrator-extension.md— Delta sectiontests/routes/triage-roux.spec.ts+tests/lib/dispute-sync-roux.spec.ts— real-Neon integration testsNotion mirror gate
linkDisputeToNotionreturnsfalse(suppression) when effectiveprivilege ∈ {privileged, pii}ORspace === 'legalink'. Effective valuesresolve as explicit >
deriveRouxFromType(dispute_type). Mapping:legal→{privileged, legalink}insurance→{pii, business}property | vendor | tenant | financial | unknown→{public, business}Neon validation
DDL validated on disposable branch
br-broad-mud-ak4k790pof projectcool-bar-13270800(parentbr-weathered-hall-akoq1ily), against apost-0003-codex-p2-fixes schema baseline:
Post-DDL verification:
Null-passthrough cast pattern verified:
SELECT (NULL::text IS NULL OR 'x' = NULL::text) → true.Test plan
npm run typecheck— cleanDATABASE_URLset against a Neon branchcurlofGET /api/triage,POST /api/triage/:id/claim,POST /api/triage/claim-next,POST /api/triage/:id/completepost-deploytools/listshows the four newtriage_*entrieslegaldispute does NOT mirror to Notionpropertydispute DOES reach the Notion code path🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests