Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ key inline** — this section is the decisions an operator has to make, not the
for a single Caddy/nginx). Without it every caller presents as the proxy's address and shares one
bucket; the log warns when it sees an `X-Forwarded-For` while this is unset. Anything Iris cannot
interpret warns and trusts nothing rather than failing startup.
- **Tagged PDFs — optional, off by default.** Set `tagged_pdf.command` to the `iris-pdf` command from
[equalify-iris-pdf](https://github.com/EqualifyEverything/equalify-iris-pdf), and a session made from
one PDF can return that PDF tagged, with its form filled in. The demo page shows this only when it is
on. Iris runs without it.

### One GitHub identity, and no sign-in

Expand Down Expand Up @@ -222,6 +226,8 @@ in the other direction: it has its own shared secret and 404s unless you set it.
| `POST /v1/sessions` | Create a session, upload images and/or PDFs (`multipart/form-data`) |
| `GET /v1/sessions/{id}` | Poll status |
| `GET /v1/sessions/{id}/output` | Fetch the HTML when ready |
| `GET /v1/sessions/{id}/fields` | The uploaded PDF's form fields (only with tagged PDFs on) |
| `POST /v1/sessions/{id}/pdf` | The uploaded PDF back, tagged, with its form filled in (only with tagged PDFs on) |
| `POST /v1/sessions/{id}/feedback` | Submit feedback, trigger a re-run |
| `POST /v1/sessions/{id}/close` | Finalize the session and clean tmp |
| `GET /v1/sessions/{id}/logs` | Fetch the run log (ndjson) |
Expand Down
14 changes: 14 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ storage:
# alternative for multi-instance deployments (not implemented in v1).
database: ./data/iris.sqlite

# Optional: give back the uploaded PDF, tagged for accessibility, with form fields filled
# in. Needs the `iris-pdf` command from
# https://github.com/EqualifyEverything/equalify-iris-pdf, which is not part of Iris.
# Blank (the default) turns it off, and Iris works as it does without it.
#
# With it on, a session made from one PDF keeps that PDF, and the demo page offers
# a form for the PDF's fields and "Download tagged PDF". Form values are personal data:
# they are used for one request, and Iris stores neither them nor the filled PDF.
tagged_pdf:
# The command to run, e.g. `iris-pdf` if it is on PATH.
command: ${IRIS_PDF_COMMAND}
# How long one PDF may take before the command is stopped.
timeout_seconds: 300

# Iris has ONE GitHub identity: the token below. The server holds it, callers never
# see it, and nobody signs in. Every session is owned by that account and every issue
# Iris files is filed under it.
Expand Down
67 changes: 63 additions & 4 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,8 @@ curl -s "$BASE/limits" | jq
"upload_per_minute": 12,
"max_upload_memory_mb": 256,
"window_seconds": 60
}
},
"tagged_pdf": false
}
```

Expand Down Expand Up @@ -740,7 +741,8 @@ are streaming: the byte total is checked before the body is read (or counted as
request declares no length), while the part count is refused during parsing, once a part past
`max_files` appears. `rate_limits` is how often you may ask
([Rate limits](#rate-limits-how-often-you-may-ask)), and is `null` on a deployment that does not
limit requests in the app — which means "not limiting", not "unknown".
limit requests in the app — which means "not limiting", not "unknown". `tagged_pdf` says whether
this deployment can [return a tagged PDF](#get-a-tagged-pdf-optional).

A PDF's **links survive**, which rasterizing alone would not manage: a link is an annotation
over the page rather than something drawn on it, so the page image carries the link text and
Expand Down Expand Up @@ -956,6 +958,55 @@ those references 404 until a consumer supplies the files. What a screen-reader u
browser will show broken images, and one that rewrites the `src`s has the log and the fragment to
match them against.

## Get a tagged PDF (optional)

A deployment that runs [equalify-iris-pdf](https://github.com/EqualifyEverything/equalify-iris-pdf)
can give you the original PDF back with tags, built from the HTML. The feature is off unless
`tagged_pdf.command` is set in the config. `GET /v1/limits` says whether it is on.

It works only for a session made from **one PDF**. Iris keeps that PDF so it can tag it. Sessions made
from images, or from several files, get `409 no_source_pdf`.

**Read the PDF's form fields:**

```bash
curl -s -H "$AUTH" "$BASE/sessions/$SID/fields" | jq '.fields[0]'
```
```json
{ "name": "applicant.name", "type": "text", "page": 1, "options": [], "required": true,
"readonly": false, "maxlen": 40, "editable": false, "multiSelect": false }
```

`type` is one of `text`, `checkbox`, `radio`, `combobox`, `listbox`, `button` or `signature`. A PDF with
no form gives `{"fields": []}`. It can answer `404`, `409 no_source_pdf`, `422` for a PDF the tagger
refuses, or `503 busy`, the same as below.

**Get the tagged PDF:**

```bash
curl -s -X POST -H "$AUTH" "$BASE/sessions/$SID/pdf" \
-H 'content-type: application/json' \
-d '{"values":{"applicant.name":"Jane Doe","applicant.consent":true}}' > tagged.json
jq -r .pdf tagged.json | base64 --decode > tagged.pdf
```

`values` is optional. Its keys are field names. A text field takes a string, a checkbox takes `true`
or `false`, and a multi-select list takes an array. Fields you leave out keep what the PDF had.
**The values are used for this one request and are never stored or logged.** The run log records
only which field names were given.

The answer is `{"filename", "pdf", "report"}`: the file name to save as, the PDF in base64, and the
tagger's report. `report.warnings` lists what the tagger could not do cleanly, such as a form field
it could not find in the HTML. The demo page turns each warning into a plain sentence.

The session must have finished (`ready_for_review` or `closed`), or you get `409 invalid_state`. The
PDF is tagged from the first pass's HTML for each page, before review. A page that failed extraction is
left out, and the report lists it as `page_not_in_html`. Errors from the tagger keep
its code: `400` for a bad value, `422` for a PDF it refuses (for example `encrypted`), and `504
timeout` after `tagged_pdf.timeout_seconds` (300 by default). Two tagger runs at once, counting
both `/fields` and `/pdf`, is the limit, so a third gets `503 busy` with `Retry-After`. A deployment without the feature answers
`404 tagged_pdf_unavailable`.

## Submit feedback (re-run)

Triggers a new run within the same session, with the feedback injected as a top-level
Expand Down Expand Up @@ -1000,8 +1051,8 @@ The events worth grepping for have a section each below, and the index is a link
the index when you have a `type` off a log line and want to know what it means; read a section when
you want to know what the field it names is for and what it costs.

**The index is the whole log.** `src/` emits **118** event types and every one of them has a section
below — **113** sections, because a few cover a pair of events that are only read together. So a
**The index is the whole log.** `src/` emits **120** event types and every one of them has a section
below — **114** sections, because a few cover a pair of events that are only read together. So a
`type` you cannot find here is not one the index skipped: it is a misread line, or a name `src/` no
longer emits.

Expand Down Expand Up @@ -1125,6 +1176,7 @@ emits fails it too.
| [`agent_issue_failed`](#agent_issue_failed) | A suggestion did not get filed, and which of the two calls failed |
| [`contribution_failed`](#contribution_failed) | The filing step threw, **after** `run_complete` |
| [`run_failed`](#run_failed) | The run threw, so there is **no document** |
| [`tagged_pdf` / `tagged_pdf_failed`](#tagged_pdf--tagged_pdf_failed) | A tagged PDF was made, or could not be |
| [`calibrate_call_failed`](#calibrate_call_failed) | One calibration verifier call threw — a tool's line, never a run's |

### `run_queued` / `run_dequeued`
Expand Down Expand Up @@ -4278,6 +4330,13 @@ which failure it was, and this `error` is written to match: with `blank: 0` it i
provider error, standing for all of them because that is the diagnosis; otherwise it counts the source
pages reported blank, which an empty document could not.

### `tagged_pdf` / `tagged_pdf_failed`

A [tagged PDF](#get-a-tagged-pdf-optional) was made, or the tagger refused. `ms` is how long it took.
`tagged_pdf` lists `fields_given`, the names of the fields that were filled in. The values are
never logged. `tagged_pdf_failed` has the tagger's `code` and `error`. An `internal_error`'s
message is left out, because it may quote a value.

### `calibrate_call_failed`

One verifier call in the calibration harness threw: `image` is the page, `defect` the seeded defect the
Expand Down
Loading
Loading