specs/011-mylar-modern-tagger/contracts/tagger-and-transport.md
Rama main · réplica de lectura del repositorio original.
# Compatibility contracts
The first increment exposes internal pure helpers only. It does not add a UI choice
or change the active tagger/transport.
`tagger_runtime.run(argv, cwd, timeout, max_output)` accepts an argument sequence,
never a shell command. It returns bounded output and a typed state. It does not
inspect or replace archives. Both version and tagging processes use this boundary.
Callers must not log argv or raw output. Timeouts kill the process group and reap the direct child. The real-CLI gate must
prove that the trusted CLI does not detach children into other sessions. Otherwise
stronger containment is required before activation.
`tagger_metadata.reconcile(original, tagged, updates, replace_fields)` accepts bounded
ComicInfo bytes. Existing fields survive unless their names appear in replace_fields
or updates. It neither reads archives nor changes files. Updates are explicit writes,
not inferred deletion. Preserve full page metadata when pages are unchanged. StoryArc and StoryArcNumber
are one replacement group. A partial original name never borrows an unrelated
tagged number; partial explicit replacement or mismatched numbered lists is rejected.
The legacy adapter retains cmtagmylar.run's path/sentinel return contract. Modern
manual tagging uses an explicit `mylar.tagger_handoff.Published` result; automatic
routing must continue to supply a verified disposable path. The observer understands
typed results; native dispatch selects Modern only for its saved opt-in preference.
One adapter owns replacement. Modern backend does not convert or delete originals.
Conversion-only and non-ZIP routes must be proved before native activation.
`tagger_adapter.Publisher.tag(source, metadata, token=...)` is an inactive internal
CBZ operation. It replaces a source only after staged verification, using Linux
atomic exchange to retain the displaced file until commit verification. Unsupported
filesystems have no weaker fallback. The caller must exclude non-cooperating writers;
an observed race keeps recovery copies and never reports success. Hardlinks and
extended attributes/ACLs are unsupported. `recover(token)` reconciles a durable
intent without repeating the CLI. Same-token/different-request calls conflict;
separate token and source locks serialize overlapping work. Raw child output and
file paths never appear in the returned `Result`. No native caller uses this API yet.
The optional discovery session exposes the current requests get/post/context lifetime and
exception expectations. Provider resolution and archive transfers are separate
choices. No double retry layer, extra cooldown mutation, automatic fallback, or
unbounded queue is acceptable. Public workflow routes retain existing authentication
and CSRF. Selection is validated server-side and shown with accurate availability.
`Publisher.recover_pending()` streams sanitized `RecoveryResult(token, state,
metadata)` records. It does not wait for token/source locks or hash cleaned history.
Callers must exhaust the iterator before admitting modern work and keep admission
closed on `busy`, `conflict`, `invalid_journal` or `io_error`. Malformed receipt names
return an empty token, never a path/name. Unexpected receipts remain untouched; one
invalid receipt does not prevent reconciliation of other valid pending jobs. Native startup
exhausts this API before admitting work.
`mylar.tagger_handoff.capture(publisher, token)` reconciles a receipt and checks its
cleaned outcome, current bytes, inode, permissions and expected source before
creating an in-place result. Paths and digests are excluded from repr. Manual
callers reverify the exact expected source before bypassing copy/delete and rescanning.
All four automatic calls reject every non-string result before placement. Unknown
or foreign typed results fail closed. Producers and consumers must import the same
canonical native module; the final image has no standalone handoff module copy.
`mylar.tagger_service.Service` requires a private cache and a global media-writer
coordinator. Every admitted job exhausts pending publication recovery under that
coordinator. Manual success returns canonical `Published`; automatic success returns
a disposable path and preserves the download source. Automatic failures remain the
literal `fail` string with a typed reason for the observer. No-overwrite on existing
ComicInfo skips lookup, overrides and CLI work. Unsupported modes fail closed.
The exact backend selector never retries through legacy after a modern failure.
The native entry point routes to this service only when the code capability gate
opens. Startup reconciliation runs before database maintenance even under Legacy.
Automatic callers require an outer native operation spanning placement and cleanup;
manual calls return canonical in-place receipts. Staging manifests never imply import
success. Uncertain copies remain private; disposable copies are deleted only against
an unchanged original and the recorded content digest.
`tagger_lookup.lookup` keeps credentials in a private temporary file, never argv,
and bounds the child to 45 seconds and 64 KiB output. HTTP responses are limited to
1 MiB, reject redirects, and use explicit fields. Issue, linked volume and expected
volume identities must agree. The caller supplies the actual annual release volume
when applicable. Provider requests wait the configured interval before each request.
Only fields explicitly returned by the provider may replace existing fields.
`Metatagging.tagger_backend` is additive, defaults to `legacy`, and accepts only the
exact values `legacy` and `modern`. Native config requests validate all matching
keys before any mutation, including duplicate case variants. An unavailable Modern
choice is rejected with a sanitized message. This image exposes Modern as an
experimental opt-in after native, live canary, UI and rollback acceptance. Native entry dispatch reads the
configured choice once, preserves legacy arguments and exception behavior, and
never falls back for a manually configured unsupported backend. Legacy remains the default and switching back preserves recovery ownership.
The version-1 media writer protocol lives in private, local Mylar state and is
mounted narrowly read-write by the opt-in normalizer. File locks exclude complete
native post-processing/manual tagging and worker cycles. A durable worker marker
blocks native admission while conversion receipts remain unfinished, surviving
process death and asynchronous Komga requests. Only worker reconciliation clears
it. Protocol files cannot be recreated in an existing directory after deletion.
Both image contexts ship identical protocol code, checked by repository tests.
Native guards also cover series imports, rescans, renames, moves, cover writes and
web/API series deletion. The v2 tagger fence is separate from the worker fence;
neither recovery owner can bypass the other. State directory identities are bound
before publication. The worker persists rescan notifications and sends them after
release, retrying failure without replaying conversion. Matching images are required;
old workers do not understand the tagger fence or deferred callback contract.
`tagger_nfs.Publisher` explicitly selects version-2 rename/link publication. It is
not a fallback and must use a separate journal from the version-1 exchange publisher.
Both retain recovery material on conflicts. NFS publication retains the displaced
inode and never replaces a competing destination. The source name can be absent
between displacement and publication, including across a crash; recovery must run
before any media writer or scanner is admitted. RPC errors are reconciled from
verified identities and bytes. Exact supported ACL/user-attribute values, mode and
ownership are checked before publication and in the canonical native handoff.
Old readers reject v2 receipts without mutation. Native modern routing explicitly
selects v2; live canary and rollback acceptance passed before opt-in activation. Never
start an old writer over pending v2 state. Reconcile with the matching image first,
or restore the verified prior state and affected fixture files with writers stopped.
Automatic cache inputs retain legacy native destination ownership policy; exact ACL
and user-attribute preservation applies to manual in-place publication. Automatic
link placement is unsupported and retains the original path rather than linking to
a disposable stage. No live
configuration, Compose, privilege or ingress change is part of this increment.
`DDL.ddl_discovery_backend` accepts exactly `requests` or `curl`, defaulting to
Requests. Config validation rejects invalid or duplicate case variants before any
mutation. The outer search/loadsite/cookie operation captures its backend and
closes responses at exit. Nested calls retain that session. Archive `self.session`
remains the original Requests session. Discovery copies scoped cookies and proxies
at entry and returns cookie updates at exit. Curl uses an isolated child for each
bounded response, normalizes network exceptions and adds no retries or fallback.
Full curl streaming is not selectable because its queue fails the bounded-buffer
gate. No D2/D3 success is implied by discovery acceptance.
Power-loss testing is excluded by user instruction. Existing durable publication
and startup recovery are checked with process interruption and injected flush
errors only. Persistent flush failure retains copies and requires review.