Skip to content

feat: versioned JobManager for reading/writing jobs across protocol versions#9434

Merged
koenvanderveen merged 37 commits into
devfrom
koen/job-reader-writer
Jul 14, 2026
Merged

feat: versioned JobManager for reading/writing jobs across protocol versions#9434
koenvanderveen merged 37 commits into
devfrom
koen/job-reader-writer

Conversation

@koenvanderveen

@koenvanderveen koenvanderveen commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator
  • Versioned JobManager (nested in JobClient) owns all JobState/JobSubmissionMetadata filesystem IO: reads upgrade objects in memory to the latest registered version, writes downgrade them to the version/layout the reading peer understands. JobClient, JobInfo, SyftJobRunner, the syft-bg job monitor, and the enclave client all route through it.
  • On-disk protocol version: new jobs live under inbox/<ds_email>/v1/<job_name>/ (same for review/). Protocol 0 = last release (0.1.38): no version segment, no canonical_name/version fields in yaml; protocol-0 jobs are read, listed, and written back byte-compatibly with 0.1.38.
  • Per-peer protocol negotiation: JobClient accepts optional peer_schemas (peer email → job ProtocolSchema); the version spoken to a peer is the min of ours and theirs, so an older peer receives the older layout/format. Unknown peers default to the current protocol.
  • syft-migration: MigrationRegistry gains a protocol_version; released schemas are frozen as ReleasedProtocol / ReleasedPackageProtocolInfo artifacts (save/load, registered on import) with drift detection that fails if a released object schema changes without a new version. A hardcoded 0.1.38 artifact ships in syft_job/migrations/history/ as if that release had emitted it.
  • JobInfo identity from the path, not config.yaml: datasite_owner_email, submitted_by, and name now come from the path-derived JobRef (governed by syft permissions), never from the DS-writable config.yaml — closing a spoofing vector where a DS could claim a fake identity. ref is now required; current_user_email stays sourced from the client config.
  • Release tooling (packages/syft-job/scripts/): export_release_artifact.py writes the JSON schema artifacts, and the new generate_release_fixture.py snapshots a full SyftBox tree of exactly how the release serializes jobs to disk. Both are run on every release and documented in CLAUDE.md / README.md.
  • Cross-release on-disk fixtures: fixtures named syft_job-<version>-protocol<p>_syftbox (0.1.38/protocol 0 hand-authored, 0.1.39/protocol 1 generated). test_older_protocol_compatibility.py loops over all of them — keyed on release, not just protocol, since a release can change how it writes bytes without bumping the protocol — asserting load/upgrade plus byte-exact scan/approve write-back per protocol.

Test plan: syft-migration + syft-job migration/p2p suites (list/upgrade, scan, approve write-back byte-match, mixed listing, reserved v<n> job names, per-protocol peer submit, perms on the v1 subfolder, repr smoke test, and an identity-from-path spoofing test) plus root tests/unit; all suites green, pre-commit clean, history JSON verified in the wheel.

koenvanderveen and others added 8 commits July 7, 2026 18:54
Loop the p2p compatibility test over every released on-disk fixture
(keyed on release, e.g. syft_job-0.1.38-protocol0_syftbox) instead of a
single protocol, asserting jobs load/upgrade and that scan/approve write
back byte-exact in each release's layout/format. Also check job reprs
render without error.

Add scripts/generate_release_fixture.py to snapshot the current release's
SyftBox tree (run on every release) and move export_release_artifact.py
alongside it in scripts/. Document both in CLAUDE.md and README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
datasite_owner_email, submitted_by and name now read from the JobRef
(parsed from the folder layout, governed by syft permissions) instead of
the DS-writable config.yaml, so a data scientist can't spoof them.
Make ref required and drop the metadata-based fallback; current_user_email
stays a stored arg from the client config. Add a test proving a tampered
config.yaml name is ignored in favor of the path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…efactor

Pass the path-derived JobRef and drop the removed datasite_owner_email kwarg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@pjwerneck pjwerneck left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments mark some minor issues found in the code.

The main high-level design issue would be having a separate ProtocolCodec class, to avoid bundling all protocol rules in the _load_upgraded and _write_in_target_version methods, as suggested in the comments.

Comment thread packages/syft-job/src/syft_job/job_storage.py
Comment thread packages/syft-job/src/syft_job/manager.py Outdated
Comment thread packages/syft-job/src/syft_job/job_storage.py
Comment thread packages/syft-job/src/syft_job/client.py Outdated
Comment thread packages/syft-job/src/syft_job/client.py Outdated
Comment thread packages/syft-job/src/syft_job/job_runner.py
return path

# -- internals -----------------------------------------------------------------
def _load_upgraded(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both _load_upgraded and _write_in_target_version work for only two versions, but are somewhat fragile when it comes to handling more future versions, and specially once we need to upgrade or downgrade jobs throughout multiple versions.

Instead of conditionals I would introduce a ProtocolCodec class that handles the serialization and loading for each version, and JobManager delegates to the appropriate class, keeping all migration specific code in the codec class. Once v2 is introduced, it's just a new class, instead of more conditionals.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes I have been thinking about this but didnt find a better solution than JobManager. The idea of JobManager was that its only responsibiliy was reading and writing jobs in the right version. Perhaps that didnt really succeed 100% since we have

negotiated_protocol_version_for_peer
_get_write_target_schema
new_submission_ref

but we could move these to JobClient if we want to. So I do like the solution but then I guess JobManager would just be a class that finds the right codec and passes it through. Which is fine I think. One thing that crossed my mind is that perhaps it wouldnt be amazing to have to create a new ProtocolCodec every time we make a change to a Job object, but I guess we can have a SIngle codex handle multiple job versions

Comment thread packages/syft-job/src/syft_job/manager.py Outdated
Comment thread packages/syft-job/src/syft_job/job_storage.py
Comment thread packages/syft-bg/src/syft_bg/notify/monitors/job.py Outdated
Rename the JobManager class to JobStorage across syft-job and syft-bg.
read_state now raises JobStateNotFoundError (a FileNotFoundError subclass)
instead of returning None, with callers updated to catch it. Also propagate
job_name into read_submission and fix reset_all_jobs job counting.
…istration

Derive the protocol-version -> codec map on access from self.codecs rather
than caching it in __init__, and add a test asserting every concrete
ProtocolCodec subclass in the package is registered in JobStorage/CODECS.
@koenvanderveen koenvanderveen merged commit 55a1dbc into dev Jul 14, 2026
17 of 18 checks passed
@koenvanderveen koenvanderveen deleted the koen/job-reader-writer branch July 14, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants