% ============================================================================ % Appendix B — Search strategies, sources and acquisition record % Written by Agent L (Appendices B/C instance), 2026-11-03. Operational record % of what ingestion/*.py, systematic_review/define_corpus.py and the runbook % actually did; Part III gives the rationale and must not be repeated here. % Corpus numbers only through macros of tables/numbers.tex; sizes, hashes, % dates, file counts and parameter values are provenance identifiers and are % typed from data/raw/README.md, reports/PHASE0_FEASIBILITY.md and the ledgers. % ============================================================================ \section{Search strategies, sources and acquisition record} \label{sec:appB} This appendix is the operational counterpart of Part~III\@. Part~III explains why four bibliographic sources were combined and what each contributes; the present appendix records, source by source, the endpoint or bucket that was read, the release or version that was read, the filters that were applied, the rate limits and budgets under which the reading took place, the licence of the material, the integrity checks performed on arrival, and what was sought but not obtained. It then gives the reporting items of the PRISMA-S extension for literature searches \citep{rethlefsen2021prismas} in the form they take for a scope-filtered mapping study, the exclusion codes of the eligibility stage with their predicates, the type and platform lists on which those predicates depend, and the record-linkage cascade with its thresholds. Every statement below can be checked against the scripts in \texttt{ingestion/} and \texttt{systematic\_review/}, the runbook (\texttt{reproducibility/RUNBOOK.md}) and the provenance note \texttt{data/raw/README.md}; where a count is a property of the corpus it is rendered by a macro of \texttt{tables/numbers.tex}, and where no macro exists the value is marked pending rather than typed. \subsection{Acquisition at a glance} \label{sec:appB-glance} Table~\ref{tab:appB-acq} lists the five acquisitions in the order of the runbook. All were performed from the compute node \texttt{M3U96b} between the evening of 2~November and 3~November~2026 (Eastern Time), with no credential of any kind: the OpenAlex key, the Semantic Scholar key and the Kaggle credentials were tested as absent before the first download (\texttt{reports/PHASE0\_FEASIBILITY.md}, §2). The absence of credentials is not incidental. It determined the choice of the snapshot over the API for OpenAlex (decision D1 of the plan), it excluded Semantic Scholar (D3), and it capped the post-snapshot increment at the public daily budget (Section~\ref{sec:appB-increment}). \begin{table}[htbp] \centering\footnotesize \caption{Acquisitions performed for the WP44 corpus, in runbook order.} \label{tab:appB-acq} \begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{1.8cm}>{\raggedright\arraybackslash}X>{\raggedright\arraybackslash}p{2.2cm}>{\raggedright\arraybackslash}p{4.0cm}} \toprule Source & Object read & Obtained & Integrity record \\ \midrule OpenAlex works & public snapshot, release 2026-10-14, \texttt{data/jsonl/works/}, 1{,}898 part files, 675{,}298{,}563{,}638 bytes gzip & 2026-11-02 21:19 to 23:05 ET, HTTPS, 8 parallel streams & manifest copy; content length and SHA-256 per file in \texttt{works/ledger.sqlite}; 1{,}898/1{,}898 done, 0 malformed lines; record total equal to the manifest total \\ OpenAlex entities & snapshots of topics, subfields, fields, domains, sources, institutions, publishers, funders, concepts & 2026-11-02 & \texttt{entities\_ledger.json}: content length and SHA-256 per file \\ OpenAlex API & works with any CS topic and publication date 2026-10-01 to 2026-11-02, by day and by primary field & 2026-11-03 onward, within the public budget & \texttt{api\_increment/}\allowbreak\texttt{ledger.json}: calls, cost, records and pages per slice; raw pages kept verbatim \\ DBLP & \texttt{dblp.xml.gz} dated 2026-11-02 22:39 UTC, 1{,}116{,}270{,}104 bytes, with \texttt{dblp.dtd} and \texttt{dblp.xml.gz.md5} & 2026-11-02 & published MD5 \texttt{ff1c424c3b4cef36}\allowbreak\texttt{582f647c36df27c0} verified on the whole file before parsing; parse report with size, MD5 of the first GiB, counts by type and year \\ arXiv & OAI-PMH, set \texttt{cs}, metadata prefix \texttt{arXiv}, datestamps 2018-01-01 to 2026-11-02 & 2026-11-02 to 03 & \texttt{harvest\_state.json} (pages, records, seconds per window); 758 raw XML pages kept; \texttt{arxiv\_parse\_}\allowbreak\texttt{report.json} \\ ACL Anthology & \texttt{anthology+abstracts.bib.gz}, 46{,}723{,}382 bytes & 2026-11-02 & SHA-256 \texttt{44c3f7e907f221b740}\allowbreak\texttt{bea09069818a3d7c0d}\allowbreak\texttt{fd88b2da3b750d9f02}\allowbreak\texttt{955efc58b3}; \texttt{acl\_parse\_}\allowbreak\texttt{report.json} \\ \bottomrule \end{tabularx} \end{table} \subsection{OpenAlex works snapshot} \label{sec:appB-openalex} \paragraph{Bucket, release and manifest.} The works entity of the OpenAlex snapshot \citep{priem2022openalex} is distributed as gzip-compressed JSON Lines files in the public S3 bucket \texttt{s3://openalex/} under the prefix \texttt{data/jsonl/works/}, partitioned by the date at which the records were last updated (\texttt{updated\_date=YYYY-MM-DD/}\allowbreak\texttt{part\_NNNN.gz}). The release read is the one whose \texttt{manifest.json} carries the date 2026-10-14; the manifest lists 1{,}898 part files with, for each, its URL, its content length in bytes and its record count, together with the totals (\LinkSlimTotal{} works, 675{,}298{,}563{,}638 bytes). A copy of the manifest is kept as \texttt{data/raw/openalex/works\_manifest\_2026-10-14.json} and is the reference against which the ledger is checked. No AWS client was installed on the node; the bucket is public and each object was fetched over HTTPS from \url{https://openalex.s3.amazonaws.com/} with \texttt{curl} (\texttt{-L --retry 5 --retry-delay 3 --retry-all-errors -C -}, the last option resuming an interrupted transfer), up to five outer attempts per file with a linearly increasing pause, and the transfer was accepted only when the byte count on disk equalled the content length in the manifest. The measured single-stream throughput from the node was about 71~MB/s and the aggregate over eight streams about 108~MB/s, which fixed the worker count at eight and the wall time at one hour and forty-six minutes for the whole bucket (\texttt{ingestion/openalex\_snapshot.py --workers 8}). \paragraph{Filter applied on the stream (pass 1).} Each part file was decompressed in memory and parsed line by line (\texttt{orjson}); a line that failed to parse was counted as malformed and skipped, and the count of such lines (zero for every file) is in the ledger. For every parsed work $w$ the script evaluated the predicate \begin{equation} \mathrm{CS}(w) \;=\; \Bigl[\exists\, \theta \in \mathrm{topics}(w) : \mathrm{field}(\theta) = \text{\texttt{fields/17}}\Bigr] \;\vee\; \Bigl[\mathrm{field}\bigl(\mathrm{primary\_topic}(w)\bigr) = \text{\texttt{fields/17}}\Bigr], \label{eq:appB-csfilter} \end{equation} that is, whether any of the up to three topics attached to the record, or its primary topic, belongs to the OpenAlex field with identifier~17 (\emph{Computer Science}; \citealp{openalex2024topics}), and kept the \emph{complete} JSON record when $\mathrm{CS}(w)$ held and \texttt{publication\_year} lay in $[2018, 2026]$. Records flagged \texttt{is\_xpac} (OpenAlex's expansion of coverage from non-Crossref sources) were kept at this stage with their flag, so that the exclusion could be applied later by the eligibility predicate rather than silently at ingestion. The retained records were re-compressed with Zstandard (level~6) into one file per part, \texttt{works/cs/}\allowbreak\texttt{updated\_date=X\_\_part\_NNNN.jsonl.zst}, about 20~GB in total for \PrismaIdentifiedOpenalexPassOneCsTopicFilter{} records. No text search of any kind was performed: the scope of the OpenAlex layer is defined entirely by the topic classifier's field assignment, which is the search strategy appropriate to a field-level mapping and whose consequences are discussed in Part~III\@. \paragraph{Slim table of every work.} In the same pass the script wrote, for \emph{every} work in the snapshot regardless of field or year, one row of a slim Parquet table with the following columns: OpenAlex identifier (as an integer), DOI (lower-cased, prefix removed), publication year and date, type, \texttt{cited\_by\_count}, primary topic, subfield, field and domain identifiers, the value of $\mathrm{CS}(w)$, the XPAC, open-access and retraction flags, language, primary source identifier, number of referenced works, number of authors, whether an abstract is present, and FWCI\@. The 1{,}898 per-part files were later concatenated into \texttt{compact/slim\_all\_works.parquet} (\LinkSlimTotal{} rows). The slim table is what allows a reference from a computer-science work to a work in another field to be typed and dated (Part~IV), and it is also what made the second pass of Section~\ref{sec:appB-pass2} possible without re-reading the bucket for a search. \paragraph{Integrity and provenance.} Before parsing, the SHA-256 digest of the gzip bytes of each part file was computed and written, with the file's URL, content length, manifest record count, parsed record count, retained record count, malformed-line count, download and total times, attempt count and timestamps, to a SQLite ledger (\texttt{works/ledger.sqlite}, table \texttt{files}); a second table (\texttt{runs}) records the manifest date and totals, the worker count, the host name and the command line of every invocation. The ledger makes the job resumable (a file with status \texttt{done} is never re-read) and is the provenance record of the raw layer. The raw \texttt{.gz} files were deleted after a successful parse (decision D6 of the plan): the snapshot is a public, versioned release, the manifest and the per-file digests permit an independent party to re-download and verify any part, and keeping 675~GB of compressed JSON on the node would have precluded the rest of the pipeline. Three checks were made at the end of the pass and are recorded in \texttt{data/raw/README.md}: all 1{,}898 files have status \texttt{done}; the sum of parsed records over files equals the manifest total; and the sum of malformed lines is zero. The OpenAlex snapshot is released under CC0~1.0 (\texttt{LICENSE.txt} in the bucket, copied next to the manifest), which permits redistribution of derived record-level data. \paragraph{Entity snapshots.} The smaller entity snapshots were downloaded from the same bucket by \texttt{ingestion/}\allowbreak\texttt{openalex\_entities.py}, using each entity's own manifest, with the same length check and SHA-256 ledger (\texttt{entities\_ledger.json}). The manifests list 4{,}516 topics, 252 subfields, 26 fields, 4 domains, 268{,}629 sources, 143{,}455 institutions, 10{,}713 publishers, 41{,}799 funders and 65{,}026 concepts; the topics snapshot was flattened to \texttt{topics\_flat.csv} (identifier, name, subfield, field, domain, works count, keywords, description) and is the vocabulary over which the ontology of Appendix~A maps OpenAlex topics to categories. The authors entity was not downloaded, because the authorship blocks embedded in each work record already carry the author identifier, display name, ORCID, position and institutions; the keywords entity (1{,}881{,}723 entries in its manifest) was not downloaded either, the per-work keyword lists being embedded as well. \subsection{Second snapshot pass by identifier set} \label{sec:appB-pass2} The first pass retains what OpenAlex calls computer science. The record linkage of Section~\ref{sec:appB-linkage} showed that \LinkDblpUnlinkedWithDoiInSlim{} DBLP records of 2018--2026 have a DOI that exists in the slim table of all works but not in the retained subset, that is, works indexed by DBLP that OpenAlex labels with no computer-science topic. Rather than accept OpenAlex's boundary, the pipeline reads the bucket a second time in \emph{identifier-set mode}. The linkage script writes the list \texttt{data/metadata/}\allowbreak\texttt{needed\_oa\_ids.csv} of OpenAlex identifiers to recover, namely the works of the slim table outside the retained subset that an unlinked DBLP DOI or an unlinked arXiv DOI reaches. The list is re-derived at every linkage run; after the run on the union of both passes it holds only the residue that the second pass left outside the subset (\LinkNeededOaIdsSecondPassDblpDoi{} identifiers reached through a DBLP DOI and \LinkNeededOaIdsSecondPassArxivDoi{} through an arXiv DOI). Given the list, \texttt{openalex\_snapshot.py --id-list} streams every part file again, keeping exactly the records whose identifier is in the set and writing no slim rows. The identifier set is loaded in each worker process (macOS uses the \texttt{spawn} start method, so module globals are rebuilt per worker), the same download, length and SHA-256 discipline applies, and the output lands in \texttt{works\_pass2/cs/} with its own ledger. The recovered records are normalised with the suffix \texttt{\_p2} and the linkage is rerun on the union of both passes (\texttt{ingestion/run\_pass2\_chain.sh}). The second pass costs the same bandwidth as the first---about one hour and fifty minutes of streaming---because an identifier lookup cannot be pushed to the bucket; the price was accepted because the alternative, fetching the identifiers one by one through the API, would have exceeded the public budget by two orders of magnitude. The PRISMA counts of Part~III are generated from the union of both passes: the first pass retained \PrismaIdentifiedOpenalexPassOneCsTopicFilter{} records through the topic filter, the second added \PrismaIdentifiedOpenalexPassTwoDblpArxivLinked{} records reached through DBLP or arXiv identifiers, and the identification total of the PRISMA figure is their sum, \PrismaIdentifiedOpenalexTotalRecordsLoaded{} records loaded into $\mathcal{W}_0$. \subsection{OpenAlex API increment} \label{sec:appB-increment} The snapshot ends on 14~October 2026 and the observation window on 2~November 2026. The gap was covered by the REST API (\texttt{https://api.openalex.org/works}) without a key. Two constraints of the unkeyed API shaped the harvest and are recorded here because they bound what the increment can contain. First, cursor paging requires a key, so the harvest used basic page numbering, which the API caps at 10{,}000 results per filter expression; the harvest is therefore \emph{sliced}, one request series per publication day $d$ and per primary-field class, with the filter \begin{quote}\ttfamily topics.field.id:17, from\_publication\_date:$d$, to\_publication\_date:$d$, primary\_topic.field.id:17 \quad (slice ``cs\_primary'')\\ topics.field.id:17, from\_publication\_date:$d$, to\_publication\_date:$d$, primary\_topic.field.id:!17 \quad (slice ``cs\_secondary'') \end{quote} for every day from 2026-10-01 to 2026-11-02 (the start was placed two weeks before the snapshot date to absorb records that OpenAlex back-dates), 200 results per page, a \texttt{select} list of the fields used by the normaliser (identifiers, dates, type, language, locations, open access, authorships, topics, keywords, referenced works, citation counts, abstract inverted index, flags, FWCI, funders, indexed-in, full-text flag, APC), the polite-pool \texttt{mailto} parameter and a descriptive user agent. A slice whose reported \texttt{count} exceeds 10{,}000 is marked \texttt{truncated} in the ledger; the two-way split by primary field was chosen because a single day of computer-science output in 2026 can exceed the cap while each half rarely does. Second, the unkeyed API is metered: list-and-filter calls are charged at about \$0.0001 each against a shared budget of \$0.10 per day, and the script stops at \$0.085 per day (a margin for the free single-record lookups used for reference verification), sleeps 0.12~s between calls, and halts on HTTP~429, 403 or any 5xx response so that the next day's run resumes from the ledger. Every page is stored verbatim as \texttt{api\_increment/pages/\_/page\_NNNN.json}; the ledger (\texttt{api\_increment/ledger.json}) records per slice the pages, records, reported count, completion and truncation flags, and globally the number of calls and the cost. At the time of writing the harvest had used the budget of several consecutive days and was not complete: \IncrementApiCalls{} calls had retrieved \IncrementRecordsRetrieved{} records at a cost of USD~\IncrementCostUsd{}, and \IncrementSlicesDone{} of the sixty-six day-slices (thirty-three days times two strata) were closed. The number of truncated slices is \IncrementTruncatedSlices{}: the \texttt{cs\_secondary} stratum of 16~October 2026 reported a count nearly four times the 10{,}000-record paging limit, so that roughly a quarter of its records were retrieved. The remaining slices will be reported from the ledger when they close. The conversion step (\texttt{ingest\_api\_increment.py}) rewrites the pages into the same \texttt{.jsonl.zst} part format as the snapshot filter (\texttt{works\_pass3/}), skipping any work whose identifier is already in the pass-1 or pass-2 tables and writing an \texttt{increment\_report.json} (pages, new works, works already present); the number of works added by the increment is \pending{IncrementNewWorks}. Two limitations follow from the design and are not recoverable from the ledger: a truncated slice loses the records beyond the 10{,}000th in the API's default ordering, and a record created after 2~November 2026 with a publication date inside the window is absent whatever its date. Both affect only the last weeks of a window that is already treated as right-censored (Part~III), so no analysis depends on them. \subsection{DBLP} \label{sec:appB-dblp} \paragraph{Dump and version.} DBLP \citep{ley2002dblp, ley2009dblp} publishes its whole database as a single XML file. The file read is \texttt{dblp.xml.gz} as served on 2~November 2026 (file timestamp 22:39 UTC), 1{,}116{,}270{,}104 bytes, together with the document type definition \texttt{dblp.dtd} and the published checksum file \texttt{dblp.xml.gz.md5}. The MD5 of the whole file, \texttt{ff1c424c3b4cef36582f647c36df27c0}, was computed and compared with the published value before parsing; the parse report additionally stores the file size and an MD5 of the first gibibyte as a quick identity check for later sessions. DBLP is released under the Open Data Commons Attribution licence (ODC-BY~1.0), which permits the redistribution of derived data with attribution. The download from \texttt{dblp.org} proceeded at about 12~MB/s, the slowest link of the whole acquisition. \paragraph{Parsing rule.} The DTD matters: DBLP titles use XML entities for accented characters, and a parser that does not resolve them against \texttt{dblp.dtd} corrupts titles and therefore the title-based linkage. The parser (\texttt{ingestion/dblp\_parse.py}) is \texttt{lxml.etree.iterparse} over the gzip stream with \texttt{load\_dtd=True}, \texttt{resolve\_entities=True} and \texttt{huge\_tree=True}, run from the directory containing the DTD because the dump references it relatively. It emits one row per record of the types \textit{article}, \textit{inproceedings}, \textit{proceedings}, \textit{book}, \textit{incollection}, \textit{phdthesis}, \textit{mastersthesis} and \textit{data}; person records (\texttt{www}) are consumed and discarded. For each record it keeps the key, type, modification date, publication-type attribute, title (concatenated text of the title element, so that embedded markup such as subscripts is flattened), year, author names and DBLP person identifiers, editors, journal, booktitle, volume, number, pages, publisher, series, the DOI (taken from the first electronic-edition URL beginning with \texttt{https://doi.org/}, lower-cased), the full list of electronic-edition URLs, the URL, the cross-reference to the proceedings record, and the \emph{stream}, the part of the key before its last slash (\texttt{journals/tocs}, \texttt{conf/nips}), which is DBLP's venue identifier and the unit on which the ontology's venue signals are defined. All years are parsed; counts by type and by year for the entire archive are written to \texttt{dblp\_parse\_report.json}, and the records of 2018--2026 (\LinkDblpTwoZeroOneEightTwoZeroTwoSix{} after the type restriction of the linkage step) are the ones that enter the corpus. The total number of publication records in the dump is \DblpParsedRecords{}. A second script (\texttt{dblp\_streams.py}) lists the streams with at least one hundred records of type article or inproceedings in 2018--2026, with their first and last years and a sample journal or booktitle name; this list (\texttt{taxonomy/dblp\_streams\_2018\_2026.csv}) is the input of the venue-to-category mapping and is not itself a corpus filter. \subsection{arXiv} \label{sec:appB-arxiv} \paragraph{Protocol and windows.} arXiv \citep{ginsparg2011arxiv} metadata were harvested through its OAI-PMH endpoint \texttt{https://oaipmh.arxiv.org/oai} with the verb \texttt{ListRecords}, the set \texttt{cs} and the metadata prefix \texttt{arXiv}, which returns arXiv's own schema (identifier, dates, title, abstract, authors with forenames and keynames, categories, comments, journal reference, DOI, licence, report number, MSC and ACM classes). The harvest was sliced into calendar-month windows on the OAI \texttt{from}/\texttt{until} parameters from 2018-01-01 to 2026-11-02; each window was walked with the resumption token until exhausted, and every response page was written verbatim to \texttt{raw//page\_NNNN.xml} before being parsed, so that the Parquet table can be rebuilt from the raw pages (\texttt{--parse-only}) without touching the network. The harvester waited 3.5~s between requests, honoured the \texttt{Retry-After} header on HTTP~503 (arXiv's flow-control response), waited thirty seconds on other errors, and gave up a window after twelve consecutive failures (none occurred). The state file \texttt{harvest\_state.json} records, per window, the number of pages, of records and of seconds, and the harvest of the 107 windows produced 758 pages in about one hour. \paragraph{Datestamp semantics and deduplication.} A point about OAI-PMH that affects the counts must be stated. The \texttt{from}/\texttt{until} parameters select on the record's \emph{datestamp}, which is the date of its last modification, not on its submission date. A monthly window therefore contains every record touched in that month, including old e-prints whose metadata were edited, and a record edited in two months appears in two windows. The parser keeps, for each arXiv identifier, the occurrence with the latest datestamp, skips records whose header carries \texttt{status="deleted"}, normalises whitespace in titles and abstracts, lower-cases the DOI, takes the first listed category as the primary category, and records the set specifications. The result is \LinkArxivCsRecords{} distinct records, of which \LinkArxivCreatedTwoZeroOneEightTwoZeroTwoSix{} have a \texttt{created} date (first submission) in 2018--2026; the difference consists of older e-prints modified during the window, which are kept in the table for linkage but do not belong to the observation window. The \texttt{created} date is the one used as $d_{\mathrm{arx}}(w)$ in Part~IV\@. arXiv's metadata are offered for reuse under its metadata terms; the e-prints themselves were not downloaded in the acquisition described here. \subsection{ACL Anthology} \label{sec:appB-acl} The ACL Anthology \citep{radev2013aclanthology, acl2024anthology} distributes its complete bibliography as a BibTeX export. The variant with abstracts (\texttt{anthology+}\allowbreak\texttt{abstracts.bib.gz}) was downloaded on 2~November 2026 (46{,}723{,}382 bytes; SHA-256 in Table~\ref{tab:appB-acq}). BibTeX is not a regular language, so the parser (\texttt{ingestion/acl\_parse.py}) walks the text by brace depth: it locates each \texttt{@type\{} header, finds the matching closing brace, splits the body at its first comma into the citation key (which is the Anthology identifier, for instance \texttt{P19-1001} or \texttt{2023.acl-long.1}) and the field list, and reads each field as a braced, quoted or bare value with nested braces allowed. Fifteen fields are kept (title, author, editor, booktitle, journal, month, year, address, publisher, URL, DOI, pages, abstract, volume, number); braces inside values are removed and whitespace normalised. Entries of type \texttt{comment}, \texttt{string} and \texttt{preamble} are skipped. The export is released under CC~BY~4.0. The number of entries parsed is \AclParsedEntries{}; the linkage step uses only the \textit{inproceedings} and \textit{article} entries (\LinkAclRecords{}), of which \LinkAclTwoZeroOneEightTwoZeroTwoSix{} fall in 2018--2026. The Anthology's role is a coverage check for natural-language processing venues rather than a corpus source in its own right; an ACL entry with no OpenAlex counterpart is counted in the linkage report but is never a work of the corpus. \subsection{What was sought and not obtained} \label{sec:appB-notobtained} The mandate lists sources beyond the four used, and the integrity rules require that inaccessible data be recorded rather than worked around. The record is as follows. \begin{description} \item[Semantic Scholar Academic Graph.] An anonymous request to the endpoint \texttt{graph/v1/paper/} for \texttt{arXiv:2303.08774} returned HTTP~429 with the instruction to apply for a key, and the datasets endpoint requires a key outright \citep{kinney2023s2ag}. No key was requested because the project rule is not to assume credentials and because applying would have made the acquisition depend on an approval outside the project's control. The consequence is the absence of the influential-citation and citation-intent signals from every analysis, stated wherever it matters. \item[OpenAlex API key.] A free key raises the daily budget from \$0.10 to \$1 and enables cursor paging. Obtaining it requires the principal investigator to confirm an e-mail; this was not done without instruction, and the increment of Section~\ref{sec:appB-increment} was run under the unkeyed budget. The snapshot route does not depend on the key. \item[Full texts.] No full text was downloaded during corpus construction. Abstracts come from the OpenAlex inverted index (and from arXiv and the Anthology for their records). The plan reserves lawful full-text access (arXiv e-prints at one request per three seconds, open-access locations) for at most 10{,}000 high-priority works in a later phase; the knowledge-graph extraction of Part~IV is abstract-based and labelled as such. \item[Crossref.] The REST API was reached (\texttt{works?filter=}\allowbreak\texttt{from-pub-date:2022-11-30\&rows=0} returned a count for all fields) and is used for the verification of every DOI cited in the manuscript and for sample cross-checks of types and citation counts; it was not used as a corpus source, OpenAlex already carrying its metadata. \item[Other sources listed in the mandate.] Kaggle arXiv snapshot, Microsoft Academic Graph, Papers with Code, institutional repositories: no Kaggle credentials were configured and OAI-PMH made the snapshot unnecessary; the Microsoft Academic Graph was discontinued in 2021; benchmark and code repositories are scoped to the reproducibility analyses of a later phase and are not bibliographic sources; institutional repositories enter only through OpenAlex's indexing of them, which the generic-deposit rule of Section~\ref{sec:appB-exclusions} then restricts. \end{description} \subsection{PRISMA-S search record} \label{sec:appB-prismas} PRISMA-S \citep{rethlefsen2021prismas} is written for keyword searches of bibliographic databases. A scope-filtered mapping has no search string, but its sixteen items can still be answered, and answering them makes explicit which items have no counterpart. Table~\ref{tab:appB-prismas} gives the record; the PRISMA~2020 flow itself \citep{page2021prisma, haddaway2022prismaflow} is Figure~\ref{fig:prisma} and Table~\ref{tab:prisma} of Part~III\@. \begin{table}[htbp] \centering\footnotesize \caption{PRISMA-S items answered for the WP44 corpus.} \label{tab:appB-prismas} \begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{0.5cm}>{\raggedright\arraybackslash}p{3.2cm}>{\raggedright\arraybackslash}X} \toprule No. & Item & This study \\ \midrule 1 & Database names & OpenAlex (works and entity snapshots), DBLP, arXiv, ACL Anthology; Crossref for verification only. \\ 2 & Multi-database searching & Each source read in full under its own scope filter; no interface or vendor platform (direct bucket, dump and OAI access). \\ 3 & Study registries & Not applicable. \\ 4 & Online resources and browsing & None. \\ 5 & Citation searching & None for identification; OpenAlex reference lists are analysed but do not add records to the corpus. \\ 6 & Contacts & None. \\ 7 & Other methods & None. \\ 8 & Full search strategies & OpenAlex: predicate~\eqref{eq:appB-csfilter} with publication year 2018--2026, then identifier-set recovery of DBLP- and arXiv-linked works (Section~\ref{sec:appB-pass2}), then the API filter of Section~\ref{sec:appB-increment}. DBLP: whole dump, record types listed in Section~\ref{sec:appB-dblp}, years 2018--2026 at linkage. arXiv: OAI set \texttt{cs}, datestamps 2018-01-01 to 2026-11-02, \texttt{created} in 2018--2026 at linkage. ACL: whole export, types inproceedings and article, years 2018--2026 at linkage. \\ 9 & Limits and restrictions & Year window only; no language, type or country restriction at search time (types are an eligibility criterion, Section~\ref{sec:appB-exclusions}). \\ 10 & Search filters & None beyond the scope predicates; the OpenAlex topic classifier is the instrument that defines ``computer science'' on that layer and its properties are discussed in Part~III\@. \\ 11 & Prior work & No search strategy was reused from a prior review. \\ 12 & Updates & One update by API increment to 2026-11-02; no further update is planned for this version. \\ 13 & Dates of searches & OpenAlex snapshot released 2026-10-14, read 2026-11-02; DBLP dump 2026-11-02; arXiv harvest 2026-11-02 to 03; ACL export 2026-11-02; API increment from 2026-11-03. \\ 14 & Peer review of the search & No librarian review; the strategy is preregistered in \texttt{protocol/PROTOCOL.md} (P3) and audited by the independent agents of Phase~8. \\ 15 & Total records & OpenAlex \PrismaIdentifiedOpenalexTotalRecordsLoaded{} (\PrismaIdentifiedOpenalexPassOneCsTopicFilter{} in pass 1, \PrismaIdentifiedOpenalexPassTwoDblpArxivLinked{} in pass 2); DBLP \PrismaIdentifiedDblpTwoZeroOneEightTwoZeroTwoSix{}; arXiv \PrismaIdentifiedArxivCsTwoZeroOneEightTwoZeroTwoSix{}; ACL \PrismaIdentifiedAclTwoZeroOneEightTwoZeroTwoSix{} (all 2018--2026). \\ 16 & Deduplication & Deterministic record linkage across sources (Section~\ref{sec:appB-linkage}) and preprint--publication merging within OpenAlex (Part~III, Part~IV); OpenAlex works are the counting unit; \PrismaExcludedEFiveDuplicate{} preprint members of merged pairs are flagged E5. \\ \bottomrule \end{tabularx} \end{table} \subsection{Exclusion codes and their predicates} \label{sec:appB-exclusions} The eligibility stage (\texttt{systematic\_review/define\_corpus.py}) computes seven exclusion flags and two membership flags as columns of the table \texttt{corpus\_works}, each a SQL expression over the normalised OpenAlex record and the linkage tables. Table~\ref{tab:appB-codes} paraphrases the predicates. Three lists on which they depend are reproduced verbatim from the script, because their content is the substance of the type rule and of amendment A-002. \begin{itemize} \item $T_{\mathrm{keep}}$ (types kept without further condition): \texttt{article}, \texttt{conference-}\allowbreak\texttt{paper}, \texttt{preprint}, \texttt{book-}\allowbreak\texttt{chapter}, \texttt{book}, \texttt{dissertation}, \texttt{report}, \texttt{review}, \texttt{software-}\allowbreak\texttt{paper}. \item $T_{\mathrm{hard}}$ (types excluded even for DBLP-linked records): \texttt{editorial}, \texttt{erratum}, \texttt{retraction}, \texttt{peer-}\allowbreak\texttt{review}, \texttt{paratext}, \texttt{conference-}\allowbreak\texttt{abstract}, \texttt{reference-}\allowbreak\texttt{entry}, \texttt{dataset}, \texttt{software}, \texttt{supplementary-}\allowbreak\texttt{materials}, \texttt{libguides}, \texttt{letter}, \texttt{grant}, \texttt{standard}. \item $\mathcal{G}$ (generic deposit platforms, matched on the primary source's display name): ``Zenodo (CERN European Organization for Nuclear Research)'' and ``Zenodo''; ``Figshare''; ``Institutional Repositories DataBase (IRDB)''; ``LA Referencia (Red Federada de Repositorios Institucionales de Publicaciones Cient\'{\i}ficas)''; ``Dialnet (Universidad de la Rioja)''; ``DOAJ (DOAJ: Directory of Open Access Journals)''; ``OSTI OAI (U.S. Department of Energy Office of Scientific and Technical Information)''. \end{itemize} \begin{table}[htbp] \centering\footnotesize \caption{Exclusion and membership codes of \texttt{define\_corpus.py}, with their predicates paraphrased from SQL. $w$ is an OpenAlex record; \textit{in\_dblp}, \textit{in\_acl} and \textit{arxiv\_linked} are read from the link tables; $t_0$ = 2018-01-01, $t_1$ = 2026-11-02.} \label{tab:appB-codes} \begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{0.9cm}>{\raggedright\arraybackslash}p{3.4cm}>{\raggedright\arraybackslash}X>{\raggedright\arraybackslash}p{1.9cm}} \toprule Code & Column & Predicate (true = flagged) & Marginal count \\ \midrule E1 & \texttt{e1\_outside\_window} & \texttt{publication\_date} is null, or $< t_0$, or $> t_1$ & \PrismaExcludedEOneOutsideWindow{} \\ E2 & \texttt{e2\_type} & $\tau(w) \notin T_{\mathrm{keep}}$ and not (\textit{in\_dblp} and $\tau(w) \notin T_{\mathrm{hard}}$); a null type counts as outside $T_{\mathrm{keep}}$ & \PrismaExcludedETwoType{} \\ E3 & \texttt{e3\_xpac} & \texttt{is\_xpac} and not \textit{in\_dblp} & \PrismaExcludedEThreeXpac{} \\ --- & \texttt{xpac\_kept\_dblp} & \texttt{is\_xpac} and \textit{in\_dblp} (kept, flagged) & \PrismaExcludedXpacKeptDblp{} \\ E4 & \texttt{e4\_no\_title} & title null or empty after trimming & \PrismaExcludedEFourNoTitle{} \\ E5 & \texttt{e5\_duplicate} & $w$ is the preprint member of a surviving preprint--published pair (Part~IV, eq.~\eqref{eq:duppair}) & \PrismaExcludedEFiveDuplicate{} \\ E6 & \texttt{e6\_retracted} & \texttt{is\_retracted} (flag only; not part of eligibility) & \PrismaExcludedESixRetracted{} \\ E7 & \texttt{e7\_generic\_deposit} & primary source name $\in \mathcal{G}$ and not \textit{in\_dblp} and no linked arXiv record & \PrismaExcludedESevenGenericDeposit{} \\ core & \texttt{cs\_core} & \textit{in\_dblp} or \texttt{cs\_primary} or linked arXiv primary category begins with \texttt{cs.} (amendment A-006) & \PrismaCsCoreEligible{} eligible \\ pen. & \texttt{cs\_penumbra} & \texttt{cs\_any} and not \texttt{cs\_core} & \PrismaCsPenumbraEligibleOtherwise{} otherwise eligible \\ \midrule --- & \texttt{eligible} & $\neg$E1 $\wedge$ $\neg$E2 $\wedge$ $\neg$E3 $\wedge$ $\neg$E4 $\wedge$ $\neg$E5 $\wedge$ $\neg$E7 $\wedge$ core & \PrismaCsCoreEligible{} \\ \bottomrule \end{tabularx} \end{table} Three further columns are derived in the same statement and are used throughout the manuscript. The first date, \texttt{first\_date}, is the least of the publication date, the merged preprint's date and the arXiv creation date, except that a publication date falling on 1~January is treated as year-only precision and is overridden by either of the other two when available; \texttt{date\_precision} is \texttt{year} when the date falls on 1~January and no finer date exists, \texttt{month} otherwise; and \texttt{period} is \texttt{pre}, \texttt{baseline}, \texttt{post} or \texttt{future} according to whether the first date precedes $t_0$, lies in $[t_0, \text{2022-11-30})$, in $[\text{2022-11-30}, t_1]$, or after $t_1$. The PRISMA counts are computed twice, marginally (the column of Table~\ref{tab:appB-codes}) and sequentially in the order E1, E2, E3, E4, E5, E7, and the script asserts that the loaded total equals the sum of the sequential exclusions and the after-exclusion count (\PrismaAfterExclusions{}); the macro set of Part~III reports both. Since amendment A-006 the script also asserts that the after-exclusion count equals the core plus the penumbra plus the records that belong to neither set (\PrismaNeitherCoreNorPenumbraAfterExclusions{}, works with no computer-science topic reached only through a DOI and not carrying an arXiv \texttt{cs.} primary category). The E6 and penumbra rows are not exclusions and do not enter the sum. The numbering conflict between the protocol's E7 (penumbra) and the code's E7 (generic deposit) is resolved by amendment A-005 in favour of the code. \subsection{Record-linkage cascade and thresholds} \label{sec:appB-linkage} Part~IV states the cascade in prose; Table~\ref{tab:appB-cascade} gives it as executed, step by step, with every threshold. The normalised title $\eta(\cdot)$ lower-cases and deletes every character outside $[a$--$z0$--$9]$; ``unique'' means that the normalised title occurs exactly once on that side among the records still unlinked at that step (for OpenAlex in the arXiv and ACL cascades, unique among all OpenAlex records, the exclusion of already linked identifiers being applied to the pair instead); the length condition is on the raw title for DBLP and OpenAlex and on the normalised title for arXiv and ACL\@. The DBLP title step adds a first-author check that the other cascades do not have: the last whitespace-separated token of the first author's name on each side must coincide when both are present. Each link carries its method label, which is the stratum of the precision sample of Part~IV; the counts per method are in \texttt{data/metadata/linkage\_report.json} and Table~\ref{tab:linkage}. \begin{table}[htbp] \centering\footnotesize \caption{The deterministic linkage cascade of \texttt{ingestion/link\_sources.py}. Steps within a cascade apply only to records unlinked by the previous steps.} \label{tab:appB-cascade} \begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{1.6cm}>{\raggedright\arraybackslash}p{1.4cm}>{\raggedright\arraybackslash}X>{\raggedright\arraybackslash}p{1.8cm}} \toprule Cascade & Step & Join condition and thresholds & Method label \\ \midrule \multirow{3}{*}{DBLP--OA} & 1 & equal lower-cased DOI; all matching pairs kept (many-to-many allowed) & \texttt{doi} \\ & 2 & equal arXiv identifier (DBLP: parsed from \texttt{ee} URLs by \texttt{arxiv.org/abs/([\^{} ]+)}; both sides stripped of \texttt{v[0-9]+\$}); both records unlinked & \texttt{arxiv} \\ & 3 & equal $\eta$, raw title $\ge 25$ characters, $\eta$ unique on both sides, $|y_{\mathrm{DBLP}} - y_{\mathrm{OA}}| \le 1$, first-author surnames equal when both present & \texttt{title\_year} \\ \midrule \multirow{3}{*}{arXiv--OA} & 1 & equal arXiv identifier (OpenAlex side: \texttt{ids.arxiv}, else DOI prefix \texttt{10.48550/arxiv.}, else \texttt{arxiv.org/abs|pdf/} in any location URL); all pairs kept & \texttt{arxiv\_id} \\ & 2 & equal lower-cased DOI; arXiv record unlinked & \texttt{doi} \\ & 3 & equal $\eta$, $|\eta| \ge 25$, unique on both sides, $|y_{\mathrm{created}} - y_{\mathrm{OA}}| \le 2$, OpenAlex record unlinked & \texttt{title\_year} \\ \midrule \multirow{3}{*}{ACL--OA} & 1 & equal lower-cased DOI & \texttt{doi} \\ & 2 & Anthology identifier equal to the segment after \texttt{aclanthology.org/} in any OpenAlex landing-page URL (with or without trailing slash); ACL record unlinked & \texttt{url} \\ & 3 & equal $\eta$, $|\eta| \ge 25$, unique on both sides, $|y_{\mathrm{ACL}} - y_{\mathrm{OA}}| \le 1$, OpenAlex record unlinked & \texttt{title\_year} \\ \bottomrule \end{tabularx} \end{table} The record types admitted on the DBLP side are \textit{article}, \textit{inproceedings}, \textit{incollection}, \textit{book}, \textit{phdthesis} and \textit{data} with year 2018--2026; on the ACL side \textit{inproceedings} and \textit{article}; on the arXiv side every record of the harvest, the year condition being applied through the \texttt{created} year in the title step only. The linkage runs inside one DuckDB database with 24 threads and a 60~GB memory limit and completes in about ten seconds on the compacted tables, which is why it was possible to rerun it after each amendment rather than patch its output. The precision sample of 500 pairs (250 DOI, 200 title--year, 50 arXiv, the last stratum empty for the reason given in Part~IV) is drawn by \texttt{systematic\_review/linkage\_sample.py} with seed 44, and its automatic agreement checks (title similarity at least 0.9, year difference at most one, first-author surname agreement) are the interim figure until the principal investigator's review (checkpoint H-1). \subsection{Reproducing the acquisition} \label{sec:appB-repro} The acquisition can be repeated from the runbook in six commands, in the order of Table~\ref{tab:appB-acq}, on a machine with Python~3.12 and the locked environment of \texttt{reproducibility/}\allowbreak\texttt{environment\_M3U96b.txt}: \begin{enumerate} \item \texttt{openalex\_snapshot.py} with the manifest copy (then once more with \texttt{--id-list}); \item \texttt{openalex\_entities.py}; \item \texttt{dblp\_parse.py}; \item \texttt{arxiv\_oai.py}; \item \texttt{acl\_parse.py}; \item \texttt{openalex\_api\_increment.py}, followed by \texttt{ingest\_api\_increment.py}. \end{enumerate} Two of these are not exactly repeatable and the reader should know which. The OpenAlex bucket retains only recent releases, so a later reader must either locate the 2026-10-14 release or accept that a newer release will differ in record counts and in the nightly-recomputed fields (citation counts, FWCI); the per-file digests in the ledger allow a reader with the same release to prove identity, and a reader with a different release to quantify the difference by record count. The arXiv OAI feed is likewise live: a re-harvest of the same datestamp windows returns records as they now are, and the raw pages archived with the project are the only way to recover the state of 2--3~November 2026. The DBLP dump and the ACL export are dated files whose hashes are published here, and a copy of each is kept with the project's raw layer.