diff --git a/reference-implementations/dacs-directory/README.md b/reference-implementations/dacs-directory/README.md index 146cf99..bcb2a39 100644 --- a/reference-implementations/dacs-directory/README.md +++ b/reference-implementations/dacs-directory/README.md @@ -116,6 +116,8 @@ vendor directory. | `DACS_SCAN_MAX_TXS` | No | Maximum transactions scanned per pass; defaults to `100000` and fails closed if insufficient | | `DACS_SCAN_FINALITY_DEPTH` | No | Newest transaction count held back before indexing; defaults to `2` | | `DACS_SCAN_REPLAY_DEPTH` | No | Finalized transaction overlap replayed on every pass; defaults to `2` | +| `DACS_INDEX_INTERVAL_SECONDS` | No | Seconds between production reindex passes; defaults to `900` | +| `DACS_CURSOR_STALL_SECONDS` | No | Cursor-stall alert threshold; defaults to twice the valid index interval (minimum `300`, default `1800`, maximum `86400`) | | `DACS_RECIPE_POLICIES` | For tier elevation | JSON array of version-pinned DACS-2 recipe policies (`scheme`, `recipeVersion`, `methods`, `defaultMaxAgeSec`, `availability`, `trustedResultSigners`); absent/invalid policy fails closed to `self-declared` | | `DACS_TRUST_PROXY` | No | Set to `1` only behind a trusted proxy that overwrites client-IP headers; otherwise the in-process rate limiter is disabled and the deployment must enforce its edge limit | | `NEXT_PUBLIC_DIRECTORY_URL` | Production | Public origin used by canonical URLs, sitemap, `llms.txt`, and machine-discovery documents; defaults to `http://localhost:3400`, which silently poisons production canonical URLs and the sitemap — the server logs a warning when unset in production | diff --git a/reference-implementations/dacs-directory/src/catalog/statusDiagnostics.ts b/reference-implementations/dacs-directory/src/catalog/statusDiagnostics.ts index ab5dd12..1034bde 100644 --- a/reference-implementations/dacs-directory/src/catalog/statusDiagnostics.ts +++ b/reference-implementations/dacs-directory/src/catalog/statusDiagnostics.ts @@ -1,7 +1,9 @@ export const DEAD_LETTER_DEFAULT_LIMIT = 20; export const DEAD_LETTER_MAX_LIMIT = 100; -export const CURSOR_STALL_DEFAULT_SECONDS = 5 * 60; +export const CURSOR_STALL_MIN_SECONDS = 5 * 60; export const CURSOR_STALL_MAX_SECONDS = 24 * 60 * 60; +export const INDEX_INTERVAL_DEFAULT_SECONDS = 15 * 60; +export const INDEX_INTERVAL_MAX_SECONDS = 12 * 60 * 60; export type StatusDiagnosticsQuery = | { ok: true; deadLetterLimit: number; deadLetterLocator?: string } @@ -26,11 +28,21 @@ export function parseStatusDiagnosticsQuery(params: URLSearchParams): StatusDiag export function cursorStallThresholdSeconds( raw = process.env.DACS_CURSOR_STALL_SECONDS, + rawIndexInterval = process.env.DACS_INDEX_INTERVAL_SECONDS, ): number { - const configured = raw === undefined ? CURSOR_STALL_DEFAULT_SECONDS : Number(raw); + const indexInterval = Number(rawIndexInterval ?? INDEX_INTERVAL_DEFAULT_SECONDS); + const boundedInterval = Number.isSafeInteger(indexInterval) && indexInterval >= 1 && indexInterval <= INDEX_INTERVAL_MAX_SECONDS + ? indexInterval + : INDEX_INTERVAL_DEFAULT_SECONDS; + const scheduleAwareDefault = Math.min( + CURSOR_STALL_MAX_SECONDS, + Math.max(CURSOR_STALL_MIN_SECONDS, boundedInterval * 2), + ); + if (raw === undefined) return scheduleAwareDefault; + const configured = Number(raw); return Number.isSafeInteger(configured) && configured >= 1 && configured <= CURSOR_STALL_MAX_SECONDS ? configured - : CURSOR_STALL_DEFAULT_SECONDS; + : scheduleAwareDefault; } export function cursorProgressDiagnostics( diff --git a/reference-implementations/dacs-directory/test/dead-letter-diagnostics.test.ts b/reference-implementations/dacs-directory/test/dead-letter-diagnostics.test.ts index 214f2e8..dc17869 100644 --- a/reference-implementations/dacs-directory/test/dead-letter-diagnostics.test.ts +++ b/reference-implementations/dacs-directory/test/dead-letter-diagnostics.test.ts @@ -115,7 +115,11 @@ test("listing binding rejections are persistent, public-safe, filterable, and re test("cursor progress diagnostics distinguish caught-up, stalled, and unknown cursors", () => { const now = 1_000_000; assert.equal(cursorStallThresholdSeconds("60"), 60); - assert.equal(cursorStallThresholdSeconds("0"), 300); + assert.equal(cursorStallThresholdSeconds(undefined, "900"), 1_800); + assert.equal(cursorStallThresholdSeconds(undefined, "60"), 300); + assert.equal(cursorStallThresholdSeconds(undefined, "600"), 1_200); + assert.equal(cursorStallThresholdSeconds("0", "600"), 1_200); + assert.equal(cursorStallThresholdSeconds(undefined, "invalid"), 1_800); assert.deepEqual(cursorProgressDiagnostics( { lastSeenTxId: 10, cursorAdvancedAt: now - 61_000 }, 12,