11 added, 19 removed. Audit A to A.
---
name: audit-logging-for-configuration-changes
description: >-
- Tamper-evident, hash-chained audit trail for changes to trading-system
- configuration and risk-control parameters — recording who changed what, from
- which value to which, why, and in which environment; retaining rejected
- attempts as supervisory evidence; and verifying an archived chain for edits,
- middle deletions, reordering and gaps.
- domain: deployment-ops
- subdomain: regulatory
- tags:
- - compliance
- - audit-logging
- - change-management
- - tamper-evident
- - finra-3110
- - risk-controls
- brokers_frameworks:
- - generic
- version: "1.3.0"
- author: System
- license: MIT
+ Use when a change to a trading parameter or risk-control setting must leave evidence
+ of who changed what, from which value to which, and why. A hash-chained record, not an
+ authorisation gate; manual overrides belong to risk-control-bypass-audit-logging.
+ license: Apache-2.0
+ metadata:
+ domain: algorithmic-trading
+ subdomain: deployment-ops
+ tags: compliance, audit-logging, change-management, tamper-evident, finra-3110, risk-controls
+ brokers_frameworks: generic
+ version: "1.3.0"
+ author: algo-trading-skills-contributors
---
## When to Use
Use this skill when changes to live trading algorithms, risk-control parameters, or
system configuration need to leave an evidence trail rather than a shrug. The engine
intercepts a change request, checks that it names a parameter, an authenticated
principal and a reason, and emits a canonical JSON record chained by SHA-256 to the
record before it.
It is worth being precise about *whose* obligation this serves, because "the
regulators require an audit log" is the kind of claim that is usually stated too
broadly:
- **FINRA member broker-dealers** are subject to Rule 3110 (Supervision): written
supervisory procedures under 3110(b)(1), a record of supervisory designations kept
at least three years under 3110(b)(6)(B), and internal inspection reports kept at
least three years under 3110(c)(2).
- **FINRA Regulatory Notice 15-09** (March 2015) is *guidance*, not a rule. It offers
"suggested effective practices" that "complement, rather than supplant, obligations
firms have under existing or future rules", among them "a development and change
management process that tracks the development of new trading code or material
changes to existing code", including "a review of test results and a set of approval
protocols". It does not require that the *reason* for a change be documented.
- **US broker-dealers with market access** are subject to SEA Rule 15c3-5, and this is
the actual authority behind the `justification` field. SEC Division of Trading and
Markets FAQ No. 18 says that where a threshold is raised in accordance with
supervisory procedures, "the reasons for any such modification should be
appropriately documented and retained as part of the broker-dealer's books and
records."
- **SEC Rule 17a-4** governs preservation of the resulting records. Since the 2022
amendments (Release 34-96034; effective 3 January 2023, compliance date 3 May 2023
for broker-dealers) a system may satisfy 17a-4(f) by **either** WORM **or** an
audit-trail arrangement that permits recreation of an original record if it is
modified or deleted, capturing the date and time of each creation, modification or
deletion and the identity of the person responsible. WORM is no longer the only
permitted route.
- **SEC Regulation SCI** applies only to "SCI entities" — SROs, certain ATSs, plan
processors and certain exempt clearing agencies. The March 2023 proposal to extend it
to certain large broker-dealers and SBSDRs (88 FR 23146) was **formally withdrawn**
on 12 June 2025. Do not describe SCI-style controls as SCI compliance if the firm is
outside the perimeter.
Everyone else — a proprietary trader, a fund's internal system, an individual running
an algorithm — has no configuration-audit rule pointed at them and should use this as
operational hygiene.
## When NOT to Use
- **As an authorisation gate.** This engine records that a change was requested; it
does not decide who may make it. Maker-checker approval and RBAC belong upstream —
see `risk-control-configuration-change-approval-workflow`.
- **As the system of record.** The chain lives in process memory and is tamper-*evident*,
not immutable. Persist the emitted lines and publish the chain head to storage the
trading host cannot rewrite.
- For routine application logs that are not configuration changes — use
`structured-logging-for-post-incident-forensics`.
- For order/fill audit trails — use `data-lineage-tracking-for-audit-and-debugging` or
venue CAT reporting tooling.
- For manual overrides of a risk control that is actively rejecting orders — that is an
emergency bypass with its own evidence requirements, see
`risk-control-bypass-audit-logging`.
## Prerequisites
- Python 3.9+ (standard library only).
- A single `ConfigurationAuditLogger` instance per audited configuration domain. The
sequence number and chain are per-instance; two instances produce two independent
chains that cannot be interleaved into one.
- An authenticated caller context so `user_id` is a verified principal. Never accept
`user_id` from an unverified request body — a forged principal is worse than a
missing one, because it looks like evidence.
- Append-only storage for the emitted lines (S3 Object Lock, Splunk with data-integrity
controls, or a 17a-4(f)-compliant recordkeeping system), plus somewhere to publish the
chain head that the trading host cannot rewrite.
- The firm's own justification policy. `MIN_JUSTIFICATION_LENGTH` defaults to 5 and has
**no regulatory basis**; override it via `min_justification_chars`.
## Workflow
1. **Intercept the update.** Every UI, CLI, or API path that mutates an audited
parameter routes through `ConfigurationAuditLogger.process_change_request`. Direct
database writes and flat-file edits are backdoors that leave the firm blind at
examination; close them rather than documenting them.
2. **Validate.** In order, the logger requires a non-blank `parameter_name` (a record
that cannot name what changed proves nothing), a `justification` of at least
`min_justification_chars` after stripping, a non-blank `user_id`, and `old_value !=
new_value`. The first failure becomes the `rejection_reason`; the constants
`REASON_PARAMETER_NAME`, `REASON_JUSTIFICATION`, `REASON_USER_ID` and `REASON_NO_OP`
let callers branch without matching prose.
3. **Record and chain.** Under one lock, the engine assigns a monotonic
`sequence_number`, a high-precision UTC `timestamp_utc`, the originating
`environment`, and a SHA-256 `record_hash` over the canonical JSON of every other
field including `prev_hash`. Order the records by `sequence_number`, never by
`timestamp_utc` — the wall clock can step backwards under an NTP correction.
4. **Emit.** Approved records go out at INFO as `AUDIT_LOG_ENTRY`, rejected attempts at
WARNING as `AUDIT_LOG_REJECTED`, both as sorted-key JSON for deterministic SIEM
ingestion. Emission happens inside the lock so log order matches chain order.
5. **Commit only on approval.** Apply the underlying change when `is_approved` is
`True`. When it is `False`, the change must not be applied — but the record is still
emitted and retained, because a rejected attempt is itself a supervisory event.
6. **Publish the chain head.** Write `chain_head_hash` to storage the trading process
cannot alter. This is the only thing that reveals truncation of the newest records,
and it is what NIST SP 800-92 §3.1 means by protecting the digest.
7. **Verify at examination.** Rebuild records from the archived lines with
`ConfigChangeRecord.from_json`, then call `verify_chain(records)`. It returns
`(True, None)` or `(False, reason)` naming the first record that fails and why —
content edited, link broken, or sequence out of order. Compare the last
`record_hash` against the externally published head. Use
`verify_chain(window, expect_genesis=False)` to check a slice of a longer chain;
that proves internal consistency only and says nothing about earlier records.
## Common Pitfalls
- **Treating the length floor as a rule.** `MIN_JUSTIFICATION_LENGTH = 5` is an
engineering default with no regulatory basis. A character count cannot judge whether
a justification is adequate; it only catches `"ok"`. Adequacy is a human review
question, and citing FINRA for the number is a claim the Notice does not support.
- **Claiming Reg SCI compliance outside the SCI perimeter.** Reg SCI binds SCI entities.
The 2023 proposal to extend it to large broker-dealers was withdrawn in June 2025;
a system description that still says "SCI-mandated" is now wrong.
- **Assuming WORM is the only permitted sink.** Since the 2022 amendments, 17a-4(f)
accepts an audit-trail arrangement as an alternative to WORM. Designing as though
WORM were mandatory can force an unnecessary second recordkeeping system.
- **Assuming JSON or a hash means immutable.** The hash chain provides *detection*.
Anything that can rewrite the whole log can recompute the whole chain; prevention is
the storage layer's job.
- **Believing the chain detects truncation.** Deleting the newest N records leaves a
chain that verifies perfectly. Only an externally held chain head exposes it.
- **Sharing one logger across processes, or building one per request.** The sequence and
chain are per-instance and in-memory. Two instances make two chains; a per-request
instance makes a chain of length one and proves nothing about ordering.
- **Ordering the audit trail by timestamp.** Use `sequence_number`. NTP corrections and
DST-naive local times both reorder a timestamp-sorted trail.
- **Dropping rejected attempts.** Logging only successful changes hides exactly the
events — a missing justification, an unauthenticated principal — that a supervisor
needs to see.
- **Letting an exotic config value drop the record.** Values that are not JSON-native
are coerced to their string form, and a comparison that raises is treated as a real
change rather than propagating. Prefer JSON-native values anyway: the string form of
an unordered container is not stable between processes.
## Verification
Run `python -m unittest discover -s skills/audit-logging-for-configuration-changes/scripts`
to confirm the engine: rejects requests missing a parameter name, justification or
principal, and no-op changes; honours a configured justification floor; emits monotonic
sequence numbers and chains `prev_hash` to the prior `record_hash`; detects an in-place
edit, a middle deletion, reordering, and an edit whose own hash was recomputed;
verifies a chain rebuilt from emitted JSON and rejects an archive line with an extra or
missing field; keeps `chain_head_hash` in step with the last record; assigns
gap-free sequence numbers under eight concurrent writer threads; and records rather
than drops config values that cannot be rendered or compared. Then work through
`assets/checklist.md`.
## Related Skills
- `risk-control-configuration-change-approval-workflow`
- `risk-control-bypass-audit-logging`
- `configuration-drift-detection-across-environments`
- `structured-logging-for-post-incident-forensics`
- `record-retention-periods-by-jurisdiction`