Heratio Help Center article. Category: Technical.

Heratio AHG Framework - System Flows

Version: 2.8.2 Last Updated: February 2026


0. Heratio Dual-Mode Request Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                     HERATIO DUAL-MODE REQUEST FLOW                               │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌──────────┐                                                                   │
│  │  Client  │                                                                   │
│  │ (Browser)│                                                                   │
│  └────┬─────┘                                                                   │
│       │ HTTP Request                                                            │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                            NGINX                                          │  │
│  │                                                                          │  │
│  │   ┌──────────────────────────────┐  ┌─────────────────────────────────┐ │  │
│  │   │ heratio.conf (if installed)  │  │ Standard Heratio config            │ │  │
│  │   │                              │  │                                 │ │  │
│  │   │ /ingest/*      → heratio.php │  │ /                → index.php   │ │  │
│  │   │ /admin/ahg-*   → heratio.php │  │ /informationobj  → index.php   │ │  │
│  │   │ /display/*     → heratio.php │  │ /actor           → index.php   │ │  │
│  │   │ /privacy/*     → heratio.php │  │ /repository      → index.php   │ │  │
│  │   │ /research/*    → heratio.php │  │ /user            → index.php   │ │  │
│  │   │ (~40 plugin patterns)        │  │ (all base Heratio)                │ │  │
│  │   └──────────────┬───────────────┘  └──────────────┬──────────────────┘ │  │
│  └──────────────────┼──────────────────────────────────┼─────────────────────┘  │
│                     │                                  │                         │
│       ┌─────────────┘                                  └──────────────┐         │
│       ▼                                                               ▼         │
│  ┌────────────────────────┐                    ┌────────────────────────────┐  │
│  │  HERATIO ENTRY POINT   │                    │  SYMFONY ENTRY POINT       │  │
│  │  heratio.php           │                    │  index.php                 │  │
│  │                        │                    │                            │  │
│  │  1. Check kill-switch  │                    │  sfContext::getInstance()  │  │
│  │  2. Boot Kernel        │                    │  dispatch()                │  │
│  │  3. Middleware stack   │                    │  (unchanged Heratio)          │  │
│  │  4. Route dispatch     │                    │                            │  │
│  └───────────┬────────────┘                    └──────────────┬─────────────┘  │
│              │                                                │                 │
│              ▼                                                ▼                 │
│  ┌────────────────────────┐                    ┌────────────────────────────┐  │
│  │  HERATIO MIDDLEWARE    │                    │  SYMFONY FILTER CHAIN      │  │
│  │                        │                    │                            │  │
│  │  1. SessionMiddleware  │                    │  securityFilter            │  │
│  │  2. AuthMiddleware     │                    │  accessFilter              │  │
│  │  3. SettingsMiddleware │                    │  cacheFilter               │  │
│  │  4. CspMiddleware      │                    │  executionFilter           │  │
│  │  5. MetaMiddleware     │                    │                            │  │
│  │  6. LimitsMiddleware   │                    │                            │  │
│  └───────────┬────────────┘                    └──────────────┬─────────────┘  │
│              │                                                │                 │
│              ▼                                                ▼                 │
│  ┌────────────────────────┐                    ┌────────────────────────────┐  │
│  │  ACTION BRIDGE         │                    │  SYMFONY ACTION            │  │
│  │                        │                    │                            │  │
│  │  Dispatches to one of: │                    │  sfAction->execute()       │  │
│  │  • AhgController       │                    │  Propel ORM               │  │
│  │  • AhgActions (Blade)  │                    │  sfView rendering         │  │
│  │  • sfActions (Bridge)  │                    │                            │  │
│  └───────────┬────────────┘                    └──────────────┬─────────────┘  │
│              │                                                │                 │
│              ▼                                                ▼                 │
│  ┌────────────────────────┐                    ┌────────────────────────────┐  │
│  │  RENDERING             │                    │  RENDERING                 │  │
│  │                        │                    │                            │  │
│  │  BladeRenderer         │                    │  sfPHPView                 │  │
│  │  heratio.blade.php     │                    │  layout.php                │  │
│  │  (master layout)       │                    │  (theme layout)            │  │
│  │  + 8 partials          │                    │                            │  │
│  └───────────┬────────────┘                    └──────────────┬─────────────┘  │
│              │                                                │                 │
│              └────────────────────┬───────────────────────────┘                 │
│                                   ▼                                             │
│                          ┌─────────────┐                                        │
│                          │   Client    │ ← HTML Response                        │
│                          └─────────────┘                                        │
│                                                                                  │
│  KILL-SWITCH: Remove .heratio_enabled file → ALL routes go to index.php         │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

1. Request Processing Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                           HTTP REQUEST FLOW                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌──────────┐                                                                   │
│  │  Client  │                                                                   │
│  │ (Browser)│                                                                   │
│  └────┬─────┘                                                                   │
│       │ HTTP Request                                                            │
│       │ GET /informationobject/browse                                           │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                            NGINX                                          │  │
│  │                                                                          │  │
│  │   location / { fastcgi_pass php-fpm; }                                   │  │
│  │   location /plugins/ { alias /path/to/plugins/; }                        │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                          PHP-FPM 8.3                                      │  │
│  │                                                                          │  │
│  │   index.php → sfContext::getInstance() → dispatch()                      │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                    SYMFONY 1.x FRONT CONTROLLER                           │  │
│  │                                                                          │  │
│  │   1. Load config/ProjectConfiguration.class.php                          │  │
│  │   2. Call setup() → loadPluginsFromDatabase()                            │  │
│  │   3. Bootstrap atom-framework                                            │  │
│  │   4. Query atom_plugin for enabled plugins                               │  │
│  │   5. Enable plugins via $this->enablePlugins()                           │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                         ROUTING                                           │  │
│  │                                                                          │  │
│  │   apps/qubit/config/routing.yml                                          │  │
│  │   + plugin routing.yml files                                             │  │
│  │                                                                          │  │
│  │   /informationobject/browse → informationobject/browseAction             │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                    FILTER CHAIN                                           │  │
│  │                                                                          │  │
│  │   ┌─────────────────┐                                                    │  │
│  │   │ securityFilter  │ ◄── ahgSecurityClearancePlugin                     │  │
│  │   │ (check access)  │     Verifies user clearance vs record class        │  │
│  │   └────────┬────────┘                                                    │  │
│  │            ▼                                                             │  │
│  │   ┌─────────────────┐                                                    │  │
│  │   │ accessFilter    │ ◄── Core Heratio ACL                                  │  │
│  │   │ (check ACL)     │     Verifies group permissions                     │  │
│  │   └────────┬────────┘                                                    │  │
│  │            ▼                                                             │  │
│  │   ┌─────────────────┐                                                    │  │
│  │   │ cacheFilter     │                                                    │  │
│  │   └────────┬────────┘                                                    │  │
│  │            ▼                                                             │  │
│  │   ┌─────────────────┐                                                    │  │
│  │   │ executionFilter │ ◄── Runs the action                                │  │
│  │   └─────────────────┘                                                    │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                         ACTION EXECUTION                                  │  │
│  │                                                                          │  │
│  │   plugins/ahgDisplayPlugin/modules/informationobject/                    │  │
│  │     actions/browseAction.class.php                                       │  │
│  │                                                                          │  │
│  │   class browseAction extends sfAction {                                  │  │
│  │     public function execute($request) {                                  │  │
│  │       // Query via Propel (core Heratio)                                    │  │
│  │       $records = QubitInformationObject::getAll();                       │  │
│  │                                                                          │  │
│  │       // Query via Laravel (extension data)                              │  │
│  │       $conditions = DB::table('condition_assessment')                    │  │
│  │         ->whereIn('object_id', $ids)->get();                             │  │
│  │                                                                          │  │
│  │       // Trigger hooks for panels                                        │  │
│  │       $panels = AhgHooks::trigger('browse.panels');                      │  │
│  │     }                                                                    │  │
│  │   }                                                                      │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                       TEMPLATE RENDERING                                  │  │
│  │                                                                          │  │
│  │   Layout: plugins/ahgThemeB5Plugin/templates/layout.php                  │  │
│  │   Template: plugins/ahgDisplayPlugin/modules/.../browseSuccess.php       │  │
│  │                                                                          │  │
│  │   Template includes:                                                     │  │
│  │   • Sector-specific labels via AhgSectorProfile::getLabel()              │  │
│  │   • Registered panels via AhgPanels::forPosition()                       │  │
│  │   • Capability checks via AhgCapabilities::has()                         │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│       │                                                                         │
│       ▼                                                                         │
│  ┌──────────┐                                                                   │
│  │  Client  │ ◄── HTML Response                                                 │
│  └──────────┘                                                                   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

2. Plugin Installation Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                        PLUGIN INSTALLATION FLOW                                  │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ $ php bin/atom extension:install ahgPrivacyPlugin                        │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 1. ExtensionManager::install()                                           │   │
│  │                                                                          │   │
│  │    ┌────────────────────────────────────────────────────────────────┐   │   │
│  │    │ Check if plugin exists locally                                  │   │   │
│  │    │                                                                 │   │   │
│  │    │ if (!file_exists($pluginsPath/$name)) {                        │   │   │
│  │    │     // Fetch from GitHub via PluginFetcher                     │   │   │
│  │    │     $fetcher->clone($repoUrl, $pluginsPath);                   │   │   │
│  │    │ }                                                               │   │   │
│  │    └────────────────────────────────────────────────────────────────┘   │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 2. Read extension.json                                                   │   │
│  │                                                                          │   │
│  │    {                                                                     │   │
│  │      "name": "ahgPrivacyPlugin",                                         │   │
│  │      "version": "1.2.0",                                                 │   │
│  │      "category": "compliance",                                           │   │
│  │      "dependencies": ["ahgCorePlugin"],                                  │   │
│  │      "database": {                                                       │   │
│  │        "install": "database/install.sql",                                │   │
│  │        "migrations": "database/migrations/"                              │   │
│  │      }                                                                   │   │
│  │    }                                                                     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 3. Check dependencies                                                    │   │
│  │                                                                          │   │
│  │    foreach ($dependencies as $dep) {                                     │   │
│  │      if (!$this->isEnabled($dep)) {                                      │   │
│  │        $this->enable($dep);  // Enable dependency first                  │   │
│  │      }                                                                   │   │
│  │    }                                                                     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 4. Run database migrations                                               │   │
│  │                                                                          │   │
│  │    MigrationHandler::runInstall($plugin)                                 │   │
│  │                                                                          │   │
│  │    ┌────────────────────────────────────────────────────────────────┐   │   │
│  │    │ // Execute database/install.sql                                 │   │   │
│  │    │ CREATE TABLE privacy_breach (...);                              │   │   │
│  │    │ CREATE TABLE privacy_consent (...);                             │   │   │
│  │    │ CREATE TABLE privacy_sar_request (...);                         │   │   │
│  │    │                                                                 │   │   │
│  │    │ // Run migrations in order                                      │   │   │
│  │    │ 001_initial.sql                                                 │   │   │
│  │    │ 002_add_breach_columns.sql                                      │   │   │
│  │    └────────────────────────────────────────────────────────────────┘   │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 5. Create symlink (if not exists)                                        │   │
│  │                                                                          │   │
│  │    ln -s /path/to/atom-ahg-plugins/ahgPrivacyPlugin                      │   │
│  │           /path/to/atom/plugins/ahgPrivacyPlugin                         │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 6. Register in atom_plugin table                                         │   │
│  │                                                                          │   │
│  │    INSERT INTO atom_plugin (                                             │   │
│  │      name, class_name, version, category,                                │   │
│  │      is_enabled, is_core, is_locked, load_order                          │   │
│  │    ) VALUES (                                                            │   │
│  │      'ahgPrivacyPlugin', 'ahgPrivacyPluginConfiguration',                │   │
│  │      '1.2.0', 'compliance', 1, 0, 0, 50                                  │   │
│  │    );                                                                    │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 7. Clear Symfony cache                                                   │   │
│  │                                                                          │   │
│  │    rm -rf cache/*                                                        │   │
│  │    php symfony cc                                                        │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ ✓ Plugin ahgPrivacyPlugin installed and enabled                          │   │
│  │                                                                          │   │
│  │ Restart PHP-FPM for changes to take effect:                              │   │
│  │ $ sudo systemctl restart php8.3-fpm                                      │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

3. Audit Trail Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                            AUDIT TRAIL FLOW                                      │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ User Action: Edit record                                                 │   │
│  │ POST /informationobject/edit/123                                         │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ Action: editAction.class.php                                             │   │
│  │                                                                          │   │
│  │   // Get original values                                                 │   │
│  │   $original = $record->toArray();                                        │   │
│  │                                                                          │   │
│  │   // Apply changes                                                       │   │
│  │   $record->title = $request->getParameter('title');                      │   │
│  │   $record->save();                                                       │   │
│  │                                                                          │   │
│  │   // Calculate diff                                                      │   │
│  │   $changes = array_diff_assoc($record->toArray(), $original);            │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ AhgHooks::trigger('record.updated', $record, $changes)                   │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ ahgAuditTrailPlugin listener                                             │   │
│  │                                                                          │   │
│  │   AhgHooks::register('record.updated', function($record, $changes) {     │   │
│  │                                                                          │   │
│  │     // Create audit log entry                                            │   │
│  │     DB::table('audit_log')->insert([                                     │   │
│  │       'user_id'     => sfContext::getInstance()->getUser()->getId(),     │   │
│  │       'object_id'   => $record->id,                                      │   │
│  │       'object_type' => get_class($record),                               │   │
│  │       'action'      => 'update',                                         │   │
│  │       'module'      => 'informationobject',                              │   │
│  │       'changes'     => json_encode($changes),                            │   │
│  │       'ip_address'  => $_SERVER['REMOTE_ADDR'],                          │   │
│  │       'user_agent'  => $_SERVER['HTTP_USER_AGENT'],                      │   │
│  │       'created_at'  => date('Y-m-d H:i:s'),                              │   │
│  │     ]);                                                                  │   │
│  │                                                                          │   │
│  │     // Create detail records for each changed field                      │   │
│  │     foreach ($changes as $field => $value) {                             │   │
│  │       DB::table('audit_log_detail')->insert([...]);                      │   │
│  │     }                                                                    │   │
│  │   });                                                                    │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ Database State                                                           │   │
│  │                                                                          │   │
│  │   audit_log                                                              │   │
│  │   ┌────┬─────────┬───────────┬────────┬────────────────────────────┐    │   │
│  │   │ id │ user_id │ object_id │ action │ changes                    │    │   │
│  │   ├────┼─────────┼───────────┼────────┼────────────────────────────┤    │   │
│  │   │ 42 │    5    │    123    │ update │ {"title":{"old":"...      │    │   │
│  │   └────┴─────────┴───────────┴────────┴────────────────────────────┘    │   │
│  │                                                                          │   │
│  │   audit_log_detail                                                       │   │
│  │   ┌────┬──────────────┬────────────┬───────────────┬─────────────┐      │   │
│  │   │ id │ audit_log_id │ field_name │ old_value     │ new_value   │      │   │
│  │   ├────┼──────────────┼────────────┼───────────────┼─────────────┤      │   │
│  │   │ 98 │      42      │ title      │ Old Title     │ New Title   │      │   │
│  │   └────┴──────────────┴────────────┴───────────────┴─────────────┘      │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
│  ═══════════════════════════════════════════════════════════════════════════   │
│                                                                                  │
│  Audit Log Viewer: Admin → Audit Trail                                          │
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ Filter: [User ▼] [Action ▼] [Module ▼] [Date Range]    [Search]        │   │
│  ├─────────────────────────────────────────────────────────────────────────┤   │
│  │ Time         │ User      │ Action │ Record              │ Changes      │   │
│  ├──────────────┼───────────┼────────┼─────────────────────┼──────────────┤   │
│  │ 10:45:23     │ jsmith    │ UPDATE │ Document ABC-123    │ title, date  │   │
│  │ 10:42:11     │ admin     │ CREATE │ Collection XYZ      │ -            │   │
│  │ 10:38:05     │ jsmith    │ VIEW   │ Photo Album         │ -            │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

4. IIIF Manifest Generation Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                        IIIF MANIFEST GENERATION FLOW                             │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ Request: GET /iiif/manifest/123                                          │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 1. Check cache                                                           │   │
│  │                                                                          │   │
│  │    $cached = DB::table('iiif_manifest')                                  │   │
│  │      ->where('object_id', 123)                                           │   │
│  │      ->where('updated_at', '>', $record->updated_at)                     │   │
│  │      ->first();                                                          │   │
│  │                                                                          │   │
│  │    if ($cached) return json_decode($cached->manifest_json);              │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │ Cache miss                               │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 2. Load record and digital objects                                       │   │
│  │                                                                          │   │
│  │    $record = QubitInformationObject::getById(123);                       │   │
│  │    $digitalObjects = $record->getDigitalObjects();                       │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 3. Build IIIF Presentation 3.0 manifest                                  │   │
│  │                                                                          │   │
│  │    {                                                                     │   │
│  │      "@context": "http://iiif.io/api/presentation/3/context.json",       │   │
│  │      "id": "https://example.org/iiif/manifest/123",                      │   │
│  │      "type": "Manifest",                                                 │   │
│  │      "label": { "en": ["Record Title"] },                                │   │
│  │      "metadata": [                                                       │   │
│  │        { "label": {"en":["Creator"]}, "value": {"en":["John Doe"]} }    │   │
│  │      ],                                                                  │   │
│  │      "items": [                                                          │   │
│  │        {                                                                 │   │
│  │          "id": "https://example.org/iiif/canvas/123-1",                  │   │
│  │          "type": "Canvas",                                               │   │
│  │          "width": 4000,                                                  │   │
│  │          "height": 3000,                                                 │   │
│  │          "items": [...]                                                  │   │
│  │        }                                                                 │   │
│  │      ]                                                                   │   │
│  │    }                                                                     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 4. Add annotations (if available)                                        │   │
│  │                                                                          │   │
│  │    // OCR text                                                           │   │
│  │    $ocr = DB::table('iiif_ocr_text')->where('object_id', 123)->first();  │   │
│  │    if ($ocr) {                                                           │   │
│  │      manifest.annotations = OcrService::generateAnnotationPage();        │   │
│  │    }                                                                     │   │
│  │                                                                          │   │
│  │    // Transcriptions                                                     │   │
│  │    $transcription = TranscriptionService::get(123);                      │   │
│  │    if ($transcription) {                                                 │   │
│  │      manifest.supplementing = ...                                        │   │
│  │    }                                                                     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 5. Apply access restrictions                                             │   │
│  │                                                                          │   │
│  │    if (AhgCapabilities::has('security')) {                               │   │
│  │      $classification = SecurityService::getClassification(123);         │   │
│  │      if ($classification->level > 0) {                                   │   │
│  │        manifest.services = [                                             │   │
│  │          { "@type": "AuthCookieService1", ... }                          │   │
│  │        ];                                                                │   │
│  │      }                                                                   │   │
│  │    }                                                                     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 6. Cache and return                                                      │   │
│  │                                                                          │   │
│  │    DB::table('iiif_manifest')->updateOrInsert(                           │   │
│  │      ['object_id' => 123],                                               │   │
│  │      ['manifest_json' => json_encode($manifest), ...]                    │   │
│  │    );                                                                    │   │
│  │                                                                          │   │
│  │    return Response::json($manifest)                                      │   │
│  │      ->header('Access-Control-Allow-Origin', '*')                        │   │
│  │      ->header('Content-Type', 'application/ld+json');                    │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
│  ═══════════════════════════════════════════════════════════════════════════   │
│                                                                                  │
│  Viewer Integration:                                                            │
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ <div id="mirador">                                                       │   │
│  │   <script>                                                               │   │
│  │     Mirador.viewer({                                                     │   │
│  │       id: 'mirador',                                                     │   │
│  │       windows: [{                                                        │   │
│  │         manifestId: 'https://example.org/iiif/manifest/123'              │   │
│  │       }]                                                                 │   │
│  │     });                                                                  │   │
│  │   </script>                                                              │   │
│  │ </div>                                                                   │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

5. AI/NER Processing Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                          NER PROCESSING FLOW                                     │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ Trigger: User clicks "Extract Entities" or batch job runs               │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 1. Collect text content                                                  │   │
│  │                                                                          │   │
│  │    $text = $record->getScopeAndContent() .                               │   │
│  │            $record->getArchivalHistory() .                               │   │
│  │            $record->getTitle();                                          │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 2. Send to Python NER service                                            │   │
│  │                                                                          │   │
│  │    // atom-ahg-python/src/atom_ahg/resources/ner.py                      │   │
│  │                                                                          │   │
│  │    import spacy                                                          │   │
│  │    nlp = spacy.load("en_core_web_lg")                                    │   │
│  │                                                                          │   │
│  │    doc = nlp(text)                                                       │   │
│  │    entities = []                                                         │   │
│  │    for ent in doc.ents:                                                  │   │
│  │        entities.append({                                                 │   │
│  │            'text': ent.text,                                             │   │
│  │            'type': ent.label_,  # PERSON, ORG, GPE, DATE                 │   │
│  │            'start': ent.start_char,                                      │   │
│  │            'end': ent.end_char,                                          │   │
│  │            'confidence': ent.kb_id_                                      │   │
│  │        })                                                                │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 3. Process entities                                                      │   │
│  │                                                                          │   │
│  │    Entities found:                                                       │   │
│  │    ┌────────────────────┬──────────┬────────────────────────────────┐   │   │
│  │    │ Text               │ Type     │ Action                         │   │   │
│  │    ├────────────────────┼──────────┼────────────────────────────────┤   │   │
│  │    │ John Smith         │ PERSON   │ Link to/create actor record    │   │   │
│  │    │ Acme Corporation   │ ORG      │ Link to/create actor record    │   │   │
│  │    │ Cape Town          │ GPE      │ Link to place authority        │   │   │
│  │    │ 15 March 1952      │ DATE     │ Parse and validate date        │   │   │
│  │    └────────────────────┴──────────┴────────────────────────────────┘   │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 4. User review (optional)                                                │   │
│  │                                                                          │   │
│  │    ┌──────────────────────────────────────────────────────────────┐     │   │
│  │    │ Entity Suggestions                                           │     │   │
│  │    ├──────────────────────────────────────────────────────────────┤     │   │
│  │    │ ☑ John Smith (PERSON) → Create new actor                     │     │   │
│  │    │ ☑ Acme Corporation (ORG) → Link to existing: Acme Corp Ltd  │     │   │
│  │    │ ☐ Cape Town (PLACE) → [Reject - already linked]             │     │   │
│  │    │ ☑ 15 March 1952 (DATE) → Add to dates field                  │     │   │
│  │    │                                                              │     │   │
│  │    │                              [Apply Selected] [Reject All]   │     │   │
│  │    └──────────────────────────────────────────────────────────────┘     │   │
│  └────────────────────────────────────┬────────────────────────────────────┘   │
│                                       │                                          │
│                                       ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ 5. Apply changes                                                         │   │
│  │                                                                          │   │
│  │    // Create actor if not exists                                         │   │
│  │    $actor = QubitActor::getByName('John Smith')                          │   │
│  │      ?? QubitActor::create(['authorized_form_of_name' => 'John Smith']); │   │
│  │                                                                          │   │
│  │    // Link to record                                                     │   │
│  │    QubitRelation::create([                                               │   │
│  │      'subject_id' => $record->id,                                        │   │
│  │      'object_id' => $actor->id,                                          │   │
│  │      'type_id' => QubitTerm::NAME_ACCESS_POINT_ID                        │   │
│  │    ]);                                                                   │   │
│  │                                                                          │   │
│  │    // Log extraction                                                     │   │
│  │    DB::table('ner_extraction_log')->insert([...]);                       │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

6. Ingest Pipeline Flow

┌─────────────────────────────────────────────────────────────────────────────────┐
│                          INGEST PIPELINE FLOW                                    │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  STEP 1          STEP 2          STEP 3          STEP 4          STEP 5         │
│  CONFIGURE  ───► UPLOAD     ───► MAP & ENRICH ──► VALIDATE   ───► PREVIEW       │
│  (sector,        (CSV/ZIP/       (auto-map,       (required       (tree view,   │
│   standard,       EAD, dir)       metadata,        fields,         approval)    │
│   options)                        profiles)        checksums)                    │
│                                                                                  │
│  ──────────────────────────────────────────────────────────────────► STEP 6     │
│                                                                     COMMIT      │
│                                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │ COMMIT FLOW (Background Job)                                            │   │
│  │                                                                         │   │
│  │  Browser ──POST──► Action ──► Create ingest_job (status=queued)         │   │
│  │                      │                                                   │   │
│  │                      ├──► nohup php symfony ingest:commit --job-id=X &   │   │
│  │                      │                                                   │   │
│  │                      └──► Return page (AJAX polling starts)              │   │
│  │                                                                         │   │
│  │  CLI Task ──► Mark running ──► For each row:                            │   │
│  │                                  ├─ Create InformationObject            │   │
│  │                                  ├─ Create DigitalObject (if DO path)   │   │
│  │                                  ├─ Generate derivatives                │   │
│  │                                  └─ Run AI processing (NER/OCR/etc)     │   │
│  │                                                                         │   │
│  │              ──► Build SIP package ──► Build AIP ──► Build DIP          │   │
│  │              ──► Update search index                                     │   │
│  │              ──► Generate manifest CSV                                   │   │
│  │              ──► Mark completed                                          │   │
│  │                                                                         │   │
│  │  Browser ◄──poll every 2s──► /ingest/ajax/job-status?job_id=X          │   │
│  │           ──► Show progress bar ──► On complete: show report card       │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
│  ROLLBACK: Deletes created IOs + DOs + packages, restores session state         │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

Part of the Heratio AHG Framework - v2.8.2