Skip to main content
This is not an official U.S. federal government website. The Federal Modernization Workbook is independent technical analysis. It does not represent the position of, and has not been reviewed or endorsed by, any federal agency.

Federal Modernization Workbook

Chapter 07 — Evidence Records for Book 13 and the ISSO

The ServiceNow half of Pass 2’s evidence export: read closed and cancelled identity findings and write one record per finding in Book 13’s evidence shape (origin, owner and clock, approval, executing identity, what ran, result, closure), with the Pass 1 gaps written as null and the reason, never invented. Check each record’s evidence against its hash, its closure against the business rule, and its finish against its response window, and package the period with a manifest and a hash chain an ISSO can verify; with labs against fixtures and a ServiceNow developer instance.

The ISSO doesn’t need the tracker. The ISSO needs one record per finding, in one shape, that nobody could have edited afterwards without it showing.

NoteIn this chapter

The loop’s fifth clause, “emits verified, secured evidence automatically,” for identity findings. evidence_export.py reads closed and cancelled findings from ServiceNow, writes each as a record in Book 13’s evidence shape, checks what an assessor would check, and packages the period with a SHA-256 per file and a hash chain across records and periods, with a verifier the ISSO can run. It builds the ServiceNow half of the kit’s planned Pass 2 evidence export; the SailPoint events half needs a tenant and isn’t built yet. Lab 1 runs on a laptop; Lab 2 exports from a developer instance.

TipNarrated tour
Building Ticket-to-Machine, Chapter 07 — Evidence Records for Book 13 and the ISSO: narrated tour · 9 minutes · captions from the player's CC button · Download MP4 · All videos
Jump to a slide (15)

Deck · Narrated deck (about 9 minutes, captions) · Script · Captions. Recorded on November 1, 2026 from the approved chapter.

Goal and audience

Goal. By the end of this chapter you can produce the period’s evidence package for identity findings, say what each record proves and what it doesn’t yet, and verify a package you were handed.

Audience. Developers who build the evidence pipeline, and the ISSO and assessors who read its output. You should read Python and JSON. You don’t need an instance until Lab 2.

The code: examples/servicenow-sailpoint

What exists

Chapter 05’s re-read client writes the evidence onto each record it closes: u_evidence (the query, the check time, the intended state, a SHA-256 of the response, the differences, the checker and its identity, as canonical JSON), u_evidence_hash (the SHA-256 of that JSON), u_verified_at and a close note, plus a JSON attachment. The business rule allows a close only from the verifier with fresh evidence, or a cancellation with a note under the declared-exception lane. The implementation example maps the record’s fields to Book 13’s in its evidence-record table, and lists the evidence export among the items planned for Pass 2. Nothing read the records back out.

What this chapter adds

clients/python/evidence_export.py:

  • One record per finding, schema book13-evidence-record/identity-finding/v1, with Book 13’s seven fields. Approval, executing identity and what ran are written as null with the reason: Pass 1 doesn’t actuate, and the change or catalog record that would hold them is a later pass. The record says so rather than leaving the fields out or filling them.
  • Four checks per record, each reported by name: the evidence still hashes to u_evidence_hash (an edit after closure, or a value cut short by the 4,000-character field, fails); a closed record carries evidence at all (a close that bypassed the business rule fails); the evidence says the intended state matched; and the record finished inside its response window, verified at or before its due date.
  • The package: records.jsonl, each line with the SHA-256 of the line before it, so a removed or reordered record breaks the chain; summary.json, with window adherence, the count of declared exceptions and the records with problems; and manifest.json, with a SHA-256 per file and the chain head. --previous-manifest continues last period’s chain. --generated-at fixes the timestamp, so the same records give the same bytes.
  • A verifier. --verify-dir recomputes every file’s hash and walks the chain. It is the check an ISSO runs on a package they receive.
  • A live read through the Table API (state closed or cancelled, updated since --since), declared in Chapter 02’s call list under its own read-only role, x_example_idkit.evidence_reader.

Five fictional records in tests/fixtures/evidence/closed_records.json cover the cases: one closed in its window, one closed late, one whose evidence was edited after it closed, one cancelled as a declared exception, and one closed with no evidence at all.

Window adherence is measured as Book 13 and Book 12, Chapter 12 define it: closures inside the window over closures judged, not a ratio of open findings that improves as new ones arrive. Book 13 also counts findings still open past their window, and Book 12 counts overdue items; an export of closed findings can’t see those, so it gives the closure half and the overdue count comes from a query of the open findings.

The tests

tests/python/test_evidence_export.py, 8 tests: the seven fields and the null later-pass fields, the late, tampered and unevidenced closures, a truncated u_evidence, the package and its verification, tampering detected, the chain continued across periods, reproducible bytes, and evidence written by the re-read client verifying.

ImportantIllustrative code

The commands below export fictional records. They show the record shape, the checks and the chain, not that a ServiceNow instance returns these fields in this format. The package is tamper-evident, not signed: anyone who can rewrite every file and the manifest can rewrite the chain. Signing, and where the package is stored, are the program’s to decide (see dangers).

Lab 1: fixtures, no tenant

Work in examples/servicenow-sailpoint, with Python 3.10 or later and pytest.

NoteEvery command in Lab 1

Every command in this lab, in order, from examples/servicenow-sailpoint. Each step below says what it does and what to look for.

# Step 1: Run this chapter's tests
python -m pytest tests/python/test_evidence_export.py -q

# Step 2: Export October's closed findings
python clients/python/evidence_export.py --records-json tests/fixtures/evidence/closed_records.json --out-dir out/evidence --period 2026-10 --generated-at 2026-11-01T12:00:00Z

# Step 2: Export October's closed findings
python -c "import json; r=json.loads(open('out/evidence/records.jsonl', encoding='utf-8').readline()); [print(k, json.dumps(r[k])) for k in ('approval', 'executing_identity', 'what_ran')]"

# Step 3: Verify the package as the ISSO would
python clients/python/evidence_export.py --verify-dir out/evidence

# Step 4: Start November's chain from October's head
python clients/python/evidence_export.py --records-json tests/fixtures/evidence/closed_records.json --out-dir out/evidence-2026-11 --period 2026-11 --previous-manifest out/evidence/manifest.json --generated-at 2026-12-01T12:00:00Z

# Step 5: Remove one record from October's package, and verify again
python -c "p='out/evidence/records.jsonl'; l=open(p).readlines(); del l[1]; open(p,'w').writelines(l)"
python clients/python/evidence_export.py --verify-dir out/evidence

# Step 6: Delete the out folder
rm -rf out
  1. Run this chapter’s tests:

    python -m pytest tests/python/test_evidence_export.py -q

    Terminal window showing python -m pytest tests/python/test_evidence_export.py -q and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    8 passed.

  2. Export October’s closed findings:

    python clients/python/evidence_export.py --records-json tests/fixtures/evidence/closed_records.json --out-dir out/evidence --period 2026-10 --generated-at 2026-11-01T12:00:00Z
    5 evidence records: 4 closed, 1 declared exception
    Window adherence: 2 of 3 closed inside the window
      IDF0000902: closed after its response window
      IDF0000903: evidence doesn't hash to u_evidence_hash: edited after closure, or cut short
      IDF0000905: closed without re-read evidence
    Chain head 144f5e8494281f83598d2fae5eacac12b9b271c90372c0e503aafc2bd4cf0623; written to out/evidence

    Terminal window showing python clients/python/evidence_export.py --records-json tests/fixtures/eviden… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    The command exits 1, because three records have problems: the run says so instead of producing a clean-looking package. IDF0000905 was closed with a note that says “done,” the closure by assertion Book 13 calls prose, not evidence. Print three fields of the first line of out/evidence/records.jsonl:

    python -c "import json; r=json.loads(open('out/evidence/records.jsonl', encoding='utf-8').readline()); [print(k, json.dumps(r[k])) for k in ('approval', 'executing_identity', 'what_ran')]"

    Terminal window showing python -c 'import json; r=json.loads(open('out/evidence/records.jsonl', encod… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    approval, executing_identity and what_ran are null, each with its reason.

  3. Verify the package as the ISSO would:

    python clients/python/evidence_export.py --verify-dir out/evidence

    Terminal window showing python clients/python/evidence_export.py --verify-dir out/evidence and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    Package holds: every file matches the manifest and the chain is unbroken.

  4. Start November’s chain from October’s head:

    python clients/python/evidence_export.py --records-json tests/fixtures/evidence/closed_records.json --out-dir out/evidence-2026-11 --period 2026-11 --previous-manifest out/evidence/manifest.json --generated-at 2026-12-01T12:00:00Z

    Terminal window showing python clients/python/evidence_export.py --records-json tests/fixtures/eviden… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

    The last line reports chain head afa59e0f3038613ba36eb4a748da38bb3e76e8427d04038162d08fbdcc167bd1, and November’s manifest.json has chain_previous set to October’s head, 144f5e84.... (The lab reuses October’s records; a real November has its own.)

  5. Remove one record from October’s package, and verify again:

    python -c "p='out/evidence/records.jsonl'; l=open(p).readlines(); del l[1]; open(p,'w').writelines(l)"
    python clients/python/evidence_export.py --verify-dir out/evidence
      records.jsonl: SHA-256 doesn't match the manifest
      line 2 (IDF0000903): prev_sha256 doesn't match the line before
    Package does not hold: 2 problem(s).

    Terminal window showing python -c 'p='out/evidence/records.jsonl'; l=open(p).readlines(); del l[1]; o… and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)
  6. Delete the out folder when you’re done:

    rm -rf out

    Terminal window showing rm -rf out and what it printed

    What the terminal shows, run on the kit’s fixtures (the same output as text)

Lab 2: a ServiceNow developer instance

Not run by the authors. Use the developer instance from the earlier chapters, with at least one finding closed by Chapter 05’s Lab 2. Work in examples/servicenow-sailpoint. You’ll replace: <instance> (your developer instance’s name), <reader-client-id> and <reader-client-secret> (the evidence reader’s OAuth integration), and <closure-date> (the date of your test closure, as YYYY-MM-DD).

NoteEvery command in Lab 2

Every command in this lab, in order, from examples/servicenow-sailpoint. Each step below says what it does and what to look for.

# Step 1: Create the evidence reader
python clients/python/kit_calls.py --task evidence --system servicenow --granted "x_example_idkit.evidence_reader"

# Step 2: Export live
export SN_INSTANCE="<instance>"
export SN_ACCESS_TOKEN=$(curl -s -X POST "https://<instance>.service-now.com/oauth_token.do" -d grant_type=client_credentials -d client_id="<reader-client-id>" -d client_secret="<reader-client-secret>" | python -c "import json,sys; print(json.load(sys.stdin)['access_token'])")
python clients/python/evidence_export.py --since "<closure-date>" --out-dir out/evidence-live --period lab

# Step 3: Check the date format
python -c "import json; [print(r['record'], 'due', r['owner_and_clock']['due'], 'finished', r['owner_and_clock']['finished']) for r in map(json.loads, open('out/evidence-live/records.jsonl'))]"

# Step 4: Verify the package
python clients/python/evidence_export.py --verify-dir out/evidence-live

# Step 5: Back it out
rm -rf out/evidence-live
unset SN_INSTANCE SN_ACCESS_TOKEN
  1. Create the evidence reader: the role x_example_idkit.evidence_reader with read only on u_identity_finding, an integration user and an OAuth client for it, as in Chapter 02. Check it:

    python clients/python/kit_calls.py --task evidence --system servicenow --granted "x_example_idkit.evidence_reader"
  2. Export live, with the reader’s token in SN_ACCESS_TOKEN and the instance in SN_INSTANCE:1

    export SN_INSTANCE="<instance>"
    export SN_ACCESS_TOKEN=$(curl -s -X POST "https://<instance>.service-now.com/oauth_token.do" -d grant_type=client_credentials -d client_id="<reader-client-id>" -d client_secret="<reader-client-secret>" | python -c "import json,sys; print(json.load(sys.stdin)['access_token'])")
    python clients/python/evidence_export.py --since "<closure-date>" --out-dir out/evidence-live --period lab

    Expect each closed test finding with evidence_intact true and no problems.

  3. Check the date format. The Table API returns date and time fields in the instance’s internal format; confirm due and finished in the records are the times you expect, in UTC (research gaps, row 61). This prints them for each record:

    python -c "import json; [print(r['record'], 'due', r['owner_and_clock']['due'], 'finished', r['owner_and_clock']['finished']) for r in map(json.loads, open('out/evidence-live/records.jsonl'))]"
  4. Verify the package:

    python clients/python/evidence_export.py --verify-dir out/evidence-live
  5. Back it out. Delete out/evidence-live, and the evidence reader’s OAuth client, user and role:

    rm -rf out/evidence-live
    unset SN_INSTANCE SN_ACCESS_TOKEN

The verified facts

Item Value Source
Table API read GET /api/now/table/{tableName} with sysparm_query (encoded, ^OR for alternatives, ORDERBY), sysparm_fields, sysparm_limit, sysparm_offset; the caller needs roles that allow access to the table 2
ServiceNow Government Community Cloud FedRAMP Certified, High, JAB 3
SailPoint Search events index Exists in the v3 index list, for the SailPoint half of the export when it is built 4

The record shape, the window and the lanes are Book 13’s (program design); the hash chain and the package are design guidance, the same pattern the other developer workbooks use for their evidence runs.

What couldn’t be verified

  • The format of date and time values the Table API returns for due_date, u_verified_at and closed_at on a given instance, and their time zone. The export parses both YYYY-MM-DD HH:MM:SS (as UTC) and ISO 8601 (research gaps, row 61).
  • What SailPoint keeps in the events index, and for how long. The example guide lists event retention as unverified, which is why the SailPoint half of the export waits (research gaps, row 62).

Federal requirements and evidence

This chapter’s output is the evidence. What each part proves, and for whom:

What the output proves Requirement it supports KSI Who keeps it
Each identity finding has one record: its origin, owner, clock, intended state, re-read and closure NIST SP 800-53 CM-3 (retained records of changes) and CA-7 at Moderate;5 Book 13’s evidence record KSI-CMT-LMC; KSI-MLA-EVC The ISSO, as part of the continuous-monitoring evidence for the period
Records weren’t changed after closure, and none was removed from the period AU-9 (protection of audit information) at Moderate;6 the integrity half of Book 13’s “verified, secured evidence” KSI-MLA-OSM The manifest and the chain, verified with --verify-dir
Closures happened inside the program’s windows, and late ones are named Book 13’s window adherence; CA-7’s monitoring results KSI-MLA-EVC summary.json, reviewed by the governance board
Exceptions are declared, with a note Book 13’s declared-exception lane; the POA&M item it needs None The cancelled records, and the exception register
What isn’t evidenced yet is stated Honest scope for the assessor: no approval, executing identity or run log until actuation is built None The null fields and their reason
Lab 1, step 1: the export, its checks and the chain hold, tested before a change merges SA-11 (developer testing) at Moderate7 KSI-SCR-MIT The pytest tests/python/test_evidence_export.py run, in the pull request’s checks
Lab 1, step 2: one record per closed or cancelled finding, each carrying the hash of the one before CM-3; AU-9 KSI-CMT-LMC; KSI-MLA-OSM out/evidence/records.jsonl
Lab 1, step 2: counts, window adherence, and the records with problems, named; the run exits 1 CA-7 KSI-MLA-EVC out/evidence/summary.json and the run’s output
Lab 1, step 2: a SHA-256 per file and the chain head AU-9 KSI-MLA-OSM out/evidence/manifest.json
Lab 1, step 3: the package holds as written AU-9 KSI-MLA-OSM The evidence_export.py --verify-dir output, kept with the package
Lab 1, step 4: November’s chain starts from October’s head, so a gap between periods shows AU-9 KSI-MLA-OSM chain_previous in out/evidence-2026-11/manifest.json
Lab 1, step 5: a removed record breaks the package AU-9 KSI-MLA-OSM The --verify-dir output, Package does not hold
Lab 1, step 6: fixture output isn’t kept None None Nobody: rm -rf out deletes fictional data
Lab 2, step 1: the evidence reader can read findings and nothing else AC-6 (least privilege) and AC-5 at Moderate8 KSI-IAM-ELP; KSI-IAM-JIT The kit_calls.py --task evidence --granted output, kept with the access approval
Lab 2, step 2: a live package for the period, read by the reader’s token CM-3; CA-7; AU-9 KSI-CMT-LMC; KSI-MLA-EVC; KSI-MLA-OSM out/evidence-live/records.jsonl, summary.json and manifest.json, given to the ISSO
Lab 2, step 3: due and finished are the UTC times expected, so window adherence is right CA-7 KSI-MLA-EVC The printed dates, recorded with the first live package
Lab 2, step 4: the live package holds AU-9 KSI-MLA-OSM The --verify-dir output, kept with the package
Lab 2, step 5: no test package, client, user or role is left AC-2 (account management) at Moderate9 KSI-IAM-SNU The instance’s deletion history; rm -rf out/evidence-live

The KSI column names the FedRAMP 20x Key Security Indicators whose related SP 800-53 controls include the row’s control.10 KSIs are indicators for cloud service providers: the agency authorizes its own system and lines its own evidence up with what its providers report against them; it doesn’t report KSIs itself.

AU-10 (non-repudiation) and CM-5(1), which Book 13 maps to the loop, aren’t in the Moderate baseline;11 at Moderate the chain supports AU-9’s integrity, and signing the package is a further step. The control mapping is Book 13’s Appendix A, and the federal sources are in the federal framework crosswalk.

Dangers

Danger What happens The guard
A record edited after closure The evidence says something the re-read didn’t The evidence hash is recomputed; a mismatch is reported
u_evidence cut short by the field length The evidence can’t be parsed, or hashes wrong Reported as invalid JSON; the attachment holds the full copy
A close that bypassed the business rule A record closed with no evidence passes as closed “Closed without re-read evidence”
A record deleted from the period The package looks complete The chain breaks; --verify-dir reports it
A package rewritten end to end The chain is recomputed and holds Keep each period’s manifest where the exporter can’t write, and sign it if the program requires (design guidance)
Fields filled to look complete Approval or execution claimed that never happened Later-pass fields are null with the reason, and tests pin it
The export runs with the verifier’s role It could close findings Its own read-only role; kit_calls.py --granted

Checklist

Back to top

Footnotes

  1. ServiceNow, “Table API” (Zurich API reference), https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_TableAPI.html (read November 1, 2026).↩︎

  2. ServiceNow, “Table API” (Zurich API reference), https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_TableAPI.html (read November 1, 2026).↩︎

  3. FedRAMP Marketplace data, https://www.fedramp.gov/marketplace/products.json (last change November 1, 2026): ServiceNow Government Community Cloud, F1305072116 (read November 1, 2026).↩︎

  4. SailPoint, Identity Security Cloud OpenAPI specification, v3, search Index enumeration, https://github.com/sailpoint-oss/api-specs/blob/main/idn/v3/schemas/search/Index.yaml (read November 1, 2026).↩︎

  5. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎

  6. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎

  7. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎

  8. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎

  9. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎

  10. FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator titles and “Related SP 800-53 Controls,” https://www.fedramp.gov/2026/reference/key-security-indicators/ (read November 2, 2026).↩︎

  11. National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-5, AC-6, AU-9, CA-7, CM-3 and SA-11; doesn’t include AU-10 or CM-5(1)), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 1, 2026).↩︎