Root Cause Analysis and Complete Solution
Your audit trail logging failures stem from multiple issues across all four focus areas. Here’s the comprehensive fix:
1. Audit Trail Config - Event Timing and Persistence
The core issue is event handler timing. Your script uses an onAfterUpdate trigger, which means it runs AFTER Rally commits the state change. At that point, the story object in memory may not reflect the persisted state, causing custom field updates to target a stale object version.
Switch to onBeforeSave event:
rallyApp.on('beforeSave', async function(story) {
if (story.ScheduleState === 'Accepted' && !story.ComplianceReviewedBy) {
story.ComplianceReviewedBy = rally.getUser().UserName;
story.ComplianceReviewDate = rally.util.DateTime.toIsoString(new Date());
story.ComplianceChecksum = await generateChecksumAsync(story);
}
});
This ensures compliance stamps are written in the SAME transaction as the state change, eliminating the 30% failure rate you’re experiencing.
2. App SDK Scripting - Error Handling and Async Operations
Your generateChecksum function likely performs synchronous operations that block script execution. If it queries related objects or performs calculations exceeding Rally’s 5-second script timeout, the update call never executes.
Refactor to async with proper error handling:
async function generateChecksumAsync(story) {
try {
let relatedData = await rally.query({type: 'Task', query: `WorkProduct = ${story._ref}`});
return calculateHash(story, relatedData); // Keep calculation under 2 seconds
} catch (error) {
story.ComplianceError = `Checksum failed: ${error.message}`;
return 'CHECKSUM_ERROR';
}
}
Add a ComplianceError custom field to capture failures that would otherwise be silent.
3. Custom Fields - Data Type and Format Validation
Your ComplianceReviewDate field failures are caused by format mismatches. Rally’s Date custom fields require timezone-aware ISO8601 strings. JavaScript’s toISOString() works, but Rally’s SDK helper is more reliable:
Use rally.util.DateTime.toIsoString(new Date()) instead of `new Date().toISOString()
For ComplianceChecksum, verify the field type is “String” with sufficient length (minimum 64 characters for hash values). If it’s defined as “Text” with a 32-character limit, longer checksums will be truncated silently.
4. Compliance Stamps - Verification and Backfill Strategy
Implement a two-tier verification system:
Immediate Verification (in script):
After stamping fields, query the story back to confirm persistence:
let updated = await rally.get(story._ref);
if (!updated.ComplianceReviewedBy) {
// Trigger alert - stamp failed
await rally.createDefect({Name: `Compliance stamp failed for ${story.FormattedID}`});
}
Nightly Backfill (scheduled script):
Run a daily job that identifies and repairs gaps:
- Query all stories with ScheduleState = ‘Accepted’ AND ComplianceReviewedBy = null
- For each story, check if it was Accepted > 24 hours ago
- Stamp missing compliance fields with a “BACKFILL” marker in ComplianceChecksum
- Log to audit report for manual review
Testing and Rollout:
- Deploy the refactored script to a test workspace first
- Manually transition 50 stories to Accepted and verify 100% compliance stamp success
- Monitor script execution logs for timeout warnings
- Run the backfill script against your production workspace to repair the existing 30% gap
- Schedule the backfill script as a nightly job for ongoing verification
This multi-layered approach addresses the immediate audit trail failures while establishing preventive controls for future compliance validation. The key is moving from reactive error handling to proactive verification at multiple checkpoints in the workflow lifecycle.