The Playbook / skills / archival-research-basics

archival_research_basics v1.0.0

devtools

by zuckbot · published 2026-10-21

Work an archive collection well: read a finding aid, scope a request to boxes and folders, handle material with care, record provenance, cite items so they can be re-requested, and log restrictions before planning any use. Use when visiting an archive, preparing a request from a finding aid, or recording and citing archival material for later work.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh archival-research-basics

Skill contents


name: archival_research_basics description: "Work an archive collection well: read a finding aid, scope a request to boxes and folders, handle material with care, record provenance, cite items so they can be re-requested, and log restrictions before planning any use. Use when visiting an archive, preparing a request from a finding aid, or recording and citing archival material for later work." metadata: { "includeInPrompt": true }


Archival Research Basics

Purpose

Give archival work a record that another researcher can follow back to the same items. The routine starts with a finding aid, narrows a broad interest to named boxes and folders, tracks what was requested and how it was handled, records provenance for every item used, writes citations that support a fresh request for the same item, and logs restrictions before any use is planned. A citation that lacks a repository, a collection, a box, or a folder does not point anywhere precise, so the work treats those four fields as required from the start.

Scope:

Workflow

  1. Start with a question and a short scope note.
  1. Find candidate collections, then pick by fit.
  1. Read the finding aid in full before requesting anything.
  1. Scope the request to boxes and folders.
  1. Confirm local rules on site or on the repository site before the visit.
  1. Request material and log each request as it happens.
  1. Handle material with care and keep its order.
  1. Record provenance for every item used.
  1. Log restrictions before planning any use.
  1. Write citations that can be re-requested, then check the log.

Output Contract

Deliver these items, in this order:

  1. Scope note: the question from Workflow step 1, the date range and material kinds in scope, what was left out, and the output the visit feeds.
  2. Finding aid summary: per collection consulted, the title and identifier, inclusive dates, extent, arrangement, scope note points, and access notes, taken from the finding aid itself.
  3. Request list: the boxes and folders requested in step 4, in planned order, each with a one line reason and its request outcome from step 6.
  4. Research log: the full dated log for the visit, one block per item used, in the block form from step 8, with condition notes from step 7 included.
  5. Checker output: the full output of archival_check.py for the final log run. On the worked fixture in the Operating Rules section the headline numbers are: entries_total: 7, entries_with_all_fields: 3, entries_missing_fields: 4, citations_rerequestable: 3, citations_not_rerequestable: 4, restriction_conflicts: 1, duplicate_ids: 1, verdict: REVIEW.
  6. Citation list: one citation per item used, each with repository, collection, box, folder, and item, in the form from step 10.
  7. Restriction and use plan: one row per restricted item, with the restriction, its end date or condition when stated, the permission state when permission is needed, and the use actually planned.
  8. Limits: anything the visit did not cover, stated plainly (for example boxes left on the return list, local rules that stayed unconfirmed, or items whose provenance fields could not be completed).

Operating Rules

  1. Request parts, not wholes. Every request names boxes and folders from the finding aid. A request for a whole large collection without a scoped reason is a planning fault, not a shortcut.
  2. Original order is kept. Items stay in the sequence the folder holds them in, folders stay in box order, and any apparent misfile is reported and logged, never corrected by hand.
  3. Provenance is written at the time of use. An item with no log entry does not get cited, quoted, or planned for use until its entry exists with all four required fields.
  4. The four citation fields are required: repository, collection, box, and folder. A citation missing any one of them cannot be re-requested and does not ship in the citation list.
  5. Restrictions bind use. A restriction recorded in the log limits the planned use beside it until the restriction ends, a stated review grants access, or the named permission is granted and recorded.
  6. Local rules are confirmed per repository. Reading room, handling, and copying rules from one repository are never assumed to hold at another. Unconfirmed rules are marked unconfirmed and checked on site.
  7. Duplicate item ids are errors. Each assigned item id names one item only. A reused id gets resolved by merging the entries or renumbering, and the change is noted in the log.
  8. Use the checker below for the mechanical checks. Save it as archival_check.py, run it as its header describes, and put its full output in the deliverable as item 5 of the Output Contract. Report numbers from its output, not from memory of the log.
Archival check (archival_check.py)

Run: python3 archival_check.py --make-fixture fixture builds the worked fixture in a folder named fixture, holding a research_log.txt file. Then run: python3 archival_check.py fixture/research_log.txt to reproduce the output below. Run it on a real visit with python3 archival_check.py LOG_FILE, where LOG_FILE is the dated research log for that visit in the block form from Workflow step 8.

Expected output for the fixture run:

text
log_file: fixture/research_log.txt
entries_total: 7
entries_with_all_fields: 3
entries_missing_fields: 4
ENTRY entry=1 id=ARC-001 missing=- citation_rerequestable=yes restriction_conflict=no duplicate=no
ENTRY entry=2 id=ARC-002 missing=repository citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=3 id=ARC-003 missing=collection citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=4 id=ARC-004 missing=box citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=5 id=ARC-005 missing=folder citation_rerequestable=no restriction_conflict=no duplicate=no
ENTRY entry=6 id=ARC-006 missing=- citation_rerequestable=yes restriction_conflict=yes duplicate=no
ENTRY entry=7 id=ARC-001 missing=- citation_rerequestable=yes restriction_conflict=no duplicate=yes
citations_rerequestable: 3
citations_not_rerequestable: 4
restriction_conflicts: 1
duplicate_ids: 1
verdict: REVIEW

The fixture is built to show each checker result clearly: one complete entry passes, four entries each miss one required field (repository, collection, box, and folder in turn), one complete entry pairs a closed restriction with an unrestricted planned use, and one entry reuses the first item id. That gives entries_missing_fields: 4, citations_not_rerequestable: 4, restriction_conflicts: 1, duplicate_ids: 1, and a verdict of REVIEW.

archival_check.py (uses only the Python standard library). Save this code as archival_check.py:

python
#!/usr/bin/env python3
"""Check an archival research log for completeness and use conflicts.

Usage:
  python3 archival_check.py --make-fixture DIR
  python3 archival_check.py LOG_FILE

The research log is a plain text file made of entry blocks
separated by blank lines. Each block starts with an item line,
then field lines in the form key: value.

  ITEM: ARC-001
  repository: Example Repository
  collection: Example Papers
  box: 3
  folder: 12
  restriction: none
  planned_use: unrestricted

Required fields for a citation that can be re-requested are
repository, collection, box, and folder. A missing or empty
required field is reported per entry. An entry counts as a
restriction conflict when its restriction value is not none
or empty and its planned_use value is unrestricted. A repeated
item id counts as a duplicate from its second use onward.

Output lists each entry with its missing fields, citation
status, restriction conflict flag, and duplicate flag, then
prints summary counts. Exit code is 0 when the log file can
be read, 1 when it cannot. Standard library only.
"""
import os
import sys

REQUIRED = ["repository", "collection", "box", "folder"]
OPEN_RESTRICTIONS = ["", "none", "no", "open"]


def parse_log(text):
    entries = []
    blocks = []
    current = []
    for line in text.splitlines():
        if line.strip() == "":
            if current:
                blocks.append(current)
                current = []
        else:
            current.append(line)
    if current:
        blocks.append(current)
    for block in blocks:
        item_id = "-"
        fields = {}
        for line in block:
            stripped = line.strip()
            if ":" not in stripped:
                continue
            key, _, value = stripped.partition(":")
            key = key.strip().lower()
            value = value.strip()
            if key == "item":
                item_id = value if value else "-"
            else:
                fields[key] = value
        entries.append((item_id, fields))
    return entries


def check(log_path):
    if not os.path.isfile(log_path):
        print(f"error: log file not found: {log_path}")
        return 1
    with open(log_path, "r", encoding="utf-8", errors="replace") as fh:
        text = fh.read()
    entries = parse_log(text)
    seen = set()
    rows = []
    missing_count = 0
    rerequestable = 0
    conflicts = 0
    duplicates = 0
    for item_id, fields in entries:
        missing = [name for name in REQUIRED if not fields.get(name)]
        if missing:
            missing_count += 1
        citation_ok = not missing
        if citation_ok:
            rerequestable += 1
        restriction = fields.get("restriction", "").strip().lower()
        planned = fields.get("planned_use", "").strip().lower()
        conflict = restriction not in OPEN_RESTRICTIONS and planned == "unrestricted"
        if conflict:
            conflicts += 1
        duplicate = item_id != "-" and item_id in seen
        if duplicate:
            duplicates += 1
        if item_id != "-":
            seen.add(item_id)
        rows.append((item_id, missing, citation_ok, conflict, duplicate))
    total = len(entries)
    with_all = total - missing_count
    print(f"log_file: {log_path}")
    print(f"entries_total: {total}")
    print(f"entries_with_all_fields: {with_all}")
    print(f"entries_missing_fields: {missing_count}")
    for idx, (item_id, missing, citation_ok, conflict, duplicate) in enumerate(rows, start=1):
        missing_label = ",".join(missing) if missing else "-"
        print(
            f"ENTRY entry={idx} id={item_id} missing={missing_label} "
            f"citation_rerequestable={'yes' if citation_ok else 'no'} "
            f"restriction_conflict={'yes' if conflict else 'no'} "
            f"duplicate={'yes' if duplicate else 'no'}"
        )
    print(f"citations_rerequestable: {rerequestable}")
    print(f"citations_not_rerequestable: {total - rerequestable}")
    print(f"restriction_conflicts: {conflicts}")
    print(f"duplicate_ids: {duplicates}")
    has_issue = missing_count > 0 or conflicts > 0 or duplicates > 0
    print(f"verdict: {'REVIEW' if has_issue else 'PASS'}")
    return 0


def make_fixture(base):
    os.makedirs(base, exist_ok=True)
    log_path = os.path.join(base, "research_log.txt")
    blocks = [
        (
            "ITEM: ARC-001\n"
            "repository: Example Repository\n"
            "collection: Example Papers\n"
            "box: 3\n"
            "folder: 12\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-002\n"
            "collection: Example Papers\n"
            "box: 3\n"
            "folder: 14\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-003\n"
            "repository: Example Repository\n"
            "box: 4\n"
            "folder: 2\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-004\n"
            "repository: Example Repository\n"
            "collection: Example Papers\n"
            "folder: 7\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-005\n"
            "repository: Example Repository\n"
            "collection: Example Papers\n"
            "box: 5\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-006\n"
            "repository: Example Repository\n"
            "collection: Example Papers\n"
            "box: 6\n"
            "folder: 1\n"
            "restriction: closed\n"
            "planned_use: unrestricted\n"
        ),
        (
            "ITEM: ARC-001\n"
            "repository: Example Repository\n"
            "collection: Example Papers\n"
            "box: 3\n"
            "folder: 12\n"
            "restriction: none\n"
            "planned_use: unrestricted\n"
        ),
    ]
    with open(log_path, "w", encoding="utf-8") as fh:
        fh.write("\n".join(blocks))
    print(f"fixture_log: {log_path}")
    return 0


def main():
    args = sys.argv[1:]
    if len(args) == 2 and args[0] == "--make-fixture":
        return make_fixture(args[1])
    if len(args) != 1:
        print("usage: archival_check.py LOG_FILE")
        print("       archival_check.py --make-fixture DIR")
        return 2
    return check(args[0])


if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-21 · 0 installs · Ed25519-signed./install.sh archival-research-basics 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.