Skip to content

feat(cron): Phase 2.5 — Roux intent decay cron - #115

Merged
chitcommit merged 1 commit into
mainfrom
feat/roux-intent-decay-cron
Jun 10, 2026
Merged

chitcommit merged 1 commit into
mainfrom
feat/roux-intent-decay-cron

Conversation

@chitcommit

@chitcommit chitcommit commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 2.5 of ChittyRoux × Workspace Studio. Adds a daily decay sweep that expires stale pending roux_ingest intents (30-day TTL), preventing unbounded queue growth from Gmail ingest sources the user never triages.

  • Migration 0018cc_intents.expires_at + decayed_at columns, partial scan index, refreshed message_id uniqueness guard (excludes expired so future re-ingest is allowed if the source comes back online).
  • src/lib/intent-decay.tsdecayStaleRouxIntents(sql, {ttlDays, batchLimit}) and computeRouxExpiresAt(createdAt, ttlDays). Atomic CTE-bounded UPDATE; concurrent-safe across cron leaders. Defaults: 30 days, 500 row batch.
  • meta/intent.tsIntentStatus union gains 'expired'.
  • src/lib/cron.ts — Phase 12 wired into the daily_api cron source.
  • tests/meta/intent-decay.spec.ts — Real-Neon integration: empty case, stale vs. fresh, non-roux_ingest skip, dispatched-skip, batchLimit cap, plus unit tests for computeRouxExpiresAt.

Validation

Validated against Neon branch test-intent-decay-0018 (project cool-bar-13270800):

  • Columns added: expires_at TIMESTAMPTZ, decayed_at TIMESTAMPTZ.
  • Indexes created: idx_cc_intents_decay_scan (partial), cc_intents_roux_ingest_message_id_uidx (partial unique, excludes expired).
  • Direct SQL trial: 35-day-old roux_ingest expired with decayed_at stamped; 1-day-old roux_ingest and 60-day-old noop both untouched.
  • npx vitest run tests/meta/intent-decay.spec.ts — 8/8 pass.
  • npx tsc --noEmit — clean.
  • Full suite: 51 pass / 5 fail; the 5 failures are pre-existing on main (workspace-studio-ingest + leader specs) and unrelated to this change.

Note on executeIntent terminal-check

The blueprint asked to extend executeIntent's terminal-state guard with || intent.status === 'expired'. executeIntent does not exist on main yet (it lives on the unmerged fix/real-health-probe-and-tail-consumer family). Adding the 'expired' value to the IntentStatus union is the load-bearing part — any future executeIntent introduced on top of this will naturally compare against the full union. Deviation flagged.

Test plan

  • Migration applies cleanly on a Neon branch.
  • Decay UPDATE only touches stale pending roux_ingest rows with no dispatched_task_id.
  • Fresh / non-roux / dispatched rows untouched.
  • batchLimit caps the result; second pass picks up the remainder.
  • computeRouxExpiresAt math correct for default + custom ttlDays.
  • tsc --noEmit clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added "expired" as a new intent status.
    • Pending intents now automatically expire after 30 days of inactivity, transitioning to "expired" status to indicate lifecycle completion.

…pired)

Adds a daily decay sweep for stale 'pending' roux_ingest intents that have
outlived their 30-day TTL. Caps unbounded queue growth from Gmail ingest
sources the user never triages.

- migration 0018: cc_intents.expires_at + decayed_at columns, partial scan
  index, refreshed message_id uniqueness guard that frees the slot when an
  intent decays to 'expired' (allowing future re-ingest if the source comes
  back online).
- src/lib/intent-decay.ts: decayStaleRouxIntents(sql, {ttlDays, batchLimit})
  + computeRouxExpiresAt helper. Atomic CTE-bounded UPDATE; concurrent-safe.
- meta/intent.ts: IntentStatus union gains 'expired'.
- src/lib/cron.ts: Phase 12 in the daily_api cron source.
- tests/meta/intent-decay.spec.ts: real-Neon integration covering empty case,
  stale vs. fresh, non-roux_ingest skip, dispatched skip, batchLimit cap, plus
  unit tests for computeRouxExpiresAt.

Validated against Neon branch test-intent-decay-0018 on project cool-bar-13270800:
all 8 tests pass; typecheck clean.
@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.

@chitcommit
chitcommit merged commit 300dbb9 into main Jun 10, 2026
11 of 12 checks passed
@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.

@chitcommit
chitcommit deleted the feat/roux-intent-decay-cron branch June 10, 2026 01:09
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7c8f30a-b018-420a-adad-e7ce56dcf5ce

📥 Commits

Reviewing files that changed from the base of the PR and between 85314e3 and db37a4e.

📒 Files selected for processing (5)
  • meta/intent.ts
  • migrations/0018_intent_decay.sql
  • src/lib/cron.ts
  • src/lib/intent-decay.ts
  • tests/meta/intent-decay.spec.ts

📝 Walkthrough

Walkthrough

This PR implements a complete intent decay system for managing stale Roux intents. It introduces an 'expired' terminal status to the intent lifecycle, adds database schema for expiration tracking, implements atomic decay logic that marks eligible intents as expired on a configurable TTL, integrates decay into the cron sync pipeline, and provides comprehensive integration and unit test coverage.

Changes

Roux Intent Decay Pipeline

Layer / File(s) Summary
Intent status type and database schema
meta/intent.ts, migrations/0018_intent_decay.sql
IntentStatus extends to include 'expired' as a terminal status. Migration adds expires_at and decayed_at columns for lifecycle tracking, creates a partial index to efficiently scan pending roux_ingest intents eligible for decay, and updates the unique message-id index to exclude expired rows.
Decay function and expiration calculation
src/lib/intent-decay.ts
decayStaleRouxIntents() atomically expires eligible cc_intents rows using a bounded CTE-driven SQL update, respecting configurable TTL and batch limits. computeRouxExpiresAt() computes canonical expiration dates with a configurable TTL (default 30 days) from creation time.
Cron Phase 12 orchestration
src/lib/cron.ts
Phase 12 executes decayStaleRouxIntents() with isolated error handling, logs scanned/expired counts, and increments recordsSynced based on expired row count.
Integration and unit tests
tests/meta/intent-decay.spec.ts
Integration tests verify decay eligibility filtering (stale vs. fresh, roux_ingest type, no dispatched_task_id), batch limiting, and expiration calculation. Unit tests cover TTL computation with default and custom values, and default "now" behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • chittyos/chittycommand#103: Both PRs modify meta/intent.ts and intent status transitions—this PR adds the terminal 'expired' status and decay updates, while that PR guards state transitions in completeIntent, failIntent, and reclaimStuckIntents based on current status and token lifecycle.

Poem

🐰 Intents that linger long shall fade,
A gentle reap when time has weighed,
Expires marked, decayed with grace,
Stale Roux finds its resting place.

✨ 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/roux-intent-decay-cron

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.

chitcommit added a commit that referenced this pull request Jun 10, 2026
…LICT (#117)

PR #115 / migration 0018 added `AND status <> 'expired'` to the partial
unique index `cc_intents_roux_ingest_message_id_uidx` but did not update
the upsert's ON CONFLICT WHERE clause in createRouxIngestIntentIdempotent.

PostgreSQL requires predicate equality to match an index to ON CONFLICT —
the mismatch caused the upsert to fail with "no unique or exclusion
constraint matching the ON CONFLICT specification", breaking the Roux
ingest path. Verified against a fresh Neon branch:
  - pre-fix: SQLSTATE 42P10 (no matching constraint)
  - post-fix: ON CONFLICT resolves cleanly to the index

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chitcommit added a commit that referenced this pull request Jun 10, 2026
…LICT (#118)

PR #115 / migration 0018 added `AND status <> 'expired'` to the partial
unique index `cc_intents_roux_ingest_message_id_uidx` but did not update
the upsert's ON CONFLICT WHERE clause in createRouxIngestIntentIdempotent.

PostgreSQL requires predicate equality to match an index to ON CONFLICT —
the mismatch caused the upsert to fail with "no unique or exclusion
constraint matching the ON CONFLICT specification", breaking the Roux
ingest path. Verified against a fresh Neon branch:
  - pre-fix: SQLSTATE 42P10 (no matching constraint)
  - post-fix: ON CONFLICT resolves cleanly to the index

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant