Add read-only PT9 interlinear projectInterface to the Paratext PDP - #2707
Add read-only PT9 interlinear projectInterface to the Paratext PDP#2707jasonleenaylor wants to merge 1 commit into
Conversation
Add platformScripture.Pt9Interlinear, a read-only projectInterface on the Paratext project data provider that exposes a project's persisted PT9 interlinear files for importing legacy interlinear data. - getPt9InterlinearManifest: per-file SHA-256 over raw bytes, for cheap change detection without transferring content. - getPt9InterlinearFiles: each file's raw text plus that same byte hash, so a caller stores the fingerprint of exactly what it read. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lyonsil
left a comment
There was a problem hiding this comment.
Review: read-only PT9 interlinear projectInterface
Reviewed eee76d0 against merge-base bac6493 in a throwaway worktree. Every claim below was checked against the code rather than inferred: the PT9 source tree, the live node_modules/ws, and real PT9 project data on this machine. Where a fix is proposed I implemented and ran it - the C# suite builds and passes (476 tests in TestParanextDataProvider.Projects, including this PR's 10, with every proposed fix applied).
Verdict: the plumbing follows the PDP recipe correctly. All 6 steps are present, the C# wire names and the TS data-type keys match byte-for-byte, platformScripture.Pt9Interlinear casing follows the Versification/MarkerNames precedent, and no generated artifact is stale (lib/papi-dts/papi.d.ts does not mirror extension types, so there is nothing to regenerate). The problems are in the file-access implementation, the payload shape, and several contract claims the code does not honor.
The five I would not merge without: 1 (unbounded payload vs the WebSocket maxPayload cliff - it kills the whole C# connection, not just the request), 30 (a legal Linux filename becomes a real path traversal in the returned keys), 6 (symlinks out of the project are read and returned), 4 and 5 (two different mechanisms by which a project with interlinear data reports "no interlinear data" - silent data loss on import).
Three findings that could not be anchored inline
Their subject files are not in this diff, so GitHub has nowhere to attach them.
36. ParatextPublishedProjectDataProviderFactory.cs:9 is now false and was not updated. Its class doc states the factory "advertises every projectInterface the regular factory does EXCEPT legacyCommentManager.comments". With PT9_INTERLINEAR added to the unpublished-only list, that is no longer true, and a maintainer reading that file will reason incorrectly about what a published PDP can serve.
40. No advertisement assertions for PT9_INTERLINEAR. LocalParatextProjectsTests.cs:65,76,105 and ParatextPublishedProjectDataProviderFactoryTests.cs:24 all assert on ProjectInterfaces.LEGACY_COMMENT; there is no PT9 counterpart. Removing ProjectInterfaces.PT9_INTERLINEAR from s_paratextUnpublishedProjectInterfaces leaves the entire C# suite green.
45. No Architecture-Decisions.md entry. The last entry is still ADR-0015, and grep -rn "Pt9Interlinear" .context/ returns nothing. Root CLAUDE.md asks for a record when work surfaces a significant architecture decision, and this PR makes four: the first file-oriented projectInterface, poll-instead-of-events, advertising by project class, and placing PT9-legacy data under platformScripture.*. Three of those are choices a reasonable reviewer could decide either way, and the next PT9 import - hyphenation, renderings and spelling are all existing ProjectFileType categories - will have no recorded precedent to follow.
Measured numbers, stated once
The inline comments reference these rather than repeating them.
- JSON inflation: 1.65x, measured over the 38 real PT9 interlinear files on this machine (481,656 bytes of XML → 795,088 bytes serialized as
Pt9InterlinearFilerecords). That puts ws's 100 MBmaxPayloadcliff at roughly 61 MB of interlinear XML on disk for one project. - Local projects carry 6, 6, 4 and 2 gloss languages;
getPt9InterlinearFilesreturns every language and every book in one response. - Largest single real interlinear file: 300,127 bytes - past the large-object-heap threshold, and the manifest allocates one such array per file per poll.
Design judgment, not defects
15, 22, 29, 44, 46, 47 and 45 above are architecture and process calls rather than bugs. They are worth a deliberate decision - and writing that decision down is a fine resolution for any of them - but none is a "this is broken" claim.
Checked and clean, recorded so it is not re-litigated
All 6 PDP recipe steps present; C# interface constant and TS key match exactly; object? param = null and single-parameter Set*(object? value) match this file's own convention (GetTextCollectionOverlay, GetCellOrder, SetUserModelTexts); public sealed record matches CommentScriptureRange; FileShare.Read semantics match PT9's UserProjectFileManager; the Send/Receive write gate does not apply (read-only path); csharpier and prettier clean; PNX007 not violated; ProjectMetadata.ProjectInterfaces is recomputed on every enumeration, so no persisted-metadata migration is needed; the LocalParatextProjects watcher sets IncludeSubdirectories = false, so Interlinear_*/ never raises false events; the duplicate [TearDown] and the _scrText.Dispose()-before-ScrTextCollection.Remove ordering match the ParatextProjectDataProviderVersificationTests precedent this fixture was copied from; and the .NET 8 DOS 8.3 wildcard quirk does not apply, so .xmlbak and .xml.BAK are correctly excluded.
(AI-assisted, with my guidance)
| retVal.Add(("getFinalVerseNumbersInBook", GetFinalVerseNumbersInBook)); | ||
| retVal.Add(("setFinalVerseNumbersInBook", SetFinalVerseNumbersInBook)); | ||
|
|
||
| retVal.Add(("getPt9InterlinearManifest", GetPt9InterlinearManifest)); |
There was a problem hiding this comment.
2. The four PT9 wire methods are registered unconditionally, so published/resource PDPs expose an interface they do not advertise. 47 lines above, the comment methods are gated on ProjectDetails.Metadata.ProjectInterfaces.Contains(ProjectInterfaces.LEGACY_COMMENT) with the rationale that published PDPs "skip registration entirely instead of relying solely on per-method runtime guards." ParatextPublishedProjectDataProviderFactory builds this same class, so every resource PDP publishes getPt9InterlinearFiles in FunctionNames and in the OpenRPC document. pdpService.get('platformScripture.Pt9Interlinear', resourceId) correctly refuses, but the network-object proxy forwards any property access, so a PDP obtained via platformScripture.USFM_Book can call it.
Wrapping these four lines in the same Contains(ProjectInterfaces.PT9_INTERLINEAR) guard also removes the only path that reaches finding 3's shared-_Resources scan. Verified: with the guard added, a wire-surface test asserting a published PDP registers none of the four methods passes; without it, that test fails.
24. Neither getter carries [NetworkTimeout], so both inherit the 30 s default (PapiClient.cs:25). NetworkObject.GetTimeoutFromDelegate (NetworkObjects/NetworkObject.cs:105-121) reads the attribute, and the repo applies it to exactly this class of long-running operation: DblDownloadableDataProvider.cs:205,252,310 and ChecklistNetworkObject.cs:164. A cold multi-language read can exceed 30 s and time the caller out after C# has read, hashed, decoded and serialized everything.
|
|
||
| #endregion | ||
|
|
||
| #region PT9 Interlinear (platformScripture.Pt9Interlinear) |
There was a problem hiding this comment.
47. Placement. This 130-line region went into the largest C# file in the tree rather than a collaborator. Measured: ParatextProjectDataProvider.cs is 3056 lines at this commit; the next largest C# file in the repo is DblResourceWhiteList.cs at 1873. The repo's precedent for a self-contained file-access concern is a collaborator class (MarblePackage, the *BooksOrchestrator family).
This is the reason the new tests need a real temp directory: a collaborator taking the file source as a dependency could be exercised through the existing InMemoryFileManager seam (see findings 39/43). Design judgment, not a defect - flagging for the record.
|
|
||
| // The three project-root PT9 interlinear files. Per-language verse files live under | ||
| // Interlinear_{language}/ and are matched separately by pattern. | ||
| private static readonly string[] s_pt9InterlinearRootFileNames = |
There was a problem hiding this comment.
Three findings about what this list means to a caller.
17. Lexicon.xml and WordAnalyses.xml are PT9 ProjectFileType.Lexicon, not Interlinear, so a non-empty manifest does not mean "the project has interlinear data." Verified by running PT9's own classifier: ProjectFileClassifier.Get("Lexicon.xml", 0).FileType and …("WordAnalyses.xml", 0) both return ProjectFileType.Lexicon; only InterlinearSetup.xml and Interlinear_* files return Interlinear. A project whose team used the Wordlist/morphology analyzer but never opened the Interlinearizer returns two entries here. A consumer gating an "Import PT9 interlinear data" affordance on a non-empty manifest offers an import with nothing to import, then ships a multi-MB Lexicon.xml for nothing.
18. The inverse: a project using Settings.AssociatedLexicalProject (FLEx) has no project-local lexicon at all, and the import silently loses the sense/gloss targets. LexiconManager.GetAssociatedLexicon / GetAssociatedWordAnalyses (ParatextData/Linguistics/LexiconManager.cs:67-118) route both the lexicon and the word analyses to the external plugin when the associated project validates. Nothing in the payload distinguishes "no lexicon" from "the lexicon lives in FLEx".
20. InterlinearSetup.xml may legitimately not exist on disk. InterlinearSetups.ReadFromDisk (ParatextData/Interlinear/InterlinearSetup.cs:69-76) calls CreateDataFromSettings when !scrText.FileManager.Exists(fileName), rebuilding the setup list from the project's InterlinearRelatedLanguages.* settings (line 135). A PT7/8-era project has real interlinear setups that a file-only scan reports as absent.
PT9's classifier already draws the Interlinear/Lexicon line for you, case-insensitively - adopting it would let the payload label each file instead of conflating the two.
| /// Enumerates the project's PT9 interlinear files: whichever of the three root files are | ||
| /// present, plus every <c>Interlinear_{language}/Interlinear_{language}_{book}.xml</c>. Each | ||
| /// entry pairs the file's project-relative, forward-slash-normalized path with its absolute | ||
| /// path. Empty when the project carries no interlinear data. Both get methods build from this |
There was a problem hiding this comment.
22. The "cannot disagree" guarantee is scoped to one call, but the documented workflow is a two-call handshake. "Both get methods build from this one list, so they cannot disagree on the file set" holds only within a single call. The .d.ts prescribes probe-the-manifest-then-fetch, and across those two calls files can appear, vanish, or change with no snapshot and no generation token. The guarantee as written reads stronger than it is.
29. On the change path the caller pays the full read + SHA-256 bill twice, and the manifest's hashes are then discarded - Pt9InterlinearFile carries its own sha256, so nothing downstream uses the manifest values it already paid for.
Both dissolve if getPt9InterlinearFiles takes the paths to fetch (see finding 1). Design judgment on the shape; flagging the mismatch between the comment and the documented workflow.
| /// one list, so they cannot disagree on the file set or on the relative path each file is | ||
| /// keyed by. | ||
| /// </summary> | ||
| private List<(string RelativePath, string FullPath)> FindPt9InterlinearFiles() |
There was a problem hiding this comment.
9. The whole feature bypasses ScrText.FileManager. ProjectFileManager already provides Exists, ProjectDirectories(searchPattern), ProjectFiles, ReadAllBytes, ReadAllText - and its ProjectDirectories doc example is literally "Interlinear_*" (ParatextData/ProjectFileAccess/ProjectFileManager.cs:105-113). It is what PT9 itself uses for this exact enumeration: MigrateInterlinearData.cs:20 is scrText.FileManager.ProjectDirectories("Interlinear_*"). It is also the repo's own pattern: MarblePackage.cs:24-36, DeleteBooksOrchestrator.cs:77-79, CopyBooksOrchestrator.cs:430,1006, ImportBooksOrchestrator.cs:576.
Costs of bypassing it: zipped/resource projects are unreadable (ResourceProjectFileManager extends ZippedProjectFileManagerBase and reads from the .p8z); no FixCase; no participation in ProjectFileManager.FileChanged, which CheckCache.cs:39 already consumes and which would supply change events for free; and the InMemoryFileManager test seam is unusable - CopyBooksOrchestrator.cs:409 and :999 both cite that seam as the reason for routing through the FileManager.
One caveat worth knowing before you take this on: I measured it, and it only half fixes finding 4. Against a real UserProjectFileManager, Exists("Lexicon.xml") does find lexicon.xml on disk (FixCase corrects the final segment), but ProjectDirectories("Interlinear_*") and ProjectFiles("Interlinear_*_*.xml", …) both return [] for a lower-case interlinear_en/. The globs stay case-sensitive.
16. The scan is narrower than PT9's own definition of an interlinear file. ProjectFileClassifier.cs:439 classifies any project-relative path starting with interlinear_ and ending .xml, case-insensitively - which covers a root-level Interlinear_en_MAT.xml as well as nested ones. Verified by running it: ProjectFileClassifier.Get("Interlinear_en_MAT.xml", 0).FileType returns Interlinear. This scan requires a top-level Interlinear_*/ directory, so root-level interlinear XML is silently absent from both getters, and the API has no way to signal "I skipped files".
| * reads "interface unsupported" as "no PT9 interlinear import available for this project". | ||
| * | ||
| * Both data types are read-only: `set*` is unsupported and throws if called; the authoritative | ||
| * source is the files on disk, not this projectInterface. The interface raises no change events - |
There was a problem hiding this comment.
10. "The interface raises no change events - subscribe* emits at most the current value once" is false. ParatextProjectDataProvider.SendFullProjectUpdateEvent() (line 2431 of the PDP) emits SendDataUpdateEvent("*", …), and data-provider.service.ts:244 short-circuits its ignore check on updateEventResult !== '*' - so a wildcard update applies to every data type, this one included. It fires from ManageBooksService.cs:390, :698, :1052, and (per its own doc) after Send/Receive.
Consequence: every subscribePt9InterlinearFiles subscriber re-fetches the entire corpus after an unrelated book copy or delete. The subscriber path calls get${dataType}(selector) on each update (data-provider.service.ts:262-266), so it is N subscribers → N independent full re-reads, with nothing cached.
The same claim is repeated at lines 1249, 1298 and 1327. Either the doc needs to describe the wildcard, or the wildcard needs to exclude data types that have no events.
| * | ||
| * @experimental | ||
| */ | ||
| subscribePt9InterlinearFiles( |
There was a problem hiding this comment.
27. The default deeply-equal subscriber mode pins a second full copy of the payload per subscriber. data-provider.service.ts:211-214 defaults whichUpdates: 'deeply-equal' and retrieveDataImmediately: true; dataPrevious (line 279) holds the entire previous Pt9InterlinearFileSet alive for the subscription's lifetime. Combined with finding 10, merely subscribing pulls the whole corpus, and every unrelated wildcard update pulls it again and retains a second copy.
Worth a note in the options docs here, or a documented recommendation to pass whichUpdates: '*' for this data type. Bounding the payload (finding 1) bounds this too.
(Recorded so it is not re-raised: I initially flagged the Text-before-Sha256 field order as defeating deepEqual's short-circuit. That was wrong - fast-equals' areObjectsEqual iterates keys with a decrementing loop, so it already compares sha256 first. The current order is the favourable one; please don't "fix" it.)
| _homeDirectory = Directory.CreateTempSubdirectory("pt9Interlinear").FullName; | ||
| ProjectDetails projectDetails = | ||
| new(_scrText.Name, new ProjectMetadata(_scrText.Guid.ToString(), []), _homeDirectory); | ||
| ParatextProjects.FakeAddProject(projectDetails, _scrText); |
There was a problem hiding this comment.
39. This fixture cannot catch the registration or advertisement defects. It builds new ProjectMetadata(_scrText.Guid.ToString(), []) one line up - an empty projectInterfaces list - and calls the C# methods in-process on _provider, never through GetFunctions() / RegisterDataProviderAsync. Every test here passes identically whether or not registration is gated on the projectInterface (finding 2). ParatextProjectDataProviderWireSurfaceTests pins exactly this property for the comment methods; there is no PT9 analogue.
43. _scrText and this FakeAddProject call are inert ceremony. DummyLocalParatextProjects.FakeAddProject(ProjectDetails details, ScrText? scrText = null) does scrText ??= new DummyScrText(details); ScrTextCollection.Add(scrText, true); - when a ScrText is supplied, details is entirely unused. And the code under test never resolves the project from ScrTextCollection; it reads ProjectDetails.HomeDirectory directly.
Verified rather than argued: I deleted this line and re-ran the fixture - all 10 tests still pass. So it looks like it exercises a registered project and does not.
TestLocalParatextProjectsInTempDir.CreateTempProject(folder, projectDetails) already builds a real project directory where ScrText.Directory and ProjectDetails.HomeDirectory are the same path - which is exactly the invariant findings 3 and 21 turn on, and which this fixture's _homeDirectory (a fresh temp dir unrelated to the DummyScrText's ProjectPath) breaks. As written, nothing in CI can catch a HomeDirectory divergence, and the InMemoryFileManager seam can never intercept these reads.
| private void WriteProjectFile(string relativePath, string text) => | ||
| WriteProjectFile(relativePath, Encoding.UTF8.GetBytes(text)); | ||
|
|
||
| private static string Sha256Hex(byte[] bytes) => |
There was a problem hiding this comment.
41. This oracle mirrors the implementation, so it cannot fail. Sha256Hex is character-for-character identical to production ComputeSha256Hex (ParatextProjectDataProvider.cs:2670-2671): Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant().
The .d.ts promises sha256 is lowercase hex. If someone drops the .ToLowerInvariant() in production, this helper drops it too and every assertion still passes. Assert one hard-coded known-answer digest instead - e.g. the SHA-256 of <Lexicon/> - so the test pins the contract rather than restating the code.
| private static string Sha256Hex(byte[] bytes) => | ||
| Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(); | ||
|
|
||
| [Test] |
There was a problem hiding this comment.
42. Coverage gaps. Uncovered by this fixture, in rough order of how much they would have caught:
- case variants (
lexicon.xml,interlinear_en/) - I wrote this one and confirmed it fails on a Linux runner today, which is finding 4; - a file vanishing between the scan and the read (finding 11);
- non-UTF-8 / no-BOM content - relevant because
InterlinearSetup.xmlreally has no BOM on disk (finding 8); - a root-level
Interlinear_*.xml(finding 16); - a missing
HomeDirectory- the!Directory.Existsearly return atParatextProjectDataProvider.cs:2644is never exercised; - an unreadable file or directory (findings 5, 12, 13, 14);
- any file large enough for size to matter (finding 1).
49. The fixture drops the [Description] attribute that its direct sibling ParatextProjectDataProviderVersificationTests puts on all 11 of its tests (verified: 11 [Test], 11 [Description]; here, 10 and 0).
Code Review Summary
Branch: feature/pt9-interlinear-projectinterface
Base: origin/main
Date: 2026-08-21
Review model: Claude Fable 5
Files changed: 7
Overview
Adds a read-only
platformScripture.Pt9InterlinearprojectInterface to the Paratext project dataprovider so an interlinearizer extension can import a project's persisted Paratext 9 interlinear
files. It exposes two argument-less, read-only data types:
getPt9InterlinearManifestreturns a mapof project-relative file path to the lowercase SHA-256 hex of that file's raw bytes (a cheap
change-detection probe), and
getPt9InterlinearFilesreturns each file's raw text plus the samebyte hash so a caller can persist the fingerprint of exactly what it read. The provider scans only
the known PT9 filename patterns (
Lexicon.xml,WordAnalyses.xml,InterlinearSetup.xml, andInterlinear_{language}/Interlinear_{language}_{book}.xml) under the project's home directory, so nocaller-supplied string reaches file I/O. Set methods throw and the interface raises no change events;
the files on disk are the authoritative source. It is advertised on editable projects only, since
published (resource) projects are archives that carry no interlinear authoring data.
The change follows the existing read-only Versification projectInterface pattern closely and is
purely additive. It is C# backend plus one hand-authored TypeScript type-declaration file; no UI,
build config, localization, or shared template regions are touched.
Why review this
Part of interlinearizer epic (not yet created).
API Changes
New exports in
extensions/src/platform-scripture/src/types/platform-scripture.d.ts:Pt9InterlinearFile({ text: string; sha256: string })Pt9InterlinearFileSet({ [filePath: string]: Pt9InterlinearFile })Pt9InterlinearManifest({ [filePath: string]: string })Pt9InterlinearProjectInterfaceDataTypes(data typesPt9InterlinearManifestandPt9InterlinearFiles, both read-only viaDataProviderDataType<undefined, X, never>)IPt9InterlinearProjectDataProvider(extendsIProjectDataProvider<...>; addsget/set/subscribeforPt9InterlinearManifestandPt9InterlinearFiles)papi-shared-typesaugmentation:IPt9InterlinearProjectDataProvideradded to theplatform-scriptureimport list, and'platformScripture.Pt9Interlinear'added toProjectDataProviderInterfacesAll new type exports are tagged
@experimental. No@papi/module,lib/platform-bible-react/, orlib/platform-bible-utils/surface changed. The change is additive only: nothing removed, noexisting signature changed, nothing breaking.
Findings
Critical - Must address before merge
None.
Important - Should address before merge
None.
Minor - Consider
using System.Text.Json.Serialization;inc-sharp/Projects/ParatextProjectDataProvider.cs(the onlyJsonPropertyNamelives inPt9InterlinearFile.cs, which has its own using; IDE0005 would flag it) (fixed during review: removed the directive)PT9_INTERLINEAR_MANIFEST/PT9_INTERLINEAR_FILESinc-sharp/Projects/ProjectDataType.csare referenced nowhere (every other constant there is consumed by a change-event fan-out list; PT9 emits no events and its wire names come from the method-name literals) (fixed during review: kept the constants and added a comment noting they are reserved for parity with the other data-type constants and for possible future change-event use)s_paratextUnpublishedProjectInterfacesinc-sharp/Projects/LocalParatextProjects.cs(it said the unpublished list is "the published list plus the comment interface," but the list now also addsPT9_INTERLINEAR) (fixed during review: updated the comment to name both editable-only interfaces - legacy comments and PT9 interlinear - and note they exist only in editable projects)Punctuation inconsistency in(deliberate: this developer's personal rule is to write new/edited code ASCII-only and never rewrite pre-existing lines, so the new PT9 comments are ASCII and the pre-existing Versification em dashes are intentionally left untouched)c-sharp/Projects/ParatextProjectDataProvider.cs: the new PT9 doc comments use ASCII hyphens while adjacent Versification docs still use em dashesThe "throws on an unreadable file, never a partial result" contract has no dedicated test(acknowledged: not portably reproducible across Windows/Linux CI - it depends on file locking - and it is not a branch of our own code, just the natural throw ofFile.ReadAllBytes; the compliance analysis agreed it is reasonable to leave untested)[Author response: Author accepted the three fixes (M1-M3) and directed that M2's constants be kept with a reserved-for-parity-and-future-events comment rather than deleted. Author confirmed M4 and M5 as deliberate dismissals.]
Template Propagation
Shared Regions Modified
None. No
#region shared with <url>markers exist in any changed file. (The#region PT9 Interlinearin C# and#region Pt9 Interlinear Typesin the.d.tsare plain section markers, not propagation markers.)Extension Config Changes
extensions/src/platform-scripture/src/types/platform-scripture.d.ts- a type-declaration source file, not a config/build file; no propagation needed.Positive Observations
read-only message constant,
Set*methods throwingNotSupportedException,ExperimentalMethodDocumentationentries, and@experimentalJSDoc tags withset*(newValue: never)on the TS side, which encodes read-only at the type level (setters are uncallable).FindPt9InterlinearFilesis a single source of truth for both getters, with a comment explainingthat this is what prevents the manifest and file set from disagreeing on the file list or the
forward-slash-normalized keys - a clean DRY choice.
s_paratextUnpublishedProjectInterfaces=published list +
LEGACY_COMMENT+PT9_INTERLINEAR), matching the stated purpose and followingthe existing
LEGACY_COMMENTprecedent.DecodeFileTextstrips any BOM,so the fingerprint is decoding-independent, and a single
getPt9InterlinearFilesread pairs eachfile's
textandsha256from the same byte buffer.cases: empty project, all three root files plus per-language/per-book files, forward-slash keys,
pattern filtering (excludes
Settings.xmland non-matching files), raw-text-plus-hash shape,manifest/files hash consistency, BOM stripping vs. raw-byte hashing, and both
Set*throw paths.and all cross-module types are already imported and reachable by extension consumers.
Interview Notes
Stated purpose: expose a Paratext project's persisted PT9 interlinear files (a hash manifest for
change detection, and the raw file contents with hashes) read-only, so an interlinearizer extension
can import legacy interlinear data. This is the first slice (PR A) of a larger PT9 import effort.
Key decisions the author confirmed during review:
string reaches file I/O; the manifest already lets a caller skip the fetch when nothing changed.
Send/Receive, and restore; the author accepted that this reads every file per manifest probe.
bytes imported.
.p8z/.xml1zarchives thedirectory scan cannot read and which carry no interlinear authoring data.
M2 decision: the author chose to keep the two unused
ProjectDataTypeconstants (rather than deletethem) with a comment reserving them for parity with the other data-type constants and for possible
future change-event use.
No unresolved items. The author demonstrated full understanding of the design; nothing was deferred
to AI without explanation.
In-Review Quality Check
Three fixes were made during the review (M1-M3), all C#-only (a removed
usingand two commentedits). The quality check was scoped to C# accordingly, since these edits cannot affect the
TypeScript typecheck/lint/vitest suites:
--checkon the three edited files: clean.ParanextDataProvider.csproj): succeeded, 0 errors - confirms removing the unusedusingis safe.c-sharp-testssuite): 1590 passed, 0 failed, 6 skipped. (Console noise about acorrupt sample "Enhanced Resources" file is a pre-existing test-environment condition unrelated to
these changes.)
No unfixable failures.
Suggested Review Focus
right surface, versus a selector-based fetch.
each manifest probe (versus modified-time, which is cheaper but unreliable across copy/restore).
ProjectDataTypeconstants (reserved-for-paritycomment) rather than delete them.
excluded.
AI-assisted
This change is