Date: 2026-07-13
Foundation: PR #2313
moved transfer-source retrieval for auto-approved package creation into the
normal workflow and is present on qa/1.x.
Archivematica currently starts several kinds of transfer by moving content into
activeTransfers and waiting for MCPServer to notice it. This makes the
filesystem location serve both as content storage and as the command to begin
processing. It also couples submission APIs to internal paths and creates an
unclear boundary between "the content was copied" and "the Transfer is ready."
This proposal makes transfer submission explicit. MCPServer will create the
Transfer first, arrange for its content to be copied into a non-watched
submittedTransfers location, and start the appropriate workflow directly.
The filesystem will continue holding transfer content, but the appearance of a
path will no longer create or initiate a supported submission.
This builds directly on PR #2313. That change moved retrieval for auto-approved API transfers out of MCPServer bootstrap workers and into normal workflow tasks performed by MCPClient. It made slow, queued, and failed retrieval visible in the workflow and reduced the chance that a slow Storage Service copy could occupy MCPServer workers. It intentionally left non-auto-approved submission on the watched-directory path. This proposal applies the same explicit handoff to the remaining supported submission paths and removes that architectural split.
Existing public APIs remain available. Their request fields, response shapes,
status values, approval outcomes, accession information, and metadata
associations are preserved. In particular, the existing Automation Tools v1
sequence remains supported without client code changes: start a transfer,
modify it with pre-transfer hooks, find it through /unapproved, approve it by
directory and type, and poll its status.
Some incidental filesystem behavior will change. Returned paths and approval
directory values will use submittedTransfers and include the Transfer UUID.
Direct drops into activeTransfers, collision-padding names, and watcher-based
restart rediscovery will no longer be supported submission behavior.
This is a focused submission change, not a replacement of Archivematica's shared filesystem, workflow engine, or queues. Durable retries and restart reconstruction remain possible future improvements.
Archivematica needs a shared filesystem to hold transfer content. The problem is not that files live on a filesystem; it is that the arrival of files currently also acts as the command to create or start a Transfer.
Submission intent should be explicit. A caller should ask Archivematica to create a Transfer, and Archivematica should then arrange storage and processing for that known Transfer. The filesystem should hold its content without also being the message that processing should begin.
Issue #1803 documented a specific operational gap: Storage Service retrieval happened before the first normal workflow Job, so operators could not reliably see whether retrieval was queued, running, failed, or complete. Slow or blocked storage I/O could make a Transfer appear idle while tying up an MCPServer bootstrap worker.
PR #2313 addressed that gap for auto-approved package creation. MCPServer now plans retrieval and schedules it; MCPClient performs the Storage Service copy as normal workflow work. The same change records early failures and represents a Transfer waiting for its first Job instead of reporting it as unknown.
That work was also connected to concrete production reports:
- Issue #1790 showed that
a Storage Service or startup failure could leave an API Transfer permanently
in
UNKNOWN; PR #2313 was considered to address that failure path. - Issue #1791 described MCPServer stalling under sustained parallel API submission, requiring manual restarts and leaving newly submitted Transfers unprocessed. Moving retrieval out of MCPServer was expected to reduce that pressure, but the issue called for further load testing.
- Issue #1807 described MCPClient workers that could remain alive without processing Gearman work. PR #2313 changes the load and failure path through those components, but it was not established as a complete fix for worker readiness.
PR #2313 deliberately retained the active-transfer watched-directory path for non-auto-approved packages. As a result, Archivematica currently has a stronger, observable retrieval model for one API mode while other supported submissions still depend on filesystem discovery. This proposal is an important follow-up: it reuses that workflow foundation for both package approval modes, the legacy API, SWORD, and transfer-target reingest, then ends filesystem discovery as a supported submission mechanism.
This proposal should not be read as claiming to solve every MCPServer, MCPClient, or Gearman stall described in the related issues. Its contribution is narrower: all supported submissions receive an explicit Transfer identity and a defined handoff into processing; transfer-source retrieval and early failures are represented in the workflow instead of relying on an indirect watcher event.
An activeTransfers path currently represents several things at once:
- the submitted content;
- a request to start processing;
- the transfer type;
- part of the Transfer's identity;
- the workflow starting point.
As a result, directory names, parent directories, archive detection, trailing separators, and collision padding become part of submission behavior. A storage layout has effectively become an implicit API and data model.
Moving content into activeTransfers and starting its workflow are separate
events. A successful move does not acknowledge that MCPServer has observed the
path, selected the workflow, or created the approval Job.
The Automation Tools v1 client demonstrates this boundary. After
start_transfer returns, it sleeps and retries while waiting for the same
Transfer to appear in /unapproved. The client is compensating for a hidden
asynchronous gap between "the files were copied" and "the Transfer is ready for
approval."
An explicit submission command provides a clearer contract: the system knows which Transfer is being created, and the legacy endpoint can wait for a defined readiness condition before returning success.
The watched-directory implementation supports two different mechanisms:
- polling, which repeatedly scans every watched directory and can delay discovery until the next interval;
inotify, which is efficient on Linux but may not work as expected with some shared or network filesystems, including NFS.
The current mechanism has worked for many deployments, so this proposal does not assume that watchers always lose events. The concern is that submission coordination and timing depend on operating-system and filesystem behavior and do not have an explicit acknowledgement boundary.
The active-transfer watcher can create a Transfer when it observes a path with no matching database row. Any actor able to write into the directory can therefore initiate processing without using a supported submission endpoint.
That makes it harder to apply request validation, identify the initiating caller, reject unsupported input early, and distinguish deliberate submissions from stray or abandoned content.
The legacy /unapproved endpoint derives the transfer type and approval
selector by parsing a workflow Job path. /approve reverses the process and
reconstructs a watched path from the caller's directory and type.
This spreads one public operation across Dashboard copying code, MCPServer watchers, workflow watched-directory definitions, filesystem naming rules, and approval-path parsing. A change to an internal directory convention can affect API behavior in another component.
Auto-approved package creation already creates a Transfer explicitly and schedules transfer-source retrieval as workflow work. Non-auto-approved package creation, the legacy API, SWORD, and transfer-target reingest still use the active-transfer watcher.
Maintaining both models requires contributors to understand two different ways that a Transfer can come into existence and begin processing. Converging on explicit creation gives supported submission paths the same ownership and starting model.
Database creation, content copying, the final move, watcher observation, and workflow scheduling are separate events. When one step fails, operators and developers may need to correlate a database row, a filesystem path, watcher logs, and Jobs to determine how far submission progressed.
This proposal does not make the queues durable or provide exactly-once execution. It does make the Transfer UUID the correlation point, defines when the materialized path becomes authoritative, and records failures against a known Transfer instead of relying on later filesystem discovery.
This is not a general removal of shared storage or watched directories from Archivematica. Existing workflows continue using the shared filesystem, and unrelated watched-directory transitions remain unchanged.
The active-transfer watcher also provides some incidental restart rediscovery and supports direct filesystem drops. This proposal deliberately stops relying on those behaviors for submission. Durable dispatch, restart reconstruction, and exactly-once retry can be addressed separately if their operational value justifies the additional model and complexity.
A submission has three distinct parts:
| Part | Meaning |
|---|---|
| Request | A caller asks Archivematica to create a Transfer. |
| Content | The files that belong to that Transfer. |
| Workflow | The processing Archivematica performs on those files. |
The current design often uses one filesystem event to connect all three. A
caller places content in activeTransfers; MCPServer notices the new path,
infers which Transfer and workflow it represents, and starts processing.
The proposed design connects them explicitly. MCPServer receives a submission request, creates the Transfer, arranges for its content to be materialized, and starts the selected workflow. The filesystem holds the content but does not act as the submission message.
Automation Tools is a useful example because it uses the complete v1 submission and approval sequence:
Automation Tools
|
| 1. POST /api/transfer/start_transfer/ and wait
v
Dashboard
|
| 2. Copy the selected content
v
activeTransfers/<type>/<name>
|
| 3. A filesystem watcher notices the new path
v
MCPServer
|
| 4. Infer the workflow from the watched directory
| 5. Create an approval Job
v
Transfer waits for approval
After start_transfer returns a path, Automation Tools modifies the submitted
content with any configured pre-transfer hooks. It then calls /unapproved,
finds the path basename, and sends that directory and type to /approve.
The copy in step 2 is part of the synchronous HTTP request: the caller waits for
it. Steps 3 through 5 happen independently after the filesystem changes. This
means the copy can be complete before the watcher has made the Transfer visible
through /unapproved.
The caller sees the same API sequence, but the internal handoffs are explicit:
Automation Tools
|
| 1. POST /api/transfer/start_transfer/ and wait
v
Dashboard
|
| 2. Ask MCPServer to create a Transfer
v
MCPServer creates the Transfer and returns its UUID internally
|
| 3. Queue transfer-source retrieval
v
MCPClient and Storage Service copy the content
|
| 4. Move it to submittedTransfers/<name>-<uuid>
| 5. Record the actual path
| 6. Start the approval workflow directly
v
Transfer waits for approval
|
| 7. Dashboard returns the writable path
v
Automation Tools runs hooks, calls /unapproved, and calls /approve
The retrieval and workflow work are asynchronous inside Archivematica: they can
wait in queues and run in other processes. The legacy start_transfer request
remains synchronous from the caller's perspective. Dashboard keeps waiting
until both of these statements are true:
- the content exists at the returned path;
- the Transfer is visible through
/unapprovedand ready for a decision.
/unapproved is a synchronous, read-only snapshot. It does not discover files
or start work. It reports approval Jobs that MCPServer has already created.
/approve records the caller's decision synchronously; after that response,
the accepted or rejected workflow continues asynchronously.
For example, Automation Tools submits a standard directory named records.
The response remains:
{
"message": "Copy successful.",
"path": "/var/archivematica/sharedDirectory/submittedTransfers/records-<uuid>/"
}Automation Tools can add or modify files at that path before approval. Its next
/unapproved request includes:
{
"directory": "records-<uuid>",
"type": "standard",
"uuid": "<uuid>"
}The client derives directory from the returned path and sends the same value
and type to /approve. It does not need to know that Archivematica stopped
using activeTransfers, and it does not need to adopt UUID approval.
The externally important boundary is therefore:
start_transfer succeeds
means
content is writable + the Transfer is ready to appear in /unapproved
The main change is who initiates the workflow:
| Question | Current design | Proposed design |
|---|---|---|
| What submits a Transfer? | Content appearing in a watched path. | An explicit MCPServer command. |
| Where is submitted content held? | activeTransfers/<type>/. |
submittedTransfers/. |
| How is the workflow selected? | From the watched directory. | From the validated request type. |
| Legacy return point | Copy done; watcher may lag. | Copy and approval ready. |
/unapproved source |
Parsed workflow path. | Transfer metadata. |
| Automation Tools | Existing API sequence. | Same sequence; no stock client changes. |
The database and MCPServer command establish that a Transfer exists. The filesystem stores its content; the appearance of a path does not create a Transfer or act as the authoritative submission command.
MCPServer owns these submission steps:
- validate the request and select the transfer workflow starting point;
- create the
Transferand its compatibility metadata; - schedule transfer-source retrieval, or accept a confined materialized source;
- publish the actual materialized path after the final move succeeds;
- schedule the existing approval or accepted workflow directly.
MCPClient continues performing Storage Service retrieval and workflow file operations. Dashboard remains an HTTP compatibility layer and does not copy transfer-source content itself.
- Remove
activeTransfersfrom every supported submission path in this delivery. - Stop creating Transfers from unknown filesystem drops.
- Make MCPServer the authority that creates a Transfer and schedules its first workflow.
- Use the existing retrieval workflow for both package API approval modes.
- Preserve request fields, response shapes, public status labels, accession and metadata associations, and accept/reject outcomes.
- Keep legacy directory/type approval working when callers pass back the value
returned by
/api/transfer/unapproved. - Keep the supported Automation Tools v1 client working without source changes, including its start, pre-transfer hook, unapproved lookup, directory/type approval, and status sequence.
- Add UUID approval as the preferred selector for new integrations.
- Keep the implementation incremental and avoid introducing a second durable workflow runtime.
POST /api/v2beta/package/accepts its existing payload and returns the Transfer UUID.POST /api/transfer/start_transfer/accepts its existing form fields, creates one Transfer per selected path, preserves name suffixing for later paths, and returns the existing JSON keys.- A successful
start_transferresponse identifies content that exists, is writable by the Automation Tools process in the supported shared-filesystem deployment, and is already visible through/api/transfer/unapproved. GET /api/transfer/unapprovedkeeps its current response shape.- For a Transfer created by
start_transfer, thedirectoryreturned by/unapprovedequals the basename of the returnedstart_transfer.pathafter removing a trailing separator. POST /api/transfer/approvecontinues acceptingdirectoryandtypeand returns the approved Transfer UUID.- Transfer status APIs continue returning
PROCESSING,USER_INPUT,COMPLETE, andFAILEDaccording to the existing public model. - Non-auto-approved submissions appear in
/unapprovedonly after their content has been materialized successfully. - Rejection operates on materialized content and follows the existing rejected transfer workflow.
- Transfer-target reingest retains its request and response shapes and creates a
Transfer whose workflow type is
Archivematica AIP. - SWORD deposits still become standard Transfers and are approved automatically by the Storage Service integration.
- Physical paths move from
activeTransferstosubmittedTransfers. - New approval
directoryvalues include the Transfer UUID and should be treated as opaque round-trip selectors. - Archive UUID placement changes so the complete archive extension remains at the end of the physical filename.
- Filesystem
_2,_3, and similar collision padding is not preserved for new submissions; the Transfer UUID provides uniqueness. start_transfer.pathreturns the actual new materialized location, not anactiveTransferspath.start_transfercan spend additional time waiting for capacity inPackageQueuebefore copying begins.- Detailed workflow Job IDs, descriptions, ordering, and timestamps may change.
- MCPServer restart no longer rediscovers an unapproved Transfer from a watched path. Interrupted submissions fail conservatively.
- Direct filesystem drops into
activeTransfersno longer submit Transfers. - New and old Archivematica/Storage Service component versions are not expected to provide a supported mixed-version submission topology.
- Names previously accepted only because path operations failed later may now be rejected during request validation.
- Deployment-specific pre-transfer hooks that parse the old
activeTransfers/<type>/<name>layout or require a UUID-free basename may need adjustment. The stock Automation Tools client treats the returned path dynamically and does not depend on that layout.
These changes must be documented in release notes. They are intentional compatibility boundaries, not implementation accidents.
- Do not make
PackageQueueor Gearman durable. - Do not reconstruct interrupted jobs or approval decisions after restart.
- Do not provide exactly-once submission or retry semantics for SWORD, reingest, or the legacy APIs.
- Do not add a general local-filesystem submission endpoint.
- Do not remove unrelated watched directories from the workflow runtime.
- Do not redesign normal Transfer completion or failure workflows.
- Do not automatically delete every abandoned temporary or submitted path.
- Do not retarget internal failed-compliance links unless required for the submission cutover.
- Do not preserve unsupported mixed MCPServer versions during rolling restart.
- Do not preserve undocumented path-layout assumptions in deployment-specific Automation Tools hooks beyond the absolute writable path and transfer-type arguments.
POST /api/v2beta/package/ with auto_approve=True already creates a Transfer
through MCPServer and schedules transfer-source retrieval as workflow work. The
retrieval task currently materializes directly into currentlyProcessing and
continues at an optimized post-move link.
auto_approve=False still copies through shared tmp, moves into an
active-transfer watched directory, and relies on the watcher to create the
approval decision.
Dashboard copies each selected transfer-source path and moves it into a
type-specific directory under activeTransfers. It creates the Transfer before
the move and returns only after copying all selected paths.
/api/transfer/unapproved parses Job.directory as an active-transfer path.
/api/transfer/approve reconstructs that path from directory and type before
asking MCPServer to decide the matching Job.
Storage Service moves a finalized deposit into
activeTransfers/standardTransfer, polls /unapproved by deposit name, and
approves it.
The transfer-target reingest endpoint moves content from shared tmp into
activeTransfers/standardTransfer, creates an Archivematica AIP Transfer, and
relies on the standard transfer watcher.
The active-transfer watcher creates a Transfer when no matching database row exists. This makes the directory an unofficial filesystem API.
Some failed standard and DSpace compliance paths also move known Transfers back
into activeTransfers. Those are internal recovery operations rather than new
submission and can remain temporarily if the watcher is restricted to exact
known Transfer locations.
MCPServer creates the shared root during startup:
/var/archivematica/sharedDirectory/submittedTransfers/
It is not a watched directory. Operators and integrations must not place content there directly.
Create the root and submitted children with the same shared group and writable
permission contract used by the supported Archivematica deployment. An
Automation Tools process that could modify the old returned activeTransfers
path through the standard shared-directory mount must also be able to modify a
returned submittedTransfers path.
New destinations contain the Transfer UUID:
directory: %sharedPath%submittedTransfers/<validated-name>-<uuid>/
archive: %sharedPath%submittedTransfers/<validated-stem>-<uuid><extension>
Examples:
records-3c35c11e-d34a-43c2-a3b7-105f68f7c5d0/
deposit-3c35c11e-d34a-43c2-a3b7-105f68f7c5d0.tar.gz
Do not pad a UUID destination on collision. An existing destination for the same UUID is a conflict or an abandoned path requiring operator attention; it must not be overwritten.
submittedTransfers is pre-processing storage. The accepted standard workflow
moves content from there into currentlyProcessing. Archive workflows perform
their existing extraction or move as their first accepted step. Rejection moves
content through the existing rejected-transfer workflow.
Add nullable fields to Transfer:
submission_type
submission_display_name
submission_approval_directory
submission_materialized_at
Null fields identify legacy and pre-upgrade Transfers.
submission_typestores the legacy API value such asstandard,zipfile, orzipped bag. Do not overloadTransfer.type, which normal workflows set to values such asStandard,Dspace,TRIM, orArchivematica AIP.submission_display_nameis the stable logical name shown by status APIs.submission_approval_directoryis the opaque UUID-bearing selector returned by/unapprovedand accepted by/approve.submission_materialized_atis set only after the final destination exists. It lets the synchronous legacy endpoint distinguish accepted submission from completed copying without introducing a general lifecycle state machine.
Use these values for new submissions:
| Surface | Display name | Approval directory |
|---|---|---|
| v2beta directory | Validated request name | <name>-<uuid> |
| v2beta archive | Archive basename | <stem>-<uuid><extension> |
| legacy start directory | Suffixed target name | <target>-<uuid> |
| legacy start archive | Archive basename | <stem>-<uuid><extension> |
| SWORD | Validated deposit name | <name>-<uuid> |
| transfer reingest | Validated normalized AIP name | <name>-<uuid> |
The approval directory is the submitted physical basename without a trailing separator. It is intentionally UUID-bearing for every new surface.
Do not add submission-state, retry-attempt, planned-path, dispatch, approval-key,
or recovery columns in this change. The existing Transfer status, Job status,
workflow unit variables, and actual currentlocation remain authoritative for
their existing purposes.
Keep Transfer.currentlocation empty until content has been materialized. In
one database transaction after the final move, set:
currentlocation = <actual %sharedPath%submittedTransfers path>
submission_materialized_at = <current time>
The retrieval Job receives its copied and final paths through explicit workflow
context. It must not require a planned nonexistent value in currentlocation.
Status APIs use submission_display_name while no physical path or Job exists.
Make the two workflow entry points for every supported transfer type explicit:
- approval chain and decision link;
- accepted chain and its first link.
The mapping covers:
standard;zipfile;unzipped bag;zipped bag;dspace;maildir;TRIM;dataverse.
The temporary mapping can retain the legacy watched-directory path for pre-upgrade lookup and tests, but new submission code must never use it as a destination.
Continue using the existing retrieval chain:
Retrieve transfer source
Continue after transfer source retrieval
Set linkAfterTransferSourceRetrieval to:
auto_approve=True -> accepted chain first link
auto_approve=False -> approval decision link
Starting at the accepted chain's first link is intentional. Retrieval now ends
in submittedTransfers, so the established move or extraction step is required
and the current post-move optimization should be removed.
For either package API approval mode:
- validate name, transfer type, source selection, user, and processing config;
- create the Transfer with public
PROCESSINGstatus and minimal submission metadata; - create an MCP-controlled temporary retrieval directory;
- plan the exact UUID destination under
submittedTransfers; - schedule the retrieval chain with explicit source, copied, and final paths;
- MCPClient asks Storage Service to copy into the temporary directory;
- MCPClient renames the copied result to the exact submitted destination;
- publish
currentlocationandsubmission_materialized_attogether; - continue to the accepted first link or approval decision.
If retrieval fails, the retrieval workflow records a failed Job and marks the
Transfer FAILED. It must not create an approval prompt.
If the final move succeeds but database publication fails, log the exact orphan path and mark the Transfer failed when possible. Automatic compensation and replay are outside this proposal.
If scheduling the continuation fails after materialization, mark the Transfer failed and leave its submitted content for documented operator cleanup. Do not attempt durable redispatch.
Any label used as a physical path component must be validated before creating a
Transfer or moving content. This applies to package names, start_transfer
names, archive basenames, SWORD deposit names, and reingest names.
A valid component must:
- be non-empty;
- not be
.or..; - not contain NUL,
/, or\\; - not be absolute;
- represent exactly one component;
- fit the destination filesystem's component-length limit after adding the UUID and complete archive extension.
Reject invalid input rather than silently rewriting it. Keep the validated Unicode label as the display name.
For SWORD and reingest, accept only an opaque source kind and identifier. Resolve the source under a fixed configured root, require it to be strictly contained by that root, and reject missing paths and symlinks that escape it. Callers cannot provide a destination.
Check UUID destinations with lexists semantics and never overwrite them.
Transfer-source selection confinement continues to rely on the existing Storage
Service location association and copy API.
Keep:
POST /api/v2beta/package/and its response:
{"id": "<transfer-uuid>"}Both auto_approve values use MCPServer retrieval into submittedTransfers.
They differ only in the link selected after retrieval.
New submissions remain visible as PROCESSING before their first Job row, using
the existing processing-configuration timestamp fallback already present on
qa/1.x. The status response uses submission_display_name instead of deriving
the name from an empty or UUID-bearing path.
Keep the endpoint and form fields:
POST /api/transfer/start_transfer/Dashboard should no longer copy files or move content into a watcher. For each selected path, in order:
- preserve the current target naming (
name,name_2,name_3, ...); - call
MCPClient.create_package(..., auto_approve=False); - receive the Transfer UUID from the normal short Gearman RPC;
- poll that Transfer, treating public
FAILEDas an error before checking submission readiness; - wait for both a non-null
submission_materialized_atand the corresponding known approval Job to be committed asAWAITING_DECISION; - only then submit the next selected path.
Return the existing success response after all components are materialized and ready for approval:
{
"message": "Copy successful.",
"path": "<last actual materialized path>"
}The returned path is below submittedTransfers and includes the Transfer UUID.
Directory paths preserve the trailing separator; archive paths do not add one.
It must exist, remain at that location, and remain writable while the Transfer
awaits approval. Changes made there by pre-transfer hooks must be the content
seen by the accepted or rejected workflow.
For each returned Transfer:
unapproved.directory == basename(start_transfer.path without trailing slash)
The approval workflow must not move or otherwise process the submitted content before the explicit approval decision.
The request now waits for PackageQueue delay, copy and move time, and the short
continuation into the approval decision. Do not hold the packageCreate RPC
open for that duration. Do not return "Copy successful." before both
materialization and approval readiness. Use the same approval-readiness query
in start_transfer and /unapproved so their views cannot drift. This closes
the race where an Automation Tools client receives a path but cannot find its
Transfer in /unapproved during its finite approval retry window.
If the client disconnects, the accepted Transfer continues. Because the legacy request has no idempotency key, retrying an uncertain response can create another Transfer. Clients should inspect transfer status before retrying.
The existing partial-success behavior remains: an earlier selected path may have materialized when a later path fails.
GET /api/transfer/unapproved keeps its response shape but stops parsing
active-transfer paths.
For new submissions:
- find awaiting Jobs by known transfer approval link IDs;
- read
typefromTransfer.submission_type; - read
directoryfromTransfer.submission_approval_directory; - return the Job's Transfer UUID.
Do not return a submission before its materialization metadata is committed and
its actual path exists. A successful legacy start_transfer response must imply
that the same Transfer is immediately eligible for this result set.
The approval directory is derived from the UUID-bearing submitted basename and is unique without collision padding. Callers must treat it as opaque.
Add UUID approval:
POST /api/transfer/approve
uuid=<transfer-uuid>Retain directory/type approval:
POST /api/transfer/approve
directory=<value returned by unapproved>
type=<legacy-transfer-type>Resolution order is:
- UUID, when supplied;
- exact new submission metadata match;
- exact pre-upgrade watched-path fallback.
Only match Jobs that are awaiting a known transfer-approval decision. Do not approve an arbitrary Job or choose ambiguously.
The supported Automation Tools v1 client uses this sequence without knowing the Transfer UUID in advance:
- call
start_transferand read itspath; - run pre-transfer hooks against that absolute path;
- derive the selector from the path basename;
- find the same selector and its
typein/unapproved; - submit that
directoryandtypeto/approve; - poll the existing Transfer and ingest status APIs.
Preserve this sequence for the unmodified supported Automation Tools client and
its pinned amclient==1.6.0. It is safe for the physical basename to include the
Transfer UUID because the stock client derives and round-trips the returned
value instead of constructing it.
The compatibility guarantee assumes the normal deployment in which Automation
Tools can access /var/archivematica/sharedDirectory at the same absolute path.
Deployments that mount only the old activeTransfers subtree must expose the
new submittedTransfers root. Custom hooks that parse the old parent-directory
layout or a UUID-free basename are outside the stock-client guarantee and must
be called out in the release notes.
Add one MCPServer helper for content already present in shared storage:
submit_materialized_transfer(
source_kind,
source_identifier,
transfer_uuid,
display_name,
submission_type,
...existing Transfer metadata,
)
The helper accepts a closed source-kind mapping:
sword -> %sharedPath%tmp/transferSubmissionIntake/sword/<deposit-uuid>
reingest -> %sharedPath%tmp/<validated-reingest-name>
It does not accept an arbitrary path. It validates the source, creates the Transfer, moves content to the exact UUID destination, publishes the actual location and materialization timestamp, and schedules the approval decision.
The helper does not promise idempotent retry. A duplicate Transfer UUID or existing destination is an explicit conflict. It never overwrites content or silently creates a second destination for the same UUID.
Storage Service stages a finalized deposit at:
%sharedPath%tmp/transferSubmissionIntake/sword/<deposit-uuid>/
It calls a dedicated authenticated materialized-package endpoint with:
- source kind
sword; - deposit UUID as source identifier and requested Transfer UUID;
- validated public transfer name.
The endpoint accepts no filesystem path. Dashboard forwards the closed source identity to MCPServer.
After receiving the Transfer UUID, Storage Service polls
/api/transfer/unapproved until that UUID appears and approves it using UUID
approval. It no longer correlates by deposit name.
There is no fallback that moves the deposit into activeTransfers. The
Archivematica release must pin a Storage Service revision that implements this
endpoint contract. Unsupported old/new component pairings fail visibly rather
than silently returning to filesystem submission.
On an uncertain integration retry, a duplicate UUID can return a conflict. The deposit remains in its intake or submitted location for status inspection and operator handling. Automatic retry reconciliation is future work.
The existing transfer-target reingest endpoint keeps its public request and response shapes.
Before destructive database cleanup or filesystem movement:
- validate
nameas one component; - resolve the source strictly beneath
%sharedPath%tmp; - plan the exact UUID destination and reject an existing path.
Then perform the existing SIP, Job, rights, and Dublin Core cleanup and invoke
submit_materialized_transfer with the initial
Transfer.type=Archivematica AIP. The helper creates the new Transfer, moves the
payload, and schedules the standard transfer approval decision directly.
The response remains:
{
"message": "Approval successful.",
"reingest_uuid": "<uuid>"
}The historical message does not mean the transfer bypasses its normal approval decision.
Reingest retry remains non-idempotent unless its caller supplies and retains a stable UUID in a future contract. A repeated request can conflict or create a new operation, as it can today.
At final cutover, the active-transfer watcher must not call
get_or_create_from_db_by_path() for unknown paths.
For an active-transfer watched directory:
- convert the observed path to its exact database form;
- look up an existing Transfer whose
currentlocationis exactly that value; - schedule the internal recovery chain only when that exact row exists;
- otherwise log a warning and leave the unknown content untouched.
A UUID-looking filename is not sufficient evidence of ownership.
This guarded behavior keeps known failed-compliance recovery working while ending direct filesystem submission immediately. Release notes must tell users to use the package or legacy transfer APIs.
After failed-compliance links are retargeted, a future change can remove the remaining active-transfer watcher definitions and directories entirely.
Submission uses the existing MCPServer failure model rather than adding a durable lifecycle.
- Validation failure creates no Transfer and moves no content.
- Executor or initial scheduling rejection marks a created Transfer failed.
- Retrieval failure creates a visible failed Job and marks the Transfer failed.
- Final move failure marks the Transfer failed and leaves enough logging for temporary-path cleanup.
- Database publication failure after a move logs the exact orphan destination and attempts to mark the Transfer failed.
- Continuation scheduling failure after materialization marks the Transfer failed and leaves content for operator cleanup.
- Rejection follows the existing rejected-transfer workflow.
On MCPServer restart, retain existing conservative cleanup:
- queued or retrieving
PROCESSINGTransfers becomeFAILED; - materialized Transfers waiting for approval become
FAILEDwhen their in-memory decision is lost; - Transfers whose accepted workflow was running become
FAILEDas today; - awaiting-decision Job rows are not reconstructed.
The old active-transfer watcher previously rediscovered some waiting Transfers.
That recovery was an incidental consequence of filesystem submission and is not
preserved. Content left under submittedTransfers after failure is never treated
as a new submission.
- Successful accepted workflows move or extract content out of
submittedTransfers. - Rejection moves content into the rejected-transfer location.
- Retrieval failure should remove its temporary copy on a best-effort basis.
- Failed materialized content and UUID destination conflicts are logged for operator inspection.
- Initially, do not automatically delete unreferenced submitted content.
- Document how operators correlate submitted paths with Transfer UUIDs and remove confirmed abandoned content.
- Monitor shared-directory capacity because unapproved and failed content can remain for an extended period.
Age-based orphan cleanup can be introduced later after operational experience defines a safe retention policy.
- Stop all old MCPServer processes before starting the new version.
- Deploy the Archivematica and Storage Service revisions from the supported release pairing.
- Drain active submissions before upgrade where practical.
- Keep exact-path approval fallback for pre-upgrade awaiting Jobs.
- Do not automatically migrate arbitrary content already in
activeTransfers. - Unknown active-transfer content remains for operator inspection after cutover.
- Application rollback after new submissions requires draining or manually
handling content under
submittedTransfers.
The cutover release intentionally ends direct active-transfer filesystem submission. It does not require a prior compatibility release because the replacement public APIs already exist, but the removal must be prominent in release and upgrade notes.
Use an expand-migrate-contract sequence within the same delivery.
- add
submittedTransfersand the SWORD intake root; - add the four nullable Transfer fields;
- make approval and accepted starting points explicit;
- validate names and plan exact UUID destinations;
- route both package API approval modes through retrieval;
- continue at the accepted first link or approval decision;
- update status, unapproved, and approve readers for new metadata;
- add UUID approval while keeping pre-upgrade path fallback.
- replace Dashboard-side copying with package creation;
- preserve sequential multi-path behavior and response keys;
- wait for materialization and the matching awaiting-approval Job after the short RPC;
- preserve the Automation Tools writable-path and directory/type round trip;
- document new paths and PackageQueue delay.
- add
submit_materialized_transfer; - migrate transfer-target reingest;
- add the authenticated path-free SWORD endpoint;
- cover source containment and no-overwrite behavior.
- stage deposits in the UUID intake root;
- call the new endpoint;
- wait and approve by returned Transfer UUID;
- remove SWORD writes to
activeTransfers; - do not add an old-pipeline filesystem fallback.
- update the Storage Service submodule revision;
- guard the active-transfer watcher against unknown paths;
- verify repository-wide that supported writers no longer use
activeTransfers; - retain only named internal failed-compliance compatibility writes;
- add release notes and upgrade guidance.
PRs 1 through 4 add and migrate the replacement while the old watcher remains available to callers not yet migrated in the development branch. PR 5 is the release cutover and must not merge until every supported writer is migrated.
- Both package approval modes retrieve to
submittedTransfers. - Every supported transfer type has valid approval and accepted starting links.
- Auto approval enters the accepted chain at its first link.
- Non-auto approval stops at the approval decision after materialization.
currentlocationremains empty before materialization.currentlocationandsubmission_materialized_atare published together.- Retrieval, move, publication, and continuation failures mark the Transfer failed without creating an approval prompt.
- Standard accepted, rejected, archive, bag, DSpace, maildir, TRIM, and Dataverse continuations operate from the submitted location.
- Restart marks queued, retrieving, awaiting-approval, and continuing Transfers failed according to the documented policy.
- Submitted content is not rediscovered as a new Transfer after restart.
- v2beta request and response shapes remain unchanged.
start_transfersubmits every selected path in order and waits for both materialization and approval readiness.- Multi-path suffixing and partial success remain.
start_transfer.pathis the last actual UUID-bearing path.- The returned path exists, remains writable while awaiting approval, and contains changes made before approval when processing continues.
- Queue delay does not produce early
"Copy successful."responses. - A delay between materialization and creation of the awaiting-approval Job does not produce an early success response.
- Status APIs use
submission_display_namebefore and after Jobs exist. /unapprovedreturns the existing shape and opaque UUID-bearing directory.- The new
/unapprovedresult is committed beforestart_transferreturns, and itsdirectoryequals the basename ofstart_transfer.path. - UUID approval works.
- Directory/type approval round-trips the new value.
- Pre-upgrade watched-path approval fallback works.
- Duplicate display names remain independently approvable because selectors include different UUIDs.
- Run a Compose integration test with the supported Automation Tools v1 client
unchanged and its pinned
amclient==1.6.0. - Exercise its real sequence:
start_transfer, a pre-transfer hook,/unapproved, directory/type/approve, and status polling. - Have the hook write a marker or metadata file through the returned absolute path and verify that the accepted workflow processes that change.
- Cover a standard directory and an archive transfer so trailing-separator and archive-basename behavior are both exercised.
- Delay approval-Job creation beyond the client's finite retry window and
verify that
start_transferabsorbs the delay instead of returning early. - Do not patch Automation Tools to understand UUIDs or use the new UUID approval parameter; compatibility is demonstrated by its existing basename round trip.
- Empty, dot, traversal, separator, absolute, NUL, and overlong components are rejected before Transfer creation or movement.
- Escaping symlinks and sources outside fixed local roots are rejected.
- Broken symlink and existing UUID destinations are conflicts.
- Destinations are never padded or overwritten.
- Complete archive extensions are preserved after UUID insertion.
- SWORD uses the new endpoint and never writes to
activeTransfers. - SWORD correlates and approves by returned Transfer UUID.
- Missing intake content and duplicate UUID conflicts fail visibly.
- No HTTP error causes a fallback into
activeTransfers. - Reingest validates its source before destructive cleanup.
- Transfer-target reingest moves to
submittedTransfers, retainsArchivematica AIP, and enters standard transfer approval. - Existing endpoint response shapes remain.
- Unknown direct drops do not create Transfers.
- UUID-looking unknown paths are also rejected.
- Exact known failed-compliance paths still run while compatibility remains.
- Repository-wide search finds no supported submission writer targeting
activeTransfers. - Remaining
activeTransferswrites are enumerated internal recovery cases.
Run the relevant Compose targets for each pull request, including:
make -C hack test-dashboard
make -C hack test-mcp-server
make -C hack test-mcp-client
make -C hack test-storage-service
make -C hack test-storage-service-integration
make -C hack test-linting
MCPServer still keeps PackageQueue and approval decisions in memory. A future MCPServer-wide design could persist dispatch attempts or reconstruct waiting decisions. That work should address Transfers, SIPs, and DIPs consistently rather than creating a durable subsystem only for submission.
SWORD and reingest can later gain a persisted operation identity and explicit retry contract. This proposal detects UUID conflicts but does not decide whether to resume, return, or retry every partial filesystem/database state.
Transfer-target reingest also retains the existing lack of one transaction across destructive cleanup and filesystem movement. A failure after database cleanup can require operator repair. Solving that belongs with a future idempotent reingest operation contract, not with the active-transfer cutover.
Operational experience should define an age-based cleanup policy for failed or unreferenced content. Automatic deletion is deferred to avoid removing content that an operator may need to inspect or recover.
Failed-compliance workflows may temporarily retain exact-known-path use of the active-transfer watcher. Retargeting those links will allow removal of the active-transfer watcher definitions and physical directories.
A future versioned endpoint could make materialization explicitly asynchronous, return operation identifiers for multi-path requests, and avoid holding an HTTP request through PackageQueue delay. The existing endpoint retains synchronous copy semantics in this delivery.
If operators later need to distinguish queued, copying, materialized, waiting, and continuing states independently of Jobs, add a focused lifecycle model at that time. The current design deliberately uses only the one materialization timestamp required by the legacy synchronous contract.
Release notes must state:
- supported transfer submission no longer uses
watchedDirectories/activeTransfers; - direct filesystem drops there are no longer supported;
- public submission APIs remain available;
- unapproved directory values and returned physical paths can now contain the Transfer UUID and use the new root;
- interrupted unapproved submissions are no longer rediscovered after MCPServer restart and instead fail conservatively;
- Archivematica and Storage Service must use the supported paired versions;
- operators may see
submittedTransfersand must not submit content there manually; - the supported Automation Tools v1 client remains compatible without code changes when it can access the full shared-directory mount;
- custom pre-transfer hooks that parse the old parent path or a UUID-free
basename may require changes, and mounts limited to the old
activeTransferssubtree must exposesubmittedTransfers; - old active-transfer content should be drained or inspected before upgrade.
Developer and operator documentation must explain:
- the distinction between submitted and currently-processing content;
- UUID approval and opaque directory/type compatibility approval;
- the synchronous
start_transferwait and PackageQueue delay; - manual inspection and cleanup of failed submitted content;
- the guarded purpose of any remaining active-transfer watcher.
- No supported package API, legacy start, SWORD, or transfer-target reingest
submission writes to
activeTransfersafter cutover. - Both v2beta package approval modes use the same retrieval path into
submittedTransfers. - MCPServer creates the Transfer and schedules the workflow explicitly.
- Only four nullable submission compatibility/materialization fields are added
to
Transfer; no submission state machine or durable dispatch model is added. currentlocationrepresents actual materialized content, not a planned path.- Public request and response shapes and status labels remain supported.
- New approval directory values are opaque and UUID-bearing; UUID and directory/type approval both work.
- Duplicate display names do not create ambiguous approval selectors.
start_transferwaits for materialization and returns the actual new path.start_transferdoes not return success until the matching Transfer is also observable through/unapprovedas awaiting approval.- The returned legacy path remains writable until approval, and its basename is
the exact directory/type approval selector returned by
/unapproved. - The supported Automation Tools v1 client and
amclient==1.6.0complete start, pre-transfer modification, unapproved lookup, directory/type approval, and status polling without source changes. - SWORD uses the path-free endpoint and approves by returned Transfer UUID,
without an
activeTransfersfallback. - Transfer-target reingest uses confined materialized submission and preserves
its public response and
Archivematica AIPworkflow type. - Unsafe path components, escaping sources, and existing UUID destinations are rejected without overwrite.
- Unknown direct drops do not create Transfers.
- Remaining
activeTransfersbehavior is limited to enumerated exact-known internal recovery paths. - Interrupted submissions fail visibly on restart and are not reconstructed.
- Release and upgrade notes describe the intentional path, naming, restart, and component-pairing changes.