# Manual Translation Protection + Auto-translation Triggers

Audience: AI coding agents first.

Date: 2026-08-09  
Plan slug: `manual-translation-lock-auto-translate`

## 1. Outcome

Build one overwrite-safety policy across content + string translations. NEVER let automatic or administrator-triggered generation replace protected manual work. Add configurable event-driven translation behavior for content, plugins, themes. Default to no automatic execution and stale-only behavior.

User-visible guarantees:

1. Global manual-protection setting supplies default.
2. Each translation MAY override global default: inherit, protected, unprotected.
3. Existing translations migrate to `inherit`.
4. Manual saves record manual provenance.
5. Every generation path checks protection before queue and before persistence.
6. Protected target NEVER changes until explicitly unlocked.
7. Source-change automation supports selected content/plugin/theme events.
8. Default automation behavior marks affected translations stale; it spends no credits.
9. UI explains every control with visible helper text plus accessible tooltip details.

## 2. Scope

### MUST build

- Post, page, custom-post-type content translations.
- Theme/plugin string translations.
- Dashboard content manual-translation modal.
- Dashboard string manual-translation modal.
- WordPress editor UI for translated posts/pages/custom post types.
- Global settings UI + REST persistence.
- Synchronous generation, async generation, bulk actions, Generate All, editor generation, callbacks, finalizers.
- Post/page/custom-post-type lifecycle triggers.
- Plugin/theme installation, update, and file-editor triggers.
- Durable event queue, idempotency, stale detection, bounded reconciliation.
- Migration, API contracts, unit/integration/E2E coverage, accessible UI.

### MUST NOT build

- Translation model selection.
- Protected-translation bypass action. Administrator MUST unlock first.
- Automatic deletion of removed source strings or duplicate content.
- Translation on drafts, autosaves, revisions, failed installs, failed updates, or unchanged content.
- Backend API protocol changes unless existing job metadata cannot carry required hashes; prefer plugin-owned metadata.

## 3. Product Decisions

| Decision | Contract |
|---|---|
| Global protection | Default + per-translation override |
| Translation domains | Content + strings |
| Existing translation migration | `inherit` global default |
| Automation control | Option A: master switch + separate behavior selector |
| Default master switch | Off |
| Default behavior | `mark_stale` |
| Default event checkboxes | Unchecked |
| Automatic behavior | `translate`: generate missing + refresh existing unlocked targets |
| Protected generation | Skip; NEVER bypass |
| Delivery authorization | Resumed 2026-08-09; autonomous factory build, test, and quality run authorized |

## 4. Canonical Terms

- **Manual translation**: target text saved by user through any supported manual-edit surface or direct WordPress editor.
- **Generated translation**: target text persisted from synchronous/async generation or callback.
- **Protected**: generated content MUST NOT replace target.
- **Override**: `inherit | locked | unlocked`.
- **Effective lock**: result of override, provenance, and global default.
- **Stale**: source changed after target's recorded source fingerprint.
- **Source event**: normalized meaningful change eligible for configured automation.
- **Translate behavior**: generate missing targets; refresh existing unlocked stale targets.
- **Mark-stale behavior**: update stale state only; NEVER queue translation.

Do NOT conflate protection with stale state. Protected translations MAY become stale; protection blocks replacement, not change detection.

## 5. Architecture

### 5.1 `TranslationProtectionPolicy`

Deep policy seam. All callers MUST use it. NEVER duplicate lock rules in controllers, bulk handlers, scanners, or finalizers.

```text
resolveEffectiveLock(
  override: inherit|locked|unlocked,
  provenance: manual|generated|unknown,
  globalProtectManual: bool
): bool

canPersistGeneratedResult(
  targetIdentity,
  queuedTargetFingerprint|null,
  currentTargetFingerprint|null,
  currentLockState
): Allow | SkipProtected | SkipTargetChanged
```

Rules, ordered:

1. `locked` => protected.
2. `unlocked` => unprotected.
3. `inherit` + provenance `manual` => global default.
4. `inherit` + provenance `unknown` => global default.
5. `inherit` + provenance `generated` => unprotected.
6. Any target fingerprint change after queue => `SkipTargetChanged`, even when unprotected. New request required.
7. Completion-time result overrides queue-time permission.

### 5.2 `AutoTranslationCoordinator`

Own source-event normalization, deduplication, behavior dispatch, job metadata, summaries. Trigger adapters MUST NOT translate directly.

```text
handle(event: SourceChangeEvent, settings: AutoTranslationSettings): EventOutcome
reconcile(cursor, batchSize): ReconciliationOutcome
```

Responsibilities:

- Reject disabled/unselected/unchanged events.
- Compute stable source fingerprint.
- Coalesce duplicate events.
- `mark_stale`: update affected existing targets only.
- `translate`: queue missing targets + unlocked stale targets.
- Snapshot source + target fingerprints per job.
- Return counts: missing, stale, queued, protected, unchanged, failed.
- Schedule bounded retry for transient failures.
- NEVER retry policy skips as failures.

### 5.3 Trigger adapters

Keep thin. Emit normalized `SourceChangeEvent`; no overwrite policy.

- `ContentChangeTrigger`: post/page/custom post type.
- `ExtensionChangeTrigger`: plugin/theme install/update/file-editor save.

Keep reconciliation inside `AutoTranslationCoordinator.reconcile(...)`; a separate reconciliation adapter would be decorative.

Deletion test: removing adapters scatters WordPress lifecycle parsing into coordinator. Keep adapters.

### 5.4 Existing persistence owners

Extend existing `ContentManager`, string repository/scanner, job receiver, and finalizer. Do NOT add single-implementation repository interfaces.

Persistence MUST provide:

- read/write lock state;
- provenance updates;
- source/target fingerprint reads;
- atomic completion guard;
- stale-state transition;
- protected/changed skip result.

### 5.5 Event + job flow

```mermaid
flowchart LR
  A[WordPress lifecycle event] --> B[Trigger adapter]
  B --> C[AutoTranslationCoordinator]
  C --> D{Configured behavior}
  D -->|mark_stale| E[Persist stale state]
  D -->|translate| F[Queue-time policy + fingerprints]
  F --> G[Translation job]
  G --> H[Completion-time policy + fingerprints]
  H -->|allowed| I[Persist generated result]
  H -->|protected/changed| J[Record skipped outcome]
  K[Scheduled reconciliation] --> C
```

## 6. Data Contracts

### 6.1 Global settings

Canonical settings contract:

```json
{
  "protect_manual_translations": true,
  "auto_translation": {
    "enabled": false,
    "behavior": "mark_stale",
    "triggers": {
      "edit_post": false,
      "new_post": false,
      "edit_page": false,
      "new_page": false,
      "edit_custom_post_type": false,
      "new_custom_post_type": false,
      "edit_update_plugins": false,
      "new_plugins": false,
      "edit_update_themes": false,
      "new_themes": false
    }
  }
}
```

Validation:

- Unknown behavior => reject REST write with field error.
- Missing booleans => preserve current value on partial write; use defaults on reset.
- Master enabled + zero selected triggers => reject save with actionable validation.
- `mark_stale` MUST NOT invoke external translation API.
- Legacy `ipz_auto_translate` / `auto_translate_on_publish` input remains accepted during compatibility window.
- Legacy true MUST NOT silently arm new paid behavior. Migration sets new master off and shows one dismissible review notice.

Add canonical accessors to `Translation\Settings`; controllers MUST NOT read raw options independently.

### 6.2 Per-translation lock state

Expose one shape for content + strings:

```json
{
  "lock": {
    "override": "inherit",
    "effective": true,
    "provenance": "manual"
  }
}
```

Storage:

- Content translation mapping: nullable lock override + provenance.
- String translation row: nullable lock override; existing `is_auto_translated` remains compatibility data, with canonical provenance derived/migrated.
- Existing rows: override `NULL` / `inherit`; provenance `unknown` where evidence cannot prove origin.
- Manual save: provenance `manual`; omitted override preserves existing state; new manual row defaults `inherit`.
- Generated save: provenance `generated`; NEVER reset explicit override.

Required values:

- Database override: `NULL` inherit, `1` locked, `0` unlocked.
- API override: `inherit | locked | unlocked`.
- Provenance: `manual | generated | unknown`.

### 6.3 Fingerprints + stale state

Content source fingerprint MUST cover canonical translated fields:

- title;
- excerpt;
- content;
- configured translatable ACF values;
- stable field ordering + normalization.

Target fingerprint MUST cover same target fields. Ignore timestamps, revision IDs, status-only changes, and plugin metadata.

String source fingerprint MUST cover stable identity + original string + context/domain/source identity. A changed original string under same stable key makes targets stale. A disappeared key becomes orphaned/absent; NEVER delete translation automatically.

Reuse content `content_hash` + `translation_status='needs_update'` where semantics match. Extend string persistence with distinct stale state; NEVER overload `needs_review` because review workflow and source freshness differ.

### 6.4 Job metadata

Each generated job MUST retain:

```json
{
  "trigger_event_id": "stable-id",
  "source_fingerprint": "sha256",
  "target_fingerprint_at_queue": "sha256-or-null",
  "target_identity": "content-or-string-identity",
  "requested_behavior": "translate",
  "requested_at": "UTC timestamp"
}
```

Finalizer MUST load current lock + fingerprints. Snapshot permission alone is insufficient.

Job terminal outcomes:

- `completed`;
- `skipped_protected`;
- `skipped_target_changed`;
- `failed`.

Bulk/UI summaries MUST count skipped outcomes separately from failures.

### 6.5 Concurrency + uniqueness

Completion MUST serialize by target identity. Use database transaction/row lock or equivalent cross-process durable lock; process-local mutex is forbidden.

Content migration MUST enforce one translation mapping per `(element_type, translation_group_id, language_code)`. Before unique-index creation:

1. Detect duplicates.
2. Preserve every post.
3. Keep deterministic canonical mapping.
4. Move extra mappings into recovery groups; record migration audit detail.
5. Add unique index.

NEVER delete duplicate posts or translation text during migration.

## 7. Source Trigger Semantics

### 7.1 Shared trigger rules

All trigger checkboxes apply only when `auto_translation.enabled=true`.

For every event:

1. Require successful persisted source change.
2. Require selected trigger.
3. Exclude translated targets, revisions, autosaves, trash, unsupported types, plugin-induced writes, and unchanged fingerprints.
4. Deduplicate retries and repeated hooks.
5. Apply selected behavior to every configured target language.
6. Skip source/default language.
7. Report protected targets; do not fail entire event.

### 7.2 Exact UI checkbox labels + functionality

Preserve these literals exactly:

| Label | Event contract |
|---|---|
| `Edit post` | Existing published standard post gets meaningful translated-field/ACF change |
| `New post` | Standard post reaches published state first time |
| `Edit Page` | Existing published page gets meaningful translated-field/ACF change |
| `New Page` | Page reaches published state first time |
| `Edit Custom Post Type` | Existing published configured translatable CPT, excluding post/page, gets meaningful change |
| `New Custom Post Type` | Configured translatable CPT reaches published state first time |
| `Edit\Update Plugins` | Successful plugin package update OR successful WordPress plugin-file-editor save |
| `New plugins` | Successful plugin installation; activation alone does not count |
| `Edit/Update Themes` | Successful theme package update OR successful WordPress theme-file-editor save |
| `New Themes` | Successful theme installation; activation/switch alone does not count |

Plugin/theme event behavior:

1. Scope scan to changed extension only.
2. Compare new scan fingerprint with last successful extension fingerprint.
3. New/changed source strings participate; unchanged strings do not.
4. `mark_stale`: mark existing translations for changed strings stale; missing translations remain visibly missing.
5. `translate`: queue missing targets for new strings + unlocked stale targets for changed strings.
6. Removed strings become absent/orphaned; NEVER delete translation rows automatically.
7. Failed/rolled-back install/update/editor save emits no event.
8. Coalesce multi-package updates per extension.

Content event behavior:

1. `mark_stale`: mark existing targets stale; missing languages remain visibly missing.
2. `translate`: queue missing languages + refresh unlocked stale targets.
3. Locked target becomes stale + protected; never queue it.
4. Another source edit supersedes older source-fingerprint jobs; stale jobs skip completion.

### 7.3 Reconciliation

Run bounded scheduled reconciliation as safety net, not primary execution path.

- Persist cursors.
- Process configurable internal batch size; no user-facing tuning.
- Compare source fingerprints only.
- Emit same normalized events; coordinator preserves idempotency.
- Exponential retry transient errors; cap retries.
- Never perform external translation while behavior is `mark_stale`.
- Surface durable failures in existing diagnostics/admin notices without leaking source content.

## 8. Overwrite Enforcement Matrix

| Path | Queue-time check | Completion-time check | Protected outcome |
|---|---:|---:|---|
| Content modal Generate | MUST | MUST | Single-action policy message |
| Content Generate All | MUST | MUST | Per-language skipped count |
| Content bulk generation | MUST | MUST | Protected skipped count |
| Editor metabox generation | MUST | MUST | Disabled/skip message |
| Legacy synchronous content generation | MUST | Same request transaction | HTTP policy conflict |
| Async content callback/finalizer | MUST | MUST | `skipped_protected` |
| String modal generation | MUST | MUST | Single-action policy message |
| String bulk/force retranslate | MUST | MUST | Protected skipped count |
| String job receiver | MUST | MUST | `skipped_protected` |
| Source-event automation | MUST | MUST | Event summary protected count |

`force_retranslate` means regenerate eligible targets. It MUST NOT bypass protection.

Single-request protected response:

- HTTP `409`;
- stable code `translation_protected`;
- translation identity + effective lock only;
- message instructs unlock before generation.

Batch requests remain successful when some targets skip; return structured outcome counts/items.

## 9. UX Contract

### 9.1 Global settings card: manual protection

Control:

- Toggle label: `Protect manual translations`.
- Default: on.

Visible helper text:

> Uses protection as the default for manually edited and pre-existing translations. Each translation can override this setting.

Tooltip detail:

> Protected translations keep your manual wording. Automatic translation, Generate, Generate All, bulk actions, callbacks, and source-change automation cannot replace them. Unlock a specific translation before regenerating it. Generated translations remain eligible unless you protect them explicitly.

### 9.2 Global settings card: automation

Use selected Option A.

1. Master switch label MUST be `Enable Auto-translation:`.
2. Behavior selector appears below:
   - `Translate automatically`;
   - `Mark translations stale only (default)`.
3. Trigger checkboxes appear in groups:
   - Content: six content labels.
   - Plugins: two plugin labels.
   - Themes: two theme labels.
4. Controls remain visible but disabled while master switch is off.
5. Enabling with no trigger selected MUST show inline error; never silently save inert enabled state.

Visible helper text:

> Choose which successful WordPress changes should update translation status or start translation jobs.

Behavior explanations:

- `Translate automatically`: “Generates missing translations and refreshes stale translations that are not protected. Translation credits may be used.”
- `Mark translations stale only (default)`: “Marks affected translations as needing an update. It does not generate text or use translation credits.”

Master tooltip:

> This switch enables handling for the checked events below. It does not override protected manual translations. With Mark translations stale only, no automatic translation request is sent.

Each trigger MUST have event-specific tooltip matching section 7.2. Do not use one generic tooltip for all ten labels.

### 9.3 Per-translation control

Control label:

- `Protect this manual translation`.

Place adjacent to manual Save action on:

- content translation modal;
- string translation modal;
- translated-content WordPress editor panel.

Behavior:

- Checkbox reflects effective state.
- Initial untouched state shows `Uses global default`.
- User change creates explicit `locked`/`unlocked` override and shows `Custom override`.
- `Use global default` action restores `inherit`.
- Unlock/override changes MUST be saved before generation becomes available; unsaved UI state never bypasses server policy.
- Saving translation + lock is one request/transaction.
- Generated-only target MAY be protected explicitly; label becomes `Protect this translation` when provenance is not manual.

Visible helper text:

> Protected translations cannot be replaced by automatic or administrator-triggered generation.

Tooltip detail:

> Use this when wording was reviewed or edited manually. Protection applies to automatic translation, Generate, Generate All, bulk actions, and delayed job results. Source changes can still mark it stale. Unlock it before generating a replacement. This setting affects only this language translation.

### 9.4 List + action feedback

- Show text-backed `Protected` status; icon alone forbidden.
- Generated actions on protected targets MUST be disabled when target known.
- Bulk actions MAY remain available; summary MUST show protected skips.
- Stale + protected MAY appear together.
- Toast/result text: `Skipped — protected manual translation. Unlock it before generating.`
- Completion summaries separate `Translated`, `Marked stale`, `Protected`, `Changed while queued`, `Failed`.

### 9.5 Tooltip accessibility

Replace title-only/non-focusable help for these controls.

Tooltip trigger MUST:

- be a real button;
- have accessible name;
- connect stable tooltip ID with `aria-describedby` while visible;
- open on hover and keyboard focus;
- close on pointer leave, blur, and Escape;
- remain usable at touch widths;
- never contain required information unavailable in visible helper text;
- avoid focus theft;
- satisfy light/dark mode contrast.

## 10. REST + Permission Contracts

Settings:

- Existing settings capability remains authoritative.
- Sanitize every enum/boolean server-side.
- Return canonical settings shape.
- Reset restores defaults in section 6.1.

Translation detail/manual save:

- Detail responses include canonical `lock` shape.
- Manual-save payload MAY include `lock_override`.
- Unknown override => `400 invalid_lock_override`.
- Omitted override preserves state.
- Capability checks apply to lock changes and content save together.

Generation:

- Single protected target => `409 translation_protected`.
- Batch => per-item skip object; never hide skipped work as success.
- Finalizer callback remains authenticated; callback payload cannot set/clear lock.

Audit:

- Record actor, target, prior/new override, provenance transition, trigger type, and skip reason.
- Never log translated source/target body content.

## 11. Migration + Compatibility

Migration MUST be idempotent and resumable.

1. Add lock/provenance/stale/fingerprint/job metadata fields.
2. Backfill existing content/string overrides as inherit.
3. Preserve provable string auto/manual provenance from `is_auto_translated` and `translated_by`; otherwise unknown.
4. Backfill content provenance unknown unless durable evidence proves generated/manual.
5. Resolve duplicate content mappings without deleting posts; then add group/language uniqueness.
6. Preserve existing `ipz_auto_translate` value for diagnostics but keep new automation disabled.
7. Add one dismissible admin notice when legacy true existed: review new controls before enabling.
8. Keep old REST input aliases for compatibility; canonical response uses new fields.
9. Rollback MUST leave added metadata harmless; no destructive down migration.

## 12. Failure Handling

| Failure | Required behavior |
|---|---|
| External API unavailable | Job fails/retries under existing policy; target untouched |
| Lock changes while job runs | Finalizer skips protected |
| Manual edit while job runs | Finalizer skips target changed |
| Source changes while job runs | Finalizer skips stale source result |
| Duplicate lifecycle hooks | Idempotency key yields one event/job set |
| Plugin/theme scan fails | Keep prior successful fingerprint; report failure; no stale/generation changes |
| Migration interruption | Resume safely; never duplicate audit/mapping mutations |
| Reconciliation interruption | Resume from durable cursor |
| No configured languages | Record no-op outcome; no external request |
| Protected batch members | Continue eligible members; count skips |

Fail closed on uncertainty: unknown lock read, missing required fingerprint, or persistence race MUST skip generated write and surface diagnostic outcome.

## 13. Testing Contract

### 13.1 Unit

- Full effective-lock truth table.
- Existing unknown + inherit follows global setting.
- Generated + inherit remains eligible.
- Explicit override wins global.
- Queue/finalization fingerprint matrix.
- Every source trigger classifier, including exclusion cases.
- Source fingerprint normalization for content + ACF + strings.
- Event deduplication and supersession.
- `mark_stale` proves zero translation API calls.

### 13.2 Integration

- Idempotent migration from representative existing schemas/data.
- Duplicate mapping recovery preserves every post.
- Settings defaults, save, reset, legacy aliases, legacy-true notice.
- Manual content/string save records provenance + lock atomically.
- Direct translated-post editor save records manual provenance.
- Every generation path respects lock.
- Async race: queue, manual save/lock, callback => no overwrite.
- Async race: source changes twice => older result skipped.
- Concurrent missing-target jobs produce one mapping.
- Plugin/theme install/update/editor events scope scans correctly.
- Reconciliation recovers deliberately suppressed primary hook.
- Batch outcomes distinguish protected/changed/failed.

### 13.3 E2E + visual

Use `~/.claude/bin/e2e-remote`; NEVER run browser + dev server locally.

Cover:

1. Global manual-protection save/reset.
2. Option A automation controls, disabled state, validation, persistence.
3. All ten trigger labels + event-specific tooltips.
4. Keyboard focus/Escape tooltip behavior.
5. Per-translation inherit/locked/unlocked flow in content modal.
6. Same flow in string modal.
7. Same flow in WordPress translated-content editor.
8. Protected Generate, Generate All, bulk, and force-retranslate feedback.
9. Stale + protected simultaneous status.
10. Dark mode, narrow viewport, screen-reader semantics.
11. Before/after screenshots + computed-style/accessibility assertions per project visual workflow.

### 13.4 Clean gates

- PHP syntax/lint + project PHP tests.
- Admin JS tests/lint if configured.
- `cd admin && npm run build`.
- Relevant integration tests.
- Remote Playwright E2E.
- No warning, notice, prevent-band, or security-gate output left unexplained.

## 14. Acceptance Scenarios

1. Global protection on; existing inherited unknown target; admin Generate => skipped, unchanged.
2. Same target explicitly unlocked; Generate => allowed if fingerprints remain unchanged.
3. User edits target while job runs => callback skipped, manual text preserved.
4. Global protection changes off; inherited manual target becomes eligible; explicit locked target remains protected.
5. Automation off => selected WordPress events do nothing.
6. Automation on + stale mode + selected `Edit post` => existing targets stale; no API call.
7. Automation on + translate mode + selected `Edit post` => missing targets queued; stale unlocked targets queued; locked targets stale + skipped.
8. `New plugins` selected; successful install scans only new plugin and applies chosen behavior.
9. `Edit/Update Themes` selected; successful update scopes to changed theme; failed update does nothing.
10. Bulk run with mixed targets completes eligible items and reports every protected skip.
11. Tooltip information remains available by pointer, keyboard, and touch; visible helper text carries essential consequence.

## 15. Architecture Decisions

Accepted:

- Keep one deep `TranslationProtectionPolicy`; reject duplicated path-specific checks.
- Keep one deep `AutoTranslationCoordinator`; reject translation logic inside WordPress hooks.
- Keep thin content/extension trigger adapters; each hides distinct lifecycle semantics.
- Collapse reconciliation scheduling into `AutoTranslationCoordinator.reconcile(...)`; reject decorative reconciliation adapter.
- Extend existing persistence owners; reject decorative repository interfaces with one implementation.
- Add bounded reconciliation because lifecycle hooks alone can be missed by third-party update/edit paths.
- Use source + target fingerprints plus completion-time policy; queue-time checks alone cannot prevent races.
- Keep protection and stale state orthogonal.
- Preserve all duplicate content during uniqueness migration.

Rejected candidates:

- Event-only automation: insufficient recovery from missed hooks/process failure.
- Scheduled full scans as primary path: unnecessary latency/load.
- Force/bypass action: violates user requirement that protected manual work never be overwritten.
- Auto-protect all migrated rows explicitly: user selected inherited global behavior.
- One three-state automation selector: user selected separate switch + behavior controls.

## 16. Existing Seams to Modify

Use current patterns; inspect before implementation:

- `admin/src/pages/settings.js`: settings state/render/save.
- `admin/src/pages/content-translate.js`: manual save, Generate, Generate All, bulk summaries.
- `admin/src/pages/string-translate.js`: manual string save/generation/provenance UI.
- `admin/src/editor/translation-metabox.js`: editor generation/manual protection surface.
- `admin/src/components/Toggle.js`: accessible switch baseline.
- `admin/src/components/FormField.js`, `Tooltip.js`, `Input.js`: replace deficient tooltip behavior for new controls.
- `includes/API/SettingsController.php`: settings REST compatibility.
- `includes/Translation/Settings.php`: canonical settings access.
- `includes/API/TranslationsController.php`: manual content save + legacy synchronous generation.
- `includes/API/TranslateController.php`: async queue path.
- `includes/Translation/TranslationFinalizer.php`: completion-time enforcement.
- `includes/API/StringTranslateController.php`: manual/bulk string paths.
- `includes/Core/StringScanner.php`, `includes/Translation/JobReceiver.php`: string persistence/finalization.
- `includes/Core/Database.php`, `includes/Core/Migrations.php`: schema + migration.
- `includes/Core/ContentManager.php`: content mapping/hash/status.

Required project specification `wp-content/PROJECT-SPECIFICATION.md` is absent. Closest current architecture sources: plugin `CLAUDE.md`, `docs/ContentManager-Architecture.md`, `docs/workflow-state-machine.md`, and current feature specs. Do not invent missing project-wide contracts.
