Define acquisition recipe and local-use rights schemas #56
No reviewers
Labels
No labels
area/deployment
area/governance
area/metadata
area/pipeline
area/search
type/data
type/design
type/implementation
type/testing
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Arkive/arkive!56
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/issue-44-acquisition-schemas"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
local_use_rights(the eight eligibility dimensions, evaluated independently),acquisition_recipe(one reviewed route as declarative, versioned, withdrawable data), andlocal_acquisition(what a user actually obtained, belonging to theuser_localdomain).distribution_scopeto document and chunk records, defaulting tobundled_core, and enforces that it is inherited down the lineage so anything produced from a user acquisition staysuser_local.not_applicablerationale and mode-binding, recipe/rights agreement, withdrawal propagation, checksum coherence, download-host allowlisting, and scope inheritance.Deliverables
meta/schemas/local_use_rights.schema.json,acquisition_recipe.schema.json,local_acquisition.schema.jsontests/fixtures/acquisition/(valid); invalid cases are single-field mutations of a valid fixture intests/test_acquisition_schemas.pymeta/schemas/README.md, new "User-side acquisition contracts" sectionmeta/schemas/README.md, "Compatibility with the existing contracts"governance/decision_records/ADR-0003-...mdAcceptance criteria
distribution_scopeis optional on both document and chunk and defaults tobundled_core. The committed corpus validates with no edits; a regression test asserts that a record omitting the field entirely is still valid. The compatibility table in the schema README states this per field.undeterminedresult in a dimension required for the selected acquisition mode blocks eligibility. Required and exemptible dimensions are declared per mode in the validator rather than inferred.review.last_reviewed,review.expires, and an optionalreview.withdrawnwith date and reason. A withdrawn rights profile forces its recipes to be withdrawn; tests cover both the failure and the valid withdrawn state.local_only— a document derived from auser_localdocument must beuser_local, a chunk must match its document's scope, and a document carrying alocal_acquisition_idmust beuser_local. Alocal_acquisitionrecord is pinned to"user_local"byconst, so no value promotes it.rights_id, source mismatch, withdrawn rights against an active recipe, checksum required without a checksum, download host outsideapproved_hosts, missing rationale, and exemption claimed where the mode forbids it.Verification
git diff --checkpython pipeline/validate/validate_records.py— PASS, committed corpus unchangedpython -m unittest discover -s tests -v— 83 tests passing, up from 55python -m ruff check src pipeline testspython -m ruff format --check src pipeline testsScope confirmation
origin.acquisition_pathis deliberately untouchedThe "arbitrary code and secrets" criterion
This is enforced structurally rather than by prohibition, which is worth checking during review because it is the strongest claim in the PR:
additionalProperties: false, so an unknown property is invalid rather than ignored;manualandhttps_direct, and adapter parameters are per-type closed objects rather than a free-form map, so there is nowhere to put a command;^https://with userinfo excluded by pattern, sofile://,http://, andhttps://user:secret@host/are all invalid;Tests assert that a recipe carrying
command,post_process_script,password,api_key,cookie, embedded userinfo, or a non-HTTPS scheme fails validation, and that a local acquisition record carryingaccount_id,order_receipt, orpayment_methodfails.Points for reviewer attention
One rule may belong to #45 rather than here. The validator rejects a
local_acquisition.jsonfound anywhere underdata/. It is a small contamination guard directly serving the inheritance criterion, but full storage-boundary enforcement is Issue #45's remit. Happy to move it if the scopes should stay cleaner.Fixtures live under
tests/fixtures/acquisition/, notdata/. A recipe committed underdata/would read as an approved acquisition target, and Issue #43 explicitly selects none. Every host in the fixtures isexample.org, and both fixture sources are markedmetadata_onlyand non-bundleable.Required and exemptible dimensions are declared per mode as explicit sets in the validator rather than derived. This is deliberate: a missing entry in a derived rule would silently permit an unreviewed dimension, which is the failure mode the fail-closed rule exists to prevent.
Only two acquisition modes and two adapters are defined. The issue mentions a possible future official API adapter; ADR-0002 defers authenticated APIs to a dedicated design, so adding the enum value now would be a speculative field. Both schemas note that a third mode requires an approved design and a version bump.
authentication_required: trueforcesmanual_import. Since credentials can never appear in a recipe, an automated mode against an authenticated route is unrepresentable in principle; this rule makes that explicit rather than leaving it implied.Linked issue
Closes #44
Review — changes required before merge
This is a substantial and well-structured implementation. The separation between rights profiles, acquisition recipes, and local acquisition records is appropriate, and the PR remains within Issue #44’s schema-and-validation scope.
I found several contract gaps that need to be corrected before the PR can be merged:
Required
not_permitteddimensions do not invalidate the selected acquisition mode.The validator currently blocks required dimensions only when their result is
undetermined. Under the approved rights policy,not_permittedin a dimension required for the selected mode must also make that mode unavailable, except for the explicitly non-blocking redistribution dimension.Please add mode-aware validation and regression tests covering:
automation = not_permittedservice_terms = not_permittednot_permittedredistribution = not_permittedremaining non-blocking for local acquisitionA rights profile can claim that evidence was recorded without containing evidence.
evidenceis optional at the root, and an evidence entry only requires its type. Please require usable supporting evidence and validate its relationship with theevidence_recordeddetermination. A valid supporting entry should include an auditable reference or sufficiently concrete recorded detail.Some recipe URLs still permit embedded credentials.
The adapter URL rejects URL user information, but
target.landing_pageandintegrity.publisher_signature_urldo not. Please apply a shared HTTPS-without-userinfo definition consistently to every URL-bearing field.Local acquisition records can omit required provenance.
final_url,adapter, anduser_acknowledgementare currently optional. Please add mode-aware requirements so the record captures:Please also add corresponding negative tests.
Jurisdiction must be explicit.
The generic value
multidoes not identify which jurisdictions were reviewed. Please replace it with explicit jurisdiction identifiers, preferably through a non-empty list where more than one jurisdiction applies, and keep recipe/profile matching machine-checkable.Tighten expected-file and post-acquisition coherence.
Please reject contradictory or ineffective combinations, including:
min_bytesgreater thanmax_bytesOnce these points and their regression tests are addressed, the PR should receive another review against Issues #43 and #44 before merge.
All six points addressed in
afcdac9, with the branch updated fromdevelopafter PR #57.1. Refused dimensions now block their mode. The gap was real, and it contradicted the policy this PR is supposed to enforce:
docs/acquisition-eligibility.mdsection 3 already says anot_permittedresult in a required dimension makes that mode unavailable, and the validator only checkedundetermined. A required dimension now blocks on both — absence and refusal are different facts with the same consequence for that mode.redistributionstays explicitly non-blocking via a namedNON_BLOCKING_DIMENSIONSset rather than an inline exception. Tests cover automated acquisition withautomationand withservice_termsrefused, refusal of a required access/jurisdiction/evidence dimension across both modes, andredistribution = not_permittedstill passing in both.2. Evidence must now be checkable.
evidenceis required at the root, and each entry must satisfyanyOf: a URL together withdate_accessed, or adetailof at least 20 characters. A bare{"type": ...}is an assertion, not evidence, and no longer validates. Tests cover the missing array, the empty array, a bare type, a URL with no access date, and a trivial detail.One note on the second half of that point. Once evidence was required at schema level with
minItems: 1, the cross-field rule checkingevidence_recorded = permittedagainst evidence presence became unreachable — schema validation rejects the record before the rule runs. I removed it rather than leave it in place, because a rule that cannot fire is the same defect as point 6: it reads as enforcement while providing none. If you would rather see the relationship expressed as a cross-field rule, the way to do it is to makeevidenceoptional again and enforce it there instead; happy to switch if you prefer that shape.3. Userinfo excluded everywhere. There is now one shared
https_urldefinition per schema, and every URL-bearing field references it: the adapter URL,target.landing_page,integrity.publisher_signature_url, rights-profile evidence URLs, and the recordedfinal_url. A subtest asserts thathttps://user:pw@…is rejected in each of those positions.4. Local acquisition provenance is now mode-aware.
adapteris required unconditionally;final_urlis required whenacquisition_modeisofficial_direct_download, via schemaif/then; and acknowledgement is cross-checked against the referenced recipe, so a record must showaccepted: truewhenever the recipe setsuser_acknowledgement.required. Negative tests cover each, including acknowledgement recorded but refused.5. Jurisdictions are explicit. The
multicatch-all is gone from both new schemas; the sharedjurisdictiondefinition is now^[A-Z]{2}$with a comment stating why there is deliberately no catch-all. Multiple jurisdictions are listed explicitly, and recipe/profile subset matching is unchanged and still machine-checkable. Tests rejectmultiin both the profile and the recipe, and confirm a three-jurisdiction list validates.One thing outside this PR's scope that you should decide on:
source.schema.jsonstill permitsmultiinlicense.jurisdiction, added under Issue #9. The same argument applies to it. No committed record uses the value, so removing it would break nothing, but it is a change to an existing contract outside #44 and I did not want to make it silently. Happy to include it here or raise a separate issue, whichever you prefer.6. Recipe coherence. Now rejected:
min_bytesgreater thanmax_bytes;require_media_type_matchwithoutexpected.media_types;require_size_within_boundswithout either bound; and a direct-download recipe stating no expected media types. Each has a regression test.The checksum sub-item was already implemented before this review —
validate_records.pyrejectedrequire_checksum_matchwithout a recordedintegrity.sha256, and there was a test for it. It is unchanged, and I mention it only so the list reconciles.Validation after the changes, with
developmerged in:git diff --checkcleanruff checkandruff format --checkcleanBoth worked fixtures still validate, and the committed corpus needed no edits.