Native Tool
License Affirmations

Ask the runner. Authorize the submission.
Record the agreement on each job.
John Chilton
The problem · issue #23261

A saved checkbox can speak
for the next runner

Author checks a box
→
Workflow saves it
→
Runner never sees it
Reruns can replay that parameter too.
The value survives; the runner’s affirmation is missing.
Make the agreement an execution requirement,
outside the saved tool inputs.
Tool authoring

Declare terms beside requirements

<tool ... profile="27.0">
  <requirements>
    <license_agreement id="license_agreement_path" version="1"
                       path="license_agreement_tool_license.txt">
      <label>Test Tool Non-Commercial License</label>
      <affirmation>
        I certify that I am not using this tool for commercial purposes.
      </affirmation>
    </license_agreement>
  </requirements>
</tool>
An adjacent UTF-8 terms file; inline <text> also works.
Profile 27.0+, login required, every declared agreement required.
Abbreviated test fixture · submission binding is the default.
Architecture

The server authorizes job creation

%%{init: {'theme':'base','flowchart':{'nodeSpacing':20,'rankSpacing':26,'padding':12},'themeVariables':{'fontFamily':'Atkinson Hyperlegible','primaryColor':'#25537b','primaryTextColor':'#ffffff','primaryBorderColor':'#2c3143','lineColor':'#58585a','fontSize':'18px','clusterBkg':'#f6f8fa','clusterBorder':'#dfe3e8'}}}%%
flowchart LR
  subgraph LOAD["Tool loading and discovery"]
    direction TB
    A["Tool XML + terms file"] --> B["ResolvedLicenseAgreement"]
    B --> C["Discovery API + run forms"]
  end
  subgraph RUN["Submission and job creation"]
    direction TB
    D["ToolExecutionContext"] --> E["ToolAccessPrecondition<br/>LicenseAcceptancePrecondition"]
    E --> F["LicenseAcceptanceManager<br/>stored terms + user events"]
    F --> G["Job +<br/>JobLicenseAcceptanceAssociation"]
  end
  LOAD -->|"affirmed hashes"| RUN
Access checks run first. A disabled Run button is the early feedback.
Identity and lifetime

Same terms. Explicit scope.

canonical affirmation + terms→versioned SHA-256
binds=“submission”
Default. Affirm for this work.
One submission can create many jobs.
A new rerun needs fresh authorization.
binds=“user”
May be remembered for this account.
Acceptance stays valid until revoked.
A one-time affirmation also works.
Changed terms or affirmation → new hash.
Same hash with mixed scopes → submission binding wins.
Walkthrough · license_agreement_path_tool

The runner sees the terms

Cropped Galaxy fixture tool form with non-commercial terms expanded, an unchecked affirmation, and Run Tool disabled
Review → affirm → submit the declared hash.
The job records one_time; the next submission prompts again.
Workflow execution

One prompt can authorize
a nested, delayed run

%%{init: {'theme':'base','themeVariables':{'fontFamily':'Atkinson Hyperlegible','primaryColor':'#25537b','primaryTextColor':'#ffffff','primaryBorderColor':'#2c3143','lineColor':'#58585a','fontSize':'18px'}}}%%
flowchart LR
  A["Walk every tool step<br/>including subworkflows"] --> B["Deduplicate hashes<br/>one prompt per agreement"]
  B --> C["Validate request<br/>pin on each invocation"]
  C --> D["Schedule step<br/>check and record on job"]
Two nested tool steps share a hash: one prompt.
One-time pins survive restarts; remembered acceptance is rechecked.
Revocation blocks later jobs relying on remembered acceptance.
Jobs already created keep running.
API and refusal handling

A refused run carries a reason

HTTP 403 · TOOL_EXECUTION_PRECONDITION_UNMET
err_code: 403009
unmet:
  - kind: license_agreement
    details:
      agreements: [ ...terms and declared hashes... ]
POST /api/tools and workflow invocations refuse immediately.
Forms refresh the agreements when a submission is refused.
POST /api/jobs is asynchronous:
the tool request can be accepted, then fail in Celery with the same structured reason.
Persistence · four new tables

Keep the terms and the authorization

Terms + account events
ToolLicenseAgreement
Canonical terms by hash.
ToolLicenseAcceptanceEvent
Append-only accept / revoke history.
Job + invocation associations
Each job: hash, authorization kind,
and an event link when persistent.
Each invocation: its one-time pins.
Purging an account removes its personal acceptance events.
Retained jobs keep the hash and authorization kind.
The database records job authorization; a public job audit API/UI is still future work.
Implementation snapshot · cd37d851bb2

The next proof is a real wrapper

Implemented: parsing, storage, API enforcement,
nested workflows, run forms, and preferences.
Recorded local checks cover backend, API, PostgreSQL cleanup,
and browser paths. No production wrapper has migrated.
Pilot one unconditional wrapper with its maintainers
and an operator reviewing the terms.
Decide the workflow-test default before Planemo support.
Conditional terms and alternative entitlements need a later design.

Extra Slides

Remembered acceptance

Users can review and revoke

Galaxy License Agreements preferences showing accepted test terms, a Revoke button, and the acceptance history table
For user-bound agreements only. Revocation affects future authorization.
Scope boundaries

Every declaration applies to every run

Supported
Unconditional agreements, combined with AND.
Shared suite terms, deduplicated by hash.
Direct execution of a licensed converter.
Needs another design
Input-dependent terms.
Academic use OR a purchased license.
Instance-wide administrator acceptance.
Data sources and actions that bypass preconditions fail to load.
Converters with agreements are excluded from automatic conversion.
Behavior pinned by tests

The job’s lifetime defines the boundary

Fresh authorization
New rerun or remap.
Later persistently authorized workflow steps.
A tool reloaded with changed terms.
Existing authorization
Resume a paused job.
Automatic resubmission of that job.
A delayed step using a matching one-time pin.
Workflow preflight includes when-skipped steps.
Local results are recorded in the branch debrief; full suites remain a CI concern.
API integration reference

Discover, display, then submit

GET  /api/tools/{id}/license_agreements
GET  /api/workflows/{id}/license_agreements

Show terms + affirmation to the runner.

POST /api/tools  |  /api/jobs  |  workflow invocation
     one_time_license_acceptances: [declared hash]

Optional for binds="user":
POST /api/users/current/license_acceptances
     tool_id + license_id + agreement_hash
Never accept client-supplied terms. Reject undeclared hashes.
Persistent acceptance must confirm the exact hash displayed.