scivetra 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: scivetra
3
+ Version: 0.1.0a1
4
+ Summary: Deterministic, read-only inventory of local PDF files
5
+ Author: tvquynh
6
+ Project-URL: Source, https://github.com/tvquynh/scivetra
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ # SciVetra bootstrap
14
+
15
+ SciVetra 0.1.0a1 provides a small, standard-library-only local PDF inventory utility.
16
+ It lists relative filenames, byte sizes, and SHA-256 digests as deterministic JSON.
17
+ It does not parse or execute PDFs, contact a network service, or upload files.
18
+
19
+ ```console
20
+ scivetra --version
21
+ scivetra pdf-inventory ./my-local-fixtures
22
+ scivetra pdf-inventory ./my-local-fixtures --output inventory.json
23
+ ```
24
+
25
+ Choose only a local directory you are authorized to inspect. No location is scanned
26
+ by default. Keep the selected tree stable while scanning. Symlinks and Windows
27
+ reparse points are skipped; linked directory ancestors and Windows network drives
28
+ are rejected. On POSIX, use a local filesystem, not a mounted network filesystem.
29
+ Limits: 100,000 filesystem entries, 1 GB per PDF and 10 GB total PDF bytes.
30
+ Unchanged trees produce the same filename-sorted JSON. Errors exit with status 2;
31
+ no incomplete JSON is emitted. Output files must be new `.json` files; existing
32
+ files are never overwritten. This utility is not a sandbox against adversarial
33
+ concurrent filesystem changes.
34
+
35
+ This independent bootstrap does not implement SLR, RAG, AI, Crossref integration,
36
+ PDF downloading, or the future SciVetra research framework. Framework architecture
37
+ and subsequent implementation remain with the primary implementation engineer.
38
+ No software license has been selected or asserted for this bootstrap.
39
+
40
+ ## Development
41
+
42
+ ```console
43
+ python -m pip install -e .
44
+ python -m unittest discover -s tests -v
45
+ python -m pip install build twine
46
+ python -m build
47
+ python -m twine check dist/*
48
+ ```
49
+
50
+ ## One-time initial publication
51
+
52
+ The manual `publish.yml` workflow accepts exactly `scivetra==0.1.0a1` as its
53
+ confirmation and runs only on `main`. Tests, wheel/sdist build, metadata validation,
54
+ and a fresh wheel CLI smoke test must pass before a separate OIDC publishing job.
55
+ Configure the GitHub `pypi` environment with required reviewer `tvquynh` and only
56
+ `main` allowed to deploy. PyPI's pending GitHub publisher must match owner
57
+ `tvquynh`, repository `scivetra`, workflow `publish.yml`, environment `pypi`.
58
+ Use no API token. Do not rerun an uncertain upload: check production PyPI and
59
+ Actions first. A pending publisher does not reserve the name.
60
+
61
+ First run the workflow with Publish unchecked (checks only). After inspecting the
62
+ successful checks, run once with Publish checked and the exact confirmation, then
63
+ approve the protected pypi deployment. A separate clean environment verifies the
64
+ production installation after upload. If upload succeeds but verification fails,
65
+ do not publish again; investigate the existing production release.
@@ -0,0 +1,53 @@
1
+ # SciVetra bootstrap
2
+
3
+ SciVetra 0.1.0a1 provides a small, standard-library-only local PDF inventory utility.
4
+ It lists relative filenames, byte sizes, and SHA-256 digests as deterministic JSON.
5
+ It does not parse or execute PDFs, contact a network service, or upload files.
6
+
7
+ ```console
8
+ scivetra --version
9
+ scivetra pdf-inventory ./my-local-fixtures
10
+ scivetra pdf-inventory ./my-local-fixtures --output inventory.json
11
+ ```
12
+
13
+ Choose only a local directory you are authorized to inspect. No location is scanned
14
+ by default. Keep the selected tree stable while scanning. Symlinks and Windows
15
+ reparse points are skipped; linked directory ancestors and Windows network drives
16
+ are rejected. On POSIX, use a local filesystem, not a mounted network filesystem.
17
+ Limits: 100,000 filesystem entries, 1 GB per PDF and 10 GB total PDF bytes.
18
+ Unchanged trees produce the same filename-sorted JSON. Errors exit with status 2;
19
+ no incomplete JSON is emitted. Output files must be new `.json` files; existing
20
+ files are never overwritten. This utility is not a sandbox against adversarial
21
+ concurrent filesystem changes.
22
+
23
+ This independent bootstrap does not implement SLR, RAG, AI, Crossref integration,
24
+ PDF downloading, or the future SciVetra research framework. Framework architecture
25
+ and subsequent implementation remain with the primary implementation engineer.
26
+ No software license has been selected or asserted for this bootstrap.
27
+
28
+ ## Development
29
+
30
+ ```console
31
+ python -m pip install -e .
32
+ python -m unittest discover -s tests -v
33
+ python -m pip install build twine
34
+ python -m build
35
+ python -m twine check dist/*
36
+ ```
37
+
38
+ ## One-time initial publication
39
+
40
+ The manual `publish.yml` workflow accepts exactly `scivetra==0.1.0a1` as its
41
+ confirmation and runs only on `main`. Tests, wheel/sdist build, metadata validation,
42
+ and a fresh wheel CLI smoke test must pass before a separate OIDC publishing job.
43
+ Configure the GitHub `pypi` environment with required reviewer `tvquynh` and only
44
+ `main` allowed to deploy. PyPI's pending GitHub publisher must match owner
45
+ `tvquynh`, repository `scivetra`, workflow `publish.yml`, environment `pypi`.
46
+ Use no API token. Do not rerun an uncertain upload: check production PyPI and
47
+ Actions first. A pending publisher does not reserve the name.
48
+
49
+ First run the workflow with Publish unchecked (checks only). After inspecting the
50
+ successful checks, run once with Publish checked and the exact confirmation, then
51
+ approve the protected pypi deployment. A separate clean environment verifies the
52
+ production installation after upload. If upload succeeds but verification fails,
53
+ do not publish again; investigate the existing production release.
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "scivetra"
7
+ version = "0.1.0a1"
8
+ description = "Deterministic, read-only inventory of local PDF files"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{name = "tvquynh"}]
12
+ dependencies = []
13
+ classifiers = ["Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Operating System :: OS Independent"]
14
+
15
+ [project.urls]
16
+ Source = "https://github.com/tvquynh/scivetra"
17
+
18
+ [project.scripts]
19
+ scivetra = "scivetra.cli:main"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ """SciVetra's independent local file inventory bootstrap."""
2
+ __version__ = "0.1.0a1"
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ raise SystemExit(main())
@@ -0,0 +1,92 @@
1
+ """Inventory only explicitly selected, stable local directories."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import stat
8
+ import sys
9
+
10
+ from . import __version__
11
+
12
+ MAX_ENTRIES = 100_000
13
+ MAX_FILE_BYTES = 1_000_000_000
14
+ MAX_TOTAL_BYTES = 10_000_000_000
15
+
16
+
17
+ def linked(info):
18
+ return stat.S_ISLNK(info.st_mode) or bool(getattr(info, "st_file_attributes", 0) & 0x400)
19
+
20
+
21
+ def inventory(directory):
22
+ root = Path(os.path.abspath(directory))
23
+ if str(root).startswith(("\\\\", "//")):
24
+ raise ValueError("Network paths are unsupported")
25
+ if os.name == "nt":
26
+ import ctypes
27
+ if ctypes.windll.kernel32.GetDriveTypeW(str(root.anchor)) != 3:
28
+ raise ValueError("Choose a local fixed drive")
29
+ for part in (*reversed(root.parents), root):
30
+ if linked(part.lstat()):
31
+ raise ValueError("Directory ancestors must not be symlinks or reparse points")
32
+ if not root.is_dir():
33
+ raise ValueError("Expected a directory")
34
+ rows, pending = [], [root]
35
+ entries = total = 0
36
+ while pending:
37
+ current = pending.pop()
38
+ with os.scandir(current) as children:
39
+ for child in children:
40
+ entries += 1
41
+ if entries > MAX_ENTRIES:
42
+ raise ValueError("Entry limit exceeded")
43
+ info = os.stat(child.path, follow_symlinks=False)
44
+ if linked(info):
45
+ continue
46
+ if stat.S_ISDIR(info.st_mode):
47
+ pending.append(Path(child.path))
48
+ elif stat.S_ISREG(info.st_mode) and child.name.lower().endswith(".pdf"):
49
+ if info.st_size > MAX_FILE_BYTES or total + info.st_size > MAX_TOTAL_BYTES:
50
+ raise ValueError("PDF byte limit exceeded")
51
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
52
+ fd = os.open(child.path, flags)
53
+ with os.fdopen(fd, "rb") as stream:
54
+ before = os.fstat(stream.fileno())
55
+ if linked(before) or not stat.S_ISREG(before.st_mode) or (before.st_dev, before.st_ino) != (info.st_dev, info.st_ino):
56
+ raise ValueError("File changed during inventory")
57
+ digest = hashlib.sha256()
58
+ count = 0
59
+ while chunk := stream.read(1024 * 1024):
60
+ count += len(chunk)
61
+ if count > MAX_FILE_BYTES or total + count > MAX_TOTAL_BYTES:
62
+ raise ValueError("PDF byte limit exceeded")
63
+ digest.update(chunk)
64
+ after = os.fstat(stream.fileno())
65
+ if count != info.st_size or (after.st_size, after.st_mtime_ns) != (info.st_size, info.st_mtime_ns):
66
+ raise ValueError("File changed during inventory")
67
+ total += count
68
+ rows.append({"filename": Path(child.path).relative_to(root).as_posix(), "size_bytes": count, "sha256": digest.hexdigest()})
69
+ return sorted(rows, key=lambda row: row["filename"])
70
+
71
+
72
+ def main(argv=None):
73
+ parser = argparse.ArgumentParser(description="Read-only inventory of explicitly selected local PDFs; no parsing or network access.")
74
+ parser.add_argument("--version", action="version", version=f"scivetra {__version__}")
75
+ commands = parser.add_subparsers(dest="command", required=True)
76
+ pdf = commands.add_parser("pdf-inventory")
77
+ pdf.add_argument("directory")
78
+ pdf.add_argument("--output", type=Path)
79
+ args = parser.parse_args(argv)
80
+ try:
81
+ result = json.dumps(inventory(args.directory), ensure_ascii=True, indent=2) + "\n"
82
+ if args.output:
83
+ if args.output.suffix.lower() != ".json":
84
+ raise ValueError("Output must use a .json extension")
85
+ # Exclusive creation prevents overwriting any user file or symlink target.
86
+ with args.output.open("x", encoding="utf-8", newline="\n") as output:
87
+ output.write(result)
88
+ else:
89
+ sys.stdout.write(result)
90
+ except (OSError, ValueError) as error:
91
+ parser.exit(2, f"scivetra: {error}\n")
92
+ return 0
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: scivetra
3
+ Version: 0.1.0a1
4
+ Summary: Deterministic, read-only inventory of local PDF files
5
+ Author: tvquynh
6
+ Project-URL: Source, https://github.com/tvquynh/scivetra
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ # SciVetra bootstrap
14
+
15
+ SciVetra 0.1.0a1 provides a small, standard-library-only local PDF inventory utility.
16
+ It lists relative filenames, byte sizes, and SHA-256 digests as deterministic JSON.
17
+ It does not parse or execute PDFs, contact a network service, or upload files.
18
+
19
+ ```console
20
+ scivetra --version
21
+ scivetra pdf-inventory ./my-local-fixtures
22
+ scivetra pdf-inventory ./my-local-fixtures --output inventory.json
23
+ ```
24
+
25
+ Choose only a local directory you are authorized to inspect. No location is scanned
26
+ by default. Keep the selected tree stable while scanning. Symlinks and Windows
27
+ reparse points are skipped; linked directory ancestors and Windows network drives
28
+ are rejected. On POSIX, use a local filesystem, not a mounted network filesystem.
29
+ Limits: 100,000 filesystem entries, 1 GB per PDF and 10 GB total PDF bytes.
30
+ Unchanged trees produce the same filename-sorted JSON. Errors exit with status 2;
31
+ no incomplete JSON is emitted. Output files must be new `.json` files; existing
32
+ files are never overwritten. This utility is not a sandbox against adversarial
33
+ concurrent filesystem changes.
34
+
35
+ This independent bootstrap does not implement SLR, RAG, AI, Crossref integration,
36
+ PDF downloading, or the future SciVetra research framework. Framework architecture
37
+ and subsequent implementation remain with the primary implementation engineer.
38
+ No software license has been selected or asserted for this bootstrap.
39
+
40
+ ## Development
41
+
42
+ ```console
43
+ python -m pip install -e .
44
+ python -m unittest discover -s tests -v
45
+ python -m pip install build twine
46
+ python -m build
47
+ python -m twine check dist/*
48
+ ```
49
+
50
+ ## One-time initial publication
51
+
52
+ The manual `publish.yml` workflow accepts exactly `scivetra==0.1.0a1` as its
53
+ confirmation and runs only on `main`. Tests, wheel/sdist build, metadata validation,
54
+ and a fresh wheel CLI smoke test must pass before a separate OIDC publishing job.
55
+ Configure the GitHub `pypi` environment with required reviewer `tvquynh` and only
56
+ `main` allowed to deploy. PyPI's pending GitHub publisher must match owner
57
+ `tvquynh`, repository `scivetra`, workflow `publish.yml`, environment `pypi`.
58
+ Use no API token. Do not rerun an uncertain upload: check production PyPI and
59
+ Actions first. A pending publisher does not reserve the name.
60
+
61
+ First run the workflow with Publish unchecked (checks only). After inspecting the
62
+ successful checks, run once with Publish checked and the exact confirmation, then
63
+ approve the protected pypi deployment. A separate clean environment verifies the
64
+ production installation after upload. If upload succeeds but verification fails,
65
+ do not publish again; investigate the existing production release.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/scivetra/__init__.py
4
+ src/scivetra/__main__.py
5
+ src/scivetra/cli.py
6
+ src/scivetra.egg-info/PKG-INFO
7
+ src/scivetra.egg-info/SOURCES.txt
8
+ src/scivetra.egg-info/dependency_links.txt
9
+ src/scivetra.egg-info/entry_points.txt
10
+ src/scivetra.egg-info/top_level.txt
11
+ tests/test_inventory.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ scivetra = scivetra.cli:main
@@ -0,0 +1 @@
1
+ scivetra
@@ -0,0 +1,62 @@
1
+ import contextlib
2
+ import hashlib
3
+ import io
4
+ import json
5
+ from pathlib import Path
6
+ import tempfile
7
+ import unittest
8
+ from unittest.mock import patch
9
+ from scivetra.cli import inventory, main
10
+
11
+ class InventoryTests(unittest.TestCase):
12
+ def test_deterministic_nested_inventory_without_parsing(self):
13
+ with tempfile.TemporaryDirectory() as directory:
14
+ root = Path(directory)
15
+ (root / "sub").mkdir()
16
+ (root / "sub" / "b.PDF").write_bytes(b"not a parsed PDF")
17
+ (root / "a.pdf").write_bytes(b"abc")
18
+ (root / "ignore.txt").write_bytes(b"ignored")
19
+ expected = [{"filename":"a.pdf", "size_bytes":3, "sha256":hashlib.sha256(b"abc").hexdigest()}, {"filename":"sub/b.PDF", "size_bytes":16, "sha256":hashlib.sha256(b"not a parsed PDF").hexdigest()}]
20
+ self.assertEqual(inventory(root), expected)
21
+ self.assertEqual(inventory(root), expected)
22
+ capture = io.StringIO()
23
+ with contextlib.redirect_stdout(capture):
24
+ self.assertEqual(main(["pdf-inventory", directory]), 0)
25
+ self.assertEqual(json.loads(capture.getvalue()), expected)
26
+ output = root / "inventory.json"
27
+ main(["pdf-inventory", directory, "--output", str(output)])
28
+ original = output.read_bytes()
29
+ with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
30
+ main(["pdf-inventory", directory, "--output", str(output)])
31
+ self.assertEqual(output.read_bytes(), original)
32
+ self.assertEqual((root / "a.pdf").read_bytes(), b"abc")
33
+
34
+ def test_limits_fail_closed(self):
35
+ with tempfile.TemporaryDirectory() as directory:
36
+ (Path(directory)/"a.pdf").write_bytes(b"abc")
37
+ with patch("scivetra.cli.MAX_FILE_BYTES", 2), self.assertRaises(ValueError):
38
+ inventory(directory)
39
+ with patch("scivetra.cli.MAX_ENTRIES", 0), self.assertRaises(ValueError):
40
+ inventory(directory)
41
+
42
+ def test_symlinks_are_not_followed(self):
43
+ with tempfile.TemporaryDirectory() as directory, tempfile.TemporaryDirectory() as outside:
44
+ root = Path(directory)
45
+ (Path(outside)/"private.pdf").write_bytes(b"outside")
46
+ try:
47
+ (root/"linked").symlink_to(outside, target_is_directory=True)
48
+ (root/"linked.pdf").symlink_to(Path(outside)/"private.pdf")
49
+ except OSError:
50
+ self.skipTest("Symlink creation unavailable; run this test on Linux CI")
51
+ self.assertEqual(inventory(root), [])
52
+ with self.assertRaises(ValueError):
53
+ inventory(root/"linked")
54
+
55
+ def test_invalid_and_empty_directories(self):
56
+ with tempfile.TemporaryDirectory() as directory:
57
+ self.assertEqual(inventory(directory), [])
58
+ with self.assertRaises(OSError):
59
+ inventory(Path(directory)/"missing")
60
+
61
+ if __name__ == "__main__":
62
+ unittest.main()