app.services.intake

CI push intake: PUT / POST /api/v1/candidates.

A pushed document goes through the same parse -> validate -> hash -> persist path as a polled one (app.services.ingest), always as a candidate row: a push can never write, replace or delete a published row, and never touches approval history. Source adapters are never called.

Checks, in this order (each failure is answered with a stable code and a hint; see docs/the router for the HTTP status):

  1. no bearer token -> 401 not_authenticated;

  2. the token is not a push token of the named registration (unknown or disabled registrations have none) -> 401 invalid_push_token. Authentication comes first, so an unauthenticated caller learns nothing about which registrations exist or what the body should contain (a request without a usable source_id is authenticated against every registration before it is refused);

  3. body larger than MAX_BODY_BYTES -> 413 payload_too_large;

  4. malformed body (not JSON, missing/extra/invalid fields; a client-supplied digest is an unexpected field) -> 422 invalid_request;

  5. registration lacks push intake -> 403 push_disabled;

  6. project outside the registration scope -> 403 project_out_of_scope;

  7. ref equals the configured published_ref -> 422 published_ref_not_allowed. A leading refs/heads/ is ignored on both sides. For published_ref: "@project-default" (GitLab) the real default branch is unknown without a source call, which intake never makes: only the literal value is refused, and a push to the default branch is stored as a candidate (documented limitation; polling still owns the published row);

  8. delivery_id already received for the registration -> the earlier result (deduplicated: true), nothing is re-ingested (checked again under the registration lock against races);

  9. content larger than MAX_CONTENT_BYTES (UTF-8) -> 413 content_too_large; content that cannot be encoded as UTF-8 (lone surrogates) -> 422 bad_encoding;

  10. document without usable frontmatter or UUID -> 422 invalid_document with the issues list; document UUID other than adr_uuid -> 422 uuid_mismatch;

  11. POST and the candidate (source_id, project, adr_uuid, ref) exists -> 409 candidate_exists.

Every request records an intake_run (kind push): successes (also deduplicated ones) with the outcome in counts, failures with errors. The token is never stored; source_id is stored as requested (truncated), possibly naming no registration. delivery_id is stored only on the successful run, so a failed delivery can be retried with the same id.

Public API: MAX_BODY_BYTES, MAX_CONTENT_BYTES and CandidateSubmission (re-exported from app.services.intake_checks), IntakeMode, IntakeResult, submit_candidate().

class app.services.intake.CandidateSubmission(*, source_id, project, adr_uuid, ref, content, path=None, delivery_id=None, author=None)

Bases: BaseModel

Body of PUT/POST /api/v1/candidates; extra keys -> 422.

model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

source_id: str
project: str
adr_uuid: UUID
ref: str
content: str
path: str | None
delivery_id: str | None
author: str | None
class app.services.intake.IntakeMode(*values)

Bases: StrEnum

PUT (create or replace) or POST (create only).

REPLACE = 'put'
CREATE = 'post'
class app.services.intake.IntakeResult(*, record_id, digest, created, updated, deduplicated, issues)

Bases: object

Outcome of one candidate submission.

Parameters:
  • record_id (UUID) – Candidate row id.

  • digest (str) – Server-computed canonical digest.

  • created (bool) – A new candidate row was inserted.

  • updated (bool) – An existing candidate got new content.

  • deduplicated (bool) – The delivery id was seen before; nothing was ingested and this is the earlier result.

  • issues (tuple[Issue, ...]) – Per-document validation issues (non-blocking).

record_id: UUID
digest: str
created: bool
updated: bool
deduplicated: bool
issues: tuple[Issue, ...]
app.services.intake.submit_candidate(provider, sources, *, token, body, mode)

Authenticate, validate and ingest one pushed candidate.

Blocking (synchronous database calls): call it from a worker thread.

Parameters:
Return type:

IntakeResult

Returns:

The outcome.

Raises:

app.errors.AdrianoError – The refusals listed in the module docstring; each one is recorded as a failed intake_run.