---
title: "Host Verification & Approved Terms"
slug: host-verification-approved-terms
section: Portals
group: Host Portal
status: stable
description: "*Status: this page is a design spec and is largely not built."
last-updated: 2026-10-07
---

# Host Verification & Approved Terms

*Status: this page is a design spec and is largely not built. Built today: host document review (`host_documents.review_state`), the `host_offer_details` table, `partner_organizations.ein_number`, the `sponsor.host_orgs.activate` permission, a simpler host verification review (the `host_verifications` table, the `/api/admin/host-verifications` routes including `PATCH /api/admin/host-verifications/:id/review` gated by `site_visits.manage`, and the `host_verification` queue), and automatic Camp job-order creation at host-org creation (`campCounselorAutoJobOrder.ts`, audit actions `job_order.auto_created` and `job_order.auto_create_failed`). Not built: the section-level compliance review workbench described here (per-section states, findings, changed-since-review indicators), the `sponsor.host_verification.review`, `sponsor.host_verification.approve` and `sponsor.host_terms.amend` permissions, `host_approved_terms` revisions and host-side field locks, the approved-terms reason codes, hire-time enforcement (`APPROVED_TERMS_MISMATCH`; the Hub has no `POST /api/placements/:id/confirm` route), the rule reason code `HOST_TERMS_NOT_ACCEPTED`, the `host.document.valid` fact, and the `host.verification.section_decided`, `host.verification.review_completed`, `host.document.decision`, `host.approved_terms.*`, `placement.confirm_rejected` and `host.arrivals.updated` events. Where this page says `job_order_requirements` the table is `host_offer_details`, and `irs_ein_number` / `irsEinNumber` are `ein_number` / `einNumber`. Sections below describe the intended design unless stated otherwise.*

One compliance workflow for Host Company verification — the Globus review workbench, document-level review, approved-terms field locks, automated Camp job-order population, and hire-time enforcement of the approved values.

> **Who reads this**
>
> Globus Hub engineers (compliance workbench, approved-terms revisions, automated job orders, placement confirmation), Host Portal engineers (locked-field rendering, hire confirmation errors), and compliance staff. This page is the coordinating contract; the pages it reconciles remain authoritative for their own surfaces and are cross-referenced throughout.

## Purpose & Scope

Today host verification is documented as a rule-derived document outcome (Rules Engine → Camp Host Verification Policy), the Sponsor Review Dashboard is a list with an activate action, and hosts and staff freely dual-write the same Offer Details fields. Nothing defines how the values compliance approved become controlled inputs to automated Camp job orders and hire confirmation. This page closes that loop:

- Compliance gets a **structured review workbench** — a drill-in detail view comparable to the Job Order detail screen, with per-section states, findings, and a document-review area.
- **Seasonal salary, contract start date, contract end date, and EIN** become host-locked after compliance approval; only authorized Globus staff can amend them, and material amendments re-enter compliance review.
- **Automated Camp job orders** are populated deterministically from the approved host profile revision plus the canonical arrivals and housing data.
- **Hire confirmation** validates submitted salary and contract dates server-side against the effective approved terms before any placement side effects.

Throughout this page, "Camp" covers camp-model programs (program_model_type `camp`) plus the SWT-Camp catalog programs (`summer_work_travel`, `summer_work_travel_wildpacks`), per `isCampFamilyProgram` in `shared/campHostPrograms.ts`, as on Host Governance → Camp Program Divergence.

Out of scope: replacing the four-document Camp verification rules or the pass/fail/review mapping; new program salary thresholds; participant eligibility, matching scores, reservation duration, or capacity rules; the camp-wide arrival-information design itself (this page only consumes its canonical output).

## Authority Reconciliation

One authority per field. This table is normative; every other surface reads from — never competes with — the named owner.

| Concern | Single authority | This page adds |
| --- | --- | --- |
| Verification outcome (pass / fail / review_required) | Rules Engine → Camp Host Verification Policy (rule-derived, never human-set) | Nothing. The workbench feeds evidence facts; it never writes the outcome. |
| Document upload, storage, versions, expiry metadata | Host Document Center + Document Storage | A review surface over the same host_documents rows; document decisions set the existing review state the facts resolver already reads. |
| pending_verification → active transition | Host Governance → Sponsor Review (sponsor.host_orgs.activate) | The workbench is the drill-in behind the same dashboard rows; activation rules are unchanged. |
| Offer fields (salary_for_season, contract_start_date, contract_end_date) | Host Offer Details — one job_order_requirements row, two write surfaces | A post-approval host-side lock on the controlled subset; the row, validation, and events are unchanged for all other fields. |
| EIN (einNumber) | Host Camp Overview — partner_organizations.ein_number | The same post-approval host-side lock. |
| Arrivals / transportation | Host Arrivals — host_arrivals row per (host_org_id, program_id), and the camp-wide arrival information direction for group arrivals | Automated job orders consume the canonical row by reference plus a creation-time snapshot; no new write path. |
| Housing | Job Orders → housing gate and housing_leads | Automated job orders reference the applicable housing leads; the housing gate still reviews them. |
| Job-order approval | Job Orders → Three Approval Gates + prerequisites | Automated orders enter the same gate model; approved terms become the seed values the job_order gate reviews against. |
| Hire confirmation | Integration Contract → POST /api/placements/:id/confirm | A server-side approved-terms validation step that runs before the existing Reserved → Confirmed transition. |

> **The rule-derived outcome is never bypassed**
>
> Section approvals, document decisions, and the overall workbench decision are human evidence. The single verification outcome the camp DS-2019 guard consumes is still derived exclusively by the Rules Engine from its evidence facts. A compliance officer cannot "approve verification" directly — they approve documents and sections, which updates the facts, which lets the rule set derive pass.

## Compliance Review Workbench

### Queue Entry & Row Projection

A **Host Company Verification** readiness queue is added under the Partner Ops queue tree using the entity-agnostic queue_definitions model (entity_type `partner`, category `readiness`, no code change — configuration only, per Queue System → Zero Hardcode). Membership is derived, never stored:

- Host org is in pending_verification (a host enters it only when an account manager submits the host for verification — creating a host enqueues nothing: its host_verifications row starts `unsubmitted`, which is not worker-eligible and is invisible to the verification queue buckets) with at least one verification evidence fact not yet passing, **or**
- The Rules Engine outcome is review_required, **or**
- A previously approved review section has been invalidated (replaced/expired evidence, material amendment) — the changed-since-review path below.

The queue row follows the Queue System row contract projected for host orgs: Host Org (link to the workbench), Program Type(s), Verification Outcome (rule-derived chip: pass / fail / review_required), Sections Pending (n of m), Documents Awaiting Review (count), Changed Since Review indicator, Risk Score, SLA Status, Assigned Reviewer, Last Activity. Camp rows keep the existing "Operational — verification pending, DS-2019 blocked" chip from the Sponsor Review Dashboard.

### Detail View Layout

Clicking a queue row opens a structured detail view comparable to the Job Order detail screen: a header strip, a left rail of review sections, and a main panel per section. Header strip: host org identity, lifecycle state, rule-derived verification outcome chip, risk score, open flags, assigned reviewer, and the overall review decision control.

Review sections (each independently reviewable):

| Section | Source surface | What is reviewed |
| --- | --- | --- |
| Business Identity | Camp Overview (partner_organizations) | Legal name, address(es), website, year established, **EIN (controlled field)** |
| Compliance Documents | Host Document Center | All verification-relevant uploads — see Document Review Area |
| Operating Agreement & Terms | Agreement acceptance record | Accepted T&C evidence (record review, not an upload) |
| Offer Terms | Host Offer Details (job_order_requirements) | **Seasonal salary, contract start date, contract end date (controlled fields)**, salary frequency, position types |
| Arrivals & Housing | Host Arrivals + housing leads | Transportation projection, camp group-arrival data, housing references |
| Automated Verification Signals | Host Governance automated verification | Web-presence / AI risk signals — advisory, read-only |

### Section States & Findings

Every section carries exactly one state, rendered on the rail and the header:

| State | Meaning |
| --- | --- |
| pending | Not yet reviewed at the current source revision |
| approved | Reviewer approved this section at a recorded source revision |
| rejected | Reviewer rejected with a reason; host/staff remediation expected |
| changed | Was approved, but the underlying data changed since — approval invalidated, back in review |

Reviewers attach **findings** to a section: free-text note (reason required, ≥ 10 characters on reject), optional severity, optional deep link to the offending field. Section decisions record reviewer, decision, reason, timestamp, and the source revision reviewed (see Data Model). Findings and decisions are immutable — corrections append, never edit.

The **overall review decision** (approve / reject) becomes available only when every section is approved and the rule-derived outcome is pass. Overall approval is what freezes the approved-terms revision (below). Overall rejection routes to the existing sponsor remediation path on Host Governance.

### Rule Outcomes vs Human Approval

Two distinct record families, never conflated:

- **Calculated rule outcome** — the Rules Engine evaluation (pass / fail / review_required) with its reason codes (HOST_DOC_*_INVALID, HOST_TERMS_NOT_ACCEPTED). Displayed read-only in the workbench header with per-rule evidence drill-down. Owned by the Rules Engine.
- **Human approval evidence** — section decisions and document decisions recorded by a named reviewer. Document decisions are inputs to the rule facts (review state feeds host.document.valid); section and overall decisions are compliance workflow records that gate the approved-terms freeze and the sponsor activation checklist, not the rule outcome.

### Changed-Since-Review Indicators

When data underlying an approved section changes — a document is replaced or expires, a host edits a reviewed profile field, staff amend a controlled field — the section flips to **changed**, the host re-enters the Host Company Verification queue, and the row shows the Changed Since Review indicator with a diff against the reviewed source revision. Prior decisions are never deleted; a new decision at the new revision supersedes them.

### Workbench RBAC

| Action | Permission | Default roles |
| --- | --- | --- |
| View workbench & documents | sponsor.host_verification.review | Compliance Officer, Sponsor Admin, Verification Reviewer |
| Section / document decisions | sponsor.host_verification.review | Compliance Officer, Verification Reviewer |
| Overall review decision | sponsor.host_verification.approve | Compliance Officer lead, Sponsor Admin |
| Amend locked approved terms | sponsor.host_terms.amend | Sponsor Admin (delegable) |
| Activate host org | sponsor.host_orgs.activate (existing) | Sponsor Admin |

Approval and activation remain separate acts: overall review approval feeds the Activation Gate Checklist; the sponsor activation transition on Host Governance is unchanged.

## Document Review Area

### Document List & Metadata

The Compliance Documents section lists **every** host-scoped compliance document required or supplied for verification, grouped by requirement, sourced from the same host_documents rows and the shared getHostDocumentRequirements resolver the Host Portal rollup uses — the workbench never re-derives status locally. Per document the workbench shows: category, original filename, upload date, replacement date (when part of a previous_document_id chain), uploadedSource (host_portal / globus_admin), expiry date and daysUntilExpiry, policy metadata (policy number, coverage, insurer — where the category carries it), current review state (pending_review / approved / rejected / resubmission_requested / expired), antivirus/hash status, and — for reviewers holding the permission — the prior-version chain with each version's decision history.

### Preview & Download Access

Preview and download use the canonical proxied access model from Document Storage: a short-lived signed GET URL minted per request after the sponsor.host_verification.review permission check, never a durable or raw GCS URL, with every access audit-logged. Unauthorized document ids return 404, never 403.

### Per-Document Decisions

Three decisions per document version, each requiring a reason on the negative paths:

| Decision | Effect on host_documents.review_state | Effect on facts |
| --- | --- | --- |
| Approve | approved | host.document.valid(category) can derive true (if unexpired, policy info present) |
| Reject (reason ≥ 10 chars) | rejected | fact derives false; reason surfaces in Host Portal document center |
| Request replacement (reason ≥ 10 chars) | resubmission_requested | fact derives false; host notified with the reason and re-upload deep link |

Decisions are per document **version** — approving a replacement never retroactively alters the superseded version's record. Every decision records reviewer, decision, reason, timestamp, documentId, and version.

### Evidence Invalidation

Replaced, expired, or newly uploaded evidence invalidates the applicable prior document approval automatically:

- **document.replaced** — the new version enters review state pending; the prior approval stays attached to the prior version only; the owning section flips to changed.
- **Expiry** — when expiring_soon crosses to expired (shared resolver precedence), the fact derives false, the section flips to changed, and the rule outcome recomputes; a lapsed required document can drop the outcome from pass.
- **New upload in a reviewed category** — same as replacement: pending review state, section to changed.

Invalidation is fact-driven and symmetric with the Rules Engine: the workbench displays what the facts resolver derives; it never suppresses a recomputation.

## Approved Terms & Revisions

### Controlled Fields

Exactly four fields become controlled after overall review approval:

| Controlled field | Owning surface & column | Wire name |
| --- | --- | --- |
| Seasonal salary | host_offer_details.salary_for_season | salary_for_season |
| Contract start date | host_offer_details.contract_start_date | contract_start_date |
| Contract end date | host_offer_details.contract_end_date | contract_end_date |
| EIN | partner_organizations.ein_number | einNumber |

No other Offer Details or Camp Overview field is affected — the existing dual-write, If-Match, validation, and event contracts on those pages continue to govern everything else.

### Approval Snapshot & Revisions

Overall review approval freezes a **host_approved_terms revision**: an immutable row capturing the four controlled values (per program for the offer fields; org-level for EIN), the source row versions reviewed, the reviewer, and the approval timestamp. Revisions are numbered per (host_org_id, program_id); exactly one revision is **effective** at a time. States: **pending_review → approved → superseded | invalidated**, and nothing else. The **effective** revision is defined as the most recent revision with status `approved`; a revision keeps status `approved` — and remains the effective revision — for the entire time a later revision sits in `pending_review`. It transitions to `superseded` only at the moment a newer revision is approved, and to `invalidated` only on evidence invalidation without a replacement decision. Prior revisions and the evidence reviewed against them are never overwritten.

### Host Portal Lock Behavior

After a revision is approved:

- **Reads** — GET /api/partner/host/offer-details and GET /api/partner/host/overview gain an additive, read-only `approvedTerms` block: the effective approved values, revision number, approval timestamp, and per-field lockState (`locked` / `pending_review` / `unlocked`). The Host Portal renders the lock state next to each controlled field.
- **Writes** — the host-facing PATCH on either surface that includes a controlled key while it is locked is rejected atomically with HTTP 409, reason code `approved_terms_locked`, and per-field details naming each locked key and the effective revision. No partial application: one locked key rejects the whole PATCH. host_readonly semantics, ownership scoping, and all other keys are unchanged.
- Before first approval (no effective revision), lockState is `unlocked` and host writes behave exactly as the existing contracts document.

### Staff Amendments & Re-Review

Only Globus staff holding sponsor.host_terms.amend may change a locked value, via the existing Globus-internal PATCH surfaces plus two additional requirements: an amendment **reason ≥ 10 characters** and the standard If-Match precondition. Every amendment of a controlled field is **material** and atomically:

1. Creates a new host_approved_terms revision in pending_review carrying the amended values. The prior revision keeps status `approved` and **remains the effective revision** — it is what portals display, job-order automation seeds, and hire-time enforcement compares against — until the pending revision is itself approved (prior → `superseded`) or rejected (prior stays `approved` and effective).
2. Flips the affected workbench section(s) to changed and re-enters the host into the Host Company Verification queue.
3. Emits host.approved_terms.amended with actor, reason, before/after diff, and both revision numbers.
4. Never overwrites the previously approved evidence — the prior revision, its snapshot, and its decisions stay immutable.

### Concurrency & Pending Revisions

While a revised value is pending re-review: the Host Portal shows the **last approved** values as effective, plus a `pending_review` lockState and the pending values labeled as under review; automated job-order creation and hire-time enforcement continue to resolve the last **approved** revision — pending values are never enforced or seeded. Optimistic concurrency: amendments require If-Match against the underlying row exactly as the existing surfaces do; two concurrent staff amendments serialize via the precondition — the loser receives 409 with the current state and rebases.

### Audit History

Every section decision, document decision, overall decision, lock rejection (the 409 itself is audited with the attempted diff), amendment, and revision transition writes an audit row with the standard actor + reason + timestamp vocabulary under `host_verification.*` / `host_terms.*` actions. History is append-only and queryable from the Audit Console; the workbench renders it as an immutable timeline per host.

The merged trail is also served read-only by `GET /api/admin/partners/organizations/:orgId/verification-activity` (`partners.view`, API-key scope `partners.read`). It combines the audit rows for the organization and each of its verifications with the verification records and automated verification attempts into one chronological list answering who approved the host, when, and on what evidence. Actor ids are resolved to display names, automated actors appear as System (automated verification), and a failed name lookup falls back to raw ids. The response is `items` and `total`; 404 for an unknown organization, 403 `FORBIDDEN` when the caller's organization scope excludes it.

## Automated Camp Job-Order Population

### Prerequisites

Automated creation of a Camp job order for a (host_org_id, program_id, season, position_type) requires, at creation time:

- An effective **approved** host_approved_terms revision for the program (pending_review does not qualify).
- An accepted, in-window operating agreement (existing portal-entry condition).
- A host_arrivals row for the (host_org_id, program_id) — including the canonical camp group-arrival output of the camp-wide arrival-information design.
- An active position-type code for the program from the Program Register lookup.

Housing leads are referenced when present but are not a creation prerequisite — the housing gate owns that review.

### Field Mapping

Population is deterministic — same inputs, same order, no defaults invented:

| Job-order input | Source | Semantics |
| --- | --- | --- |
| salary_for_season | Approved terms revision (effective) | Snapshot at creation, with sourceRevision recorded |
| wage / wage_frequency | job_order_requirements (Host Offer Details row) | Copy at creation — **not** controlled fields; they stay host-editable under the existing Offer Details contract and are never sourced from or validated against the approved revision |
| contract_start_date / contract_end_date | Approved terms revision (effective) | Snapshot at creation, with sourceRevision recorded |
| position_type, position_title, time_off_policy, benefits | job_order_requirements (Host Offer Details row) | Copy at creation; non-controlled fields follow the existing offer contract |
| participant_transportation block | host_arrivals projection (+ camp group-arrival data) | Reference to the canonical row plus a creation-time snapshot for the order record |
| Housing references | job_order_housing_leads → housing_leads | Reference only — housing gate reviews live data |
| EIN (compliance namespace) | Approved terms revision (org-level) | Snapshot with sourceRevision |

### Snapshot vs Reference

Approved offer terms and EIN are **snapshotted** onto the order with `sourceRevision` metadata (revision number + approval timestamp) so the order is auditable against exactly what compliance approved. Arrivals and housing are **referenced** — their canonical rows keep serving reads — with a creation-time snapshot retained on the order for audit only, never re-served as current data.

### Idempotency & Failure Handling

Creation is idempotent on (host_org_id, program_id, season, position_type): re-running the automation returns the existing order rather than duplicating it. Missing prerequisites **fail explicitly** — no partial or placeholder order is created — with machine-readable reason codes: `approved_terms_missing`, `approved_terms_pending_review`, `arrivals_missing`, `position_type_inactive`, `agreement_not_active`. Failures are surfaced in the ops queue and audit log; the automation never silently substitutes defaults.

### Later Profile Changes

When a newer approved-terms revision becomes effective after orders exist:

- **Draft** orders are refreshed in place to the new revision (sourceRevision updated, refresh audited).
- Orders **in review or active** are never silently rewritten — the revision change raises a job-order **amendment** through the existing amendment flow (diff against the order's sourceRevision), re-opening the job_order gate for the affected fields.
- A pending (not yet approved) revision changes nothing downstream.

## Hire-Time Enforcement

### Effective Approved Terms Resolution

The authoritative server-side hire flow — POST /api/placements/:id/confirm (Integration Contract) — resolves, before any state change: the placement's job order → the order's sourceRevision → the host's **currently effective approved** revision. If the order's snapshot is stale relative to the effective revision, enforcement compares against the **effective** revision and flags the staleness (`approved_terms_revision_stale`) in the audit metadata so ops can raise the amendment; the hire is still validated against the effective approved truth.

### Validation & Rejection

Submitted seasonal salary and contract start/end dates are normalized (seasonal salary — the single per-season USD amount `salary_for_season`, the only monetary controlled field — to an exact decimal comparison; dates to ISO-8601 calendar dates) and compared to the effective approved terms. Base wage and wage frequency are not controlled fields and are not validated here. Any mismatch rejects the confirmation **atomically before all side effects** — no Reserved → Confirmed transition, no capacity counter movement, no placement.confirmed event, no notifications. If no effective approved revision exists for a Camp host's program, confirmation is refused with `approved_terms_missing` (fail closed).

### Error Contract

Rejections return HTTP 422 with the standard error envelope, stable code `APPROVED_TERMS_MISMATCH`, and per-field errors — field name, submitted value, approved value, approved revision number — so the Host Portal can render actionable inline errors. Every rejection writes an audit row carrying the policy source metadata (revision number, approval timestamp, comparison basis) as evidence. The existing 422 `PLACEMENT_INVALID_TRANSITION` and idempotency semantics of the confirm endpoint are unchanged and evaluated after terms validation passes.

### Host Portal Offer-Period Pre-Check

Separately from the Hub-side approved-terms enforcement above, the Host Portal server validates the hire dates itself before it submits the hire to Globus (POST /api/v2/reservations/:id/hire, `assertHireDatesWithinOfferPeriod` in the reservation hiring facade). After re-reading the reservation and confirming it is still active or confirmed, it re-resolves the applicable Offer Details record for the reservation's program and job order (offer id equals job order id) and requires the submitted `start_date` and `end_date` to be valid ISO-8601 timestamps that fall inside the offer's inclusive contract period (compared as UTC calendar dates). No Globus hire mutation is sent when this check fails. Rejections are HTTP 422 with one of two codes:

| Code | Raised when |
| --- | --- |
| `OFFER_PERIOD_UNAVAILABLE` | The validation cannot be performed honestly: the reservation's program cannot be determined, the Offer Details lookup fails or returns no usable data, no offer row matches the job order, or the offer's contract period cannot be resolved. Validation is never silently skipped. |
| `HIRE_DATES_OUT_OF_RANGE` | A submitted date is not a valid ISO-8601 timestamp, the hire start date is before the offer's contract start date, or the hire end date is after the offer's contract end date. |

This pre-check compares against the host's current Offer Details row, not against the effective approved-terms revision, and does not validate salary.

## Data Model

New tables (Globus Hub — Zero Hardcode conventions, additive only):

| Table | Purpose | Key columns |
| --- | --- | --- |
| host_verification_reviews | One row per review cycle per host org | id, host_org_id, status (open / approved / rejected), opened_at, closed_at, closed_by |
| host_verification_section_decisions | Immutable section decisions | id, review_id, section, decision (approved / rejected), reason, reviewer_id, source_revision, decided_at |
| host_verification_findings | Findings attached to sections | id, review_id, section, note, severity, field_ref, author_id, created_at |
| host_document_decisions | Immutable per-version document decisions | id, document_id, document_version, decision (approve / reject / request_replacement), reason, reviewer_id, decided_at |
| host_approved_terms | Versioned controlled-field revisions | id, host_org_id, program_id, revision, salary_for_season, contract_start_date, contract_end_date, irs_ein_number, status (pending_review / approved / superseded / invalidated), source_snapshot, approved_by, approved_at, amendment_reason |

The existing host_documents, job_order_requirements, partner_organizations, host_arrivals, and job_orders tables gain **no** breaking changes; job orders gain additive sourceRevision metadata.

**Existing tables in use today.** `host_verification_attempts` is the append-only history of verification runs for a host, one row per run: `host_verification_id` (foreign key to `host_verifications`, cascade delete), `host_org_id`, `status`, `confidence_score`, `evidence_summary`, `search_results` (JSON), `recommended_status` (`verified`, `failed` or `needs_review`; null for human decisions and runs with no assessment), `triggered_by_job_order_id`, `run_by`, `source` (default `auto`) and `created_at`. The parent `host_verifications` row keeps the latest status. `host_operating_agreement_dismissals` records when a host portal user dismissed the unsigned operating agreement modal: `partner_org_id`, `partner_user_id`, `agreement_id` (null when no agreement existed yet) and `dismissed_at`, unique on user and agreement, so a new agreement prompts again. It is written by `POST /api/partner/host/operating-agreement/dismiss` (host role admin or recruiter, scope `agreements.write`), which is idempotent and returns 400 `MISSING_HOST_USER` unless the `X-Host-User-Email` header matches an active host user.

## Events

All additive, on the standard event outbox, host.* prefix (employer.* is retired):

| Event | Fires when |
| --- | --- |
| host.verification.section_decided | A section is approved or rejected (payload: section, decision, reason, reviewer, source_revision) |
| host.verification.review_completed | Overall decision recorded (decision, review_id, revision frozen on approval) |
| host.document.decision | Per-document approve / reject / request_replacement (documentId, version, decision, reason) |
| host.approved_terms.approved | A revision becomes effective (revision, values, approver) |
| host.approved_terms.amended | Staff amendment creates a pending revision (before/after diff, reason, both revisions) |
| host.approved_terms.lock_rejected | A host PATCH was refused with approved_terms_locked (fields attempted) |
| job_order.auto_created / job_order.auto_create_failed | Audit-log actions (not outbox events) written by automated Camp creation at host-org creation, on success / failure |
| placement.confirm_rejected | Hire confirmation refused with APPROVED_TERMS_MISMATCH (field diffs, revision) |

Existing events (document.uploaded / document.replaced, host.offer.updated, host.arrivals.updated, placement lifecycle) are unchanged; host.offer.updated simply never fires for a rejected locked write.

## Acceptance Scenarios

| # | Scenario | Expected |
| --- | --- | --- |
| 1 | Reviewer opens a pending host from the Host Company Verification queue | Detail view renders all six sections with per-section states, rule outcome chip, and findings panel |
| 2 | Document visibility & access control | Reviewer with sponsor.host_verification.review sees every verification document with full metadata and version chain; preview/download mint short-lived signed URLs and audit the access; unauthorized ids return 404 |
| 3 | Document approval | Approve sets review state verified, host.document.valid can derive true, rule outcome recomputes |
| 4 | Document rejection / replacement request | Status rejected / resubmission_requested with reason; host notified; fact derives false |
| 5 | Stale or replaced evidence | document.replaced or expiry flips the section to changed, invalidates the prior document approval for the new version, host re-enters the queue |
| 6 | Overall approval | Available only when all sections approved AND rule outcome pass; freezes an approved revision; approvedTerms block appears in Host Portal reads with lockState locked |
| 7 | Locked host edit | Host PATCH containing salary_for_season (or any controlled key) returns 409 approved_terms_locked with per-field details; no partial write; audit row recorded |
| 8 | EIN lock | Camp Overview PATCH with einNumber after approval → same 409; non-controlled keys in a separate PATCH still succeed |
| 9 | Internal amendment & re-review | Staff PATCH with reason creates pending revision, section flips to changed, host re-queued, prior revision and evidence untouched |
| 10 | Pending revision behavior | Host Portal shows last approved values as effective with pending_review lockState; job orders and hires keep using the approved revision |
| 11 | Job-order seeding | Automated Camp order snapshots approved terms with sourceRevision, references arrivals/housing; idempotent re-run returns the same order |
| 12 | Seeding failure | Missing arrivals row → explicit approved-terms-style failure (arrivals_missing), no partial order |
| 13 | Stale revision | Newer approved revision → drafts refresh in place; active orders raise an amendment through the existing flow |
| 14 | Hiring mismatch | Confirm with salary differing from effective approved terms → 422 APPROVED_TERMS_MISMATCH with field-level submitted vs approved values and revision metadata; no placement side effects |
| 15 | Hiring match | Values equal after normalization → existing Reserved → Confirmed flow proceeds unchanged |
| 16 | Wage source of truth | Automated order copies wage / wage_frequency from the Host Offer Details row, salary_for_season from the approved revision; hire-time enforcement validates only salary_for_season and contract dates — a wage change alone never triggers APPROVED_TERMS_MISMATCH |
| 17 | Effective revision during pending amendment | With revision 3 approved and revision 4 pending_review: revision 3 keeps status approved and is resolved as effective by portal reads, job-order seeding, and hire enforcement; approving 4 flips 3 to superseded, rejecting 4 leaves 3 approved and effective |

## Cross-References

- Rules Engine → Camp Host Verification Policy — owns the verification outcome and evidence facts this workbench feeds.
- Queue System — queue_definitions model, row contract, and document review workflow this queue instantiates.
- Host Governance — Sponsor Review Dashboard, activation gate, camp divergence, and the failure/halt path.
- Host Offer Details / Host Camp Overview — the write contracts the controlled-field locks extend.
- Host Arrivals — the canonical arrivals row automated orders reference.
- Job Orders — approval gates, prerequisites, and the amendment flow automated orders reuse.
- Integration Contract — Host Document Center and the placement confirm endpoint.
- Document Storage — proxied preview/download and version-chain semantics.

## Implementation Follow-up

Implementation is owned by the Globus Hub and Host Portal repos, not this docs site. Cross-referenced tickets are filed per the house standard (docs/asana-ticket-standard.md):

- **Globus Hub** — Host verification compliance workbench (queue definition, detail projection, section/document decision endpoints, RBAC); approved-terms revision model with host-side field locks and staff amendment/re-review routing; automated Camp job-order population with sourceRevision snapshots and idempotency; hire-time approved-terms enforcement on the placement confirm path.
- **Host Portal** — locked-field rendering of the approvedTerms block (lock/pending states) on Offer Details and Camp Overview; actionable APPROVED_TERMS_MISMATCH field errors on hire confirmation.
