Lightning.Credentials.Scoping (Lightning v2.19.0-pre)

View Source

Verifies that project-scoped references belong to the project that owns them.

Given a project id and a list of references, returns the subset whose project_credential, keychain_credential or webhook_auth_method belongs to a different project. Issues read-only queries with no side effects, so it composes inside a caller's transaction and observes read-your-writes.

Existence is not this module's concern: ids that resolve to no row are not reported (the foreign-key constraint already covers non-existence). This function is about scoping only.

Summary

Types

Human-readable subjects for violation keys (e.g. ~s(job "sync")), used when a violation has no nested changeset to carry its error.

Functions

Attaches each violation whose key matches one of the given nested changesets (compared via key_fun) as a field error on that changeset. Returns the updated changesets together with the violations that matched none of them — rows already persisted outside the incoming change.

Fails a changeset over scoping violations. Violations that could not be attached to a nested changeset (rows persisted outside the incoming change) become base errors naming the offending row via descriptions, so the rejection stays diagnosable even when the change never touched the row.

Every job in a project, shaped as scoping refs for out_of_project_references/2. Joins across all the project's workflows (including soft-deleted ones — a document that soft-deletes a workflow while planting a cross-project ref on its job must still be scanned). key is the bare job id; label is always selected (callers that don't need it ignore it, as out_of_project_references/2 does).

The jobs of a single workflow, shaped as scoping refs. See job_refs_for_project/1.

The human-readable error message for a scoping violation on a given field.

Types

descriptions()

@type descriptions() :: %{optional(term()) => String.t()}

Human-readable subjects for violation keys (e.g. ~s(job "sync")), used when a violation has no nested changeset to carry its error.

field()

@type field() ::
  :project_credential_id | :keychain_credential_id | :webhook_auth_method_id

ref()

@type ref() :: %{
  :key => term(),
  optional(:label) => String.t() | nil,
  optional(:project_credential_id) => Ecto.UUID.t() | nil,
  optional(:keychain_credential_id) => Ecto.UUID.t() | nil,
  optional(:webhook_auth_method_id) => Ecto.UUID.t() | nil
}

violation()

@type violation() :: %{key: term(), field: field()}

Functions

attach_violations(changesets, violations, key_fun)

@spec attach_violations([Ecto.Changeset.t()], [violation()], (Ecto.Changeset.t() ->
                                                          term())) ::
  {[Ecto.Changeset.t()], [violation()]}

Attaches each violation whose key matches one of the given nested changesets (compared via key_fun) as a field error on that changeset. Returns the updated changesets together with the violations that matched none of them — rows already persisted outside the incoming change.

invalidate(changeset, unattached_violations, descriptions)

@spec invalidate(Ecto.Changeset.t(), [violation()], descriptions()) ::
  Ecto.Changeset.t()

Fails a changeset over scoping violations. Violations that could not be attached to a nested changeset (rows persisted outside the incoming change) become base errors naming the offending row via descriptions, so the rejection stays diagnosable even when the change never touched the row.

valid? is forced false regardless: field errors added to nested changesets that are re-attached with put_change/3 do not propagate to the parent.

job_refs_for_project(project_id)

@spec job_refs_for_project(Ecto.UUID.t()) :: [ref()]

Every job in a project, shaped as scoping refs for out_of_project_references/2. Joins across all the project's workflows (including soft-deleted ones — a document that soft-deletes a workflow while planting a cross-project ref on its job must still be scanned). key is the bare job id; label is always selected (callers that don't need it ignore it, as out_of_project_references/2 does).

job_refs_for_workflow(workflow_id)

@spec job_refs_for_workflow(Ecto.UUID.t()) :: [ref()]

The jobs of a single workflow, shaped as scoping refs. See job_refs_for_project/1.

out_of_project_references(project_id, refs)

@spec out_of_project_references(Ecto.UUID.t(), [ref()]) :: [violation()]

violation_message(atom)

@spec violation_message(field()) :: String.t()

The human-readable error message for a scoping violation on a given field.

Shared across every consumer of out_of_project_references/2 so the wording stays consistent wherever a cross-project reference is rejected.