From b9b60cbde0c05fb4f3d90c337308f46650597e4e Mon Sep 17 00:00:00 2001 From: Jonathan Jackson Date: Thu, 21 May 2026 19:01:56 -0600 Subject: [PATCH] feat(skills): 3 Connect Interviews skills + slash commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /ace:interview-domain-bootstrap → skill orchestrates per-domain plumbing using the atoms shipped in PRs #386-393. Walks per_domain section of checklist-schema.yaml; documents 4 manual-fallback steps (subscription, UCR expression create, custom-data-field, conditional alert) where atoms are gap-tagged. - /ace:interview-cohort-create → skill orchestrates per-cohort launch. Takes a Cohort tracker-style YAML; runs the per_cohort section of the schema. Manual fallbacks: linked-app copy, user-field choice add, conditional alert create, OCS router-keywords update. - /ace:interview-opp-verify → read-only verifier. Walks every rule in checklist-schema.yaml against a live opp; produces a pass / fail / unverifiable / out_of_band report. Cross-system consistency checks included (OCS custom action target URL == HQ Inbound API URL). Exit codes 0/1/2 for clean/fail/unverifiable. Each skill points at its slash command + the canonical schema doc. Manual-fallback steps are explicit prompts; atoms for those land in V1.5. Co-Authored-By: Claude Opus 4.7 --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 2 +- VERSION | 2 +- commands/interview-cohort-create.md | 19 ++ commands/interview-domain-bootstrap.md | 19 ++ commands/interview-opp-verify.md | 24 ++ package-lock.json | 4 +- package.json | 2 +- skills/interview-cohort-create/SKILL.md | 222 ++++++++++++++++++ skills/interview-domain-bootstrap/SKILL.md | 252 +++++++++++++++++++++ skills/interview-opp-verify/SKILL.md | 149 ++++++++++++ 11 files changed, 692 insertions(+), 7 deletions(-) create mode 100644 commands/interview-cohort-create.md create mode 100644 commands/interview-domain-bootstrap.md create mode 100644 commands/interview-opp-verify.md create mode 100644 skills/interview-cohort-create/SKILL.md create mode 100644 skills/interview-domain-bootstrap/SKILL.md create mode 100644 skills/interview-opp-verify/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 9294b0f8..b00fa05e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,13 +6,13 @@ "url": "https://github.com/jjackson" }, "metadata": { - "version": "0.13.321" + "version": "0.13.322" }, "plugins": [ { "name": "ace", "source": "./", - "version": "0.13.321", + "version": "0.13.322", "description": "AI Connect Engine — orchestrates the CRISPR-Connect lifecycle from idea through app building, Connect setup, LLO management, and closeout" } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 26fc1bc4..d3fccad9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ace", - "version": "0.13.321", + "version": "0.13.322", "description": "AI Connect Engine — orchestrates the CRISPR-Connect lifecycle from idea through app building, Connect setup, LLO management, and closeout", "author": { "name": "Jonathan Jackson", diff --git a/VERSION b/VERSION index c86f8093..2d8e9e77 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.13.321 +0.13.322 diff --git a/commands/interview-cohort-create.md b/commands/interview-cohort-create.md new file mode 100644 index 00000000..40242536 --- /dev/null +++ b/commands/interview-cohort-create.md @@ -0,0 +1,19 @@ +--- +description: Launch a single Connect Interviews cohort (per-opportunity automation) +argument-hint: +allowed-tools: [Read, Write, Edit, Bash, AskUserQuestion] +--- + +# /ace:interview-cohort-create + +Per-cohort launch automation for the Connect Interviews program: linked-app copies, lookup-table rows, Connect opportunity + payment unit, per-cohort OCS bot routing. + +Reads a single YAML spec mirroring the team's Cohort tracker columns. Implements the per_cohort section of `docs/connect-interviews/checklist-schema.yaml`. Idempotent where possible; manual fallbacks for the deferred atoms (linked-app copy, user-field choice add, conditional alert). + +Usage: + +``` +/ace:interview-cohort-create +``` + +See `skills/interview-cohort-create/SKILL.md` for the input format and per-step process. diff --git a/commands/interview-domain-bootstrap.md b/commands/interview-domain-bootstrap.md new file mode 100644 index 00000000..0e6b8504 --- /dev/null +++ b/commands/interview-domain-bootstrap.md @@ -0,0 +1,19 @@ +--- +description: One-time bootstrap of a Connect Interviews HQ project-space pair (master + downstream) +argument-hint: +allowed-tools: [Read, Write, Edit, Bash, AskUserQuestion] +--- + +# /ace:interview-domain-bootstrap + +Stand up the per-project-space plumbing for the Connect Interviews program: HQ domains, linked-project-spaces relationship, Connections, Data Forwarding, Configurable Form Repeaters, Inbound APIs, lookup tables, plus the OCS Dynamic Router Bot. + +Reads a single YAML spec and walks the per_domain section of `docs/connect-interviews/checklist-schema.yaml`. Surfaces atom-gap manual steps for the 4 deferred items (subscription provisioning, UCR expression creation, custom user data field, conditional alerts). + +Usage: + +``` +/ace:interview-domain-bootstrap +``` + +See `skills/interview-domain-bootstrap/SKILL.md` for the input format, atoms used, and per-step process. diff --git a/commands/interview-opp-verify.md b/commands/interview-opp-verify.md new file mode 100644 index 00000000..0c33f2b8 --- /dev/null +++ b/commands/interview-opp-verify.md @@ -0,0 +1,24 @@ +--- +description: Verify a Connect Interviews opportunity matches the launch checklist +argument-hint: [--opp --org --domain --bot ] +allowed-tools: [Read, Write, Edit, Bash, AskUserQuestion] +--- + +# /ace:interview-opp-verify + +Read-only verifier for a configured Connect Interviews opportunity. Walks every rule in `docs/connect-interviews/checklist-schema.yaml` (per_program + per_domain + per_cohort + per_user) and grades each rule pass / fail / unverifiable / out_of_band. + +Outputs a human-readable report + machine-readable verdict YAML. Cross-system consistency checks (e.g. OCS custom action target URL == HQ Inbound API URL) included. + +Usage: + +``` +/ace:interview-opp-verify https://connect.dimagi.com/a//opportunity// +``` + +Exit codes: +- 0 — all rules pass or out_of_band +- 1 — at least one fail +- 2 — no fails but some unverifiable (operator should review action items) + +See `skills/interview-opp-verify/SKILL.md` for the report format, atoms used, and per-rule processing. diff --git a/package-lock.json b/package-lock.json index 0fb935ee..40e45491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ace", - "version": "0.13.305", + "version": "0.13.291", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ace", - "version": "0.13.305", + "version": "0.13.291", "license": "ISC", "dependencies": { "@anthropic-ai/sdk": "^0.94.0", diff --git a/package.json b/package.json index f4ac871d..92ddcd54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ace", - "version": "0.13.321", + "version": "0.13.322", "description": "AI Connect Engine - orchestrator for building Connect Opps using AI", "type": "module", "scripts": { diff --git a/skills/interview-cohort-create/SKILL.md b/skills/interview-cohort-create/SKILL.md new file mode 100644 index 00000000..10290129 --- /dev/null +++ b/skills/interview-cohort-create/SKILL.md @@ -0,0 +1,222 @@ +--- +name: interview-cohort-create +description: > + Per-cohort launch automation for Connect Interviews. Takes a cohort + YAML spec; produces linked-app copies, lookup-table rows, the Connect + opportunity + payment unit, and per-cohort OCS bot routing. Implements + the per_cohort section of docs/connect-interviews/checklist-schema.yaml. +disable-model-invocation: true +--- + +# Interview Cohort Create + +Launch a single cohort for the Connect Interviews program. Idempotent where possible. Designed to run after `/ace:interview-domain-bootstrap` has plumbed the domain. + +## Slash command + +``` +/ace:interview-cohort-create +``` + +## Input + +Single YAML spec mirroring the Cohort tracker columns (per opp-checklist steps 2.3 + 4.3): + +```yaml +# Identity +cohort_id: "08TRS" # e.g. "08TRS", "09TRE", "07T1B" +llo_org_slug: "ai-demo-space" # PM org (V1 is ACE-owned) +hq_downstream_domain: "ace-interviews-test" +hq_master_domain: "ace-interviews-master" + +# App references (master-side; cohort gets linked copies) +master_learn_app_id: "" +master_deliver_app_id: "" + +# Connect opp +connect_program_id: "" +connect_pm_org: "ai-demo-space" +opp_short_description: "Connect Interviews : 08TRS" +opp_long_description: "..." # Free text; checklist step 3.6 + +# Schedule +schedule: + - { previous: "", next: "te001", frequency_days: 2 } + - { previous: "te001", next: "te002", frequency_days: 2 } + - { previous: "te002", next: "te003", frequency_days: 9999 } # last entry's frequency_days=9999 + +# Payment + budget +payment_amount_ngn: 2.0 # per cohort tracker +max_users: 40 # rounded up per checklist note +start_date: "2026-05-21" # opp.start_date — NOT updatable later +end_date: "2026-06-30" + +# OCS-side +ocs_bot_id: 12213 # The per-domain Dynamic Router Bot from bootstrap +``` + +## Products + +- `ACE//cohort-create.yaml` — what got created (opp_id, payment_unit_id, app ids, etc.) +- `ACE//manual-steps.md` — list of manual steps (per-cohort conditional alert) + +## Process + +Walks per_cohort section of `checklist-schema.yaml`. + +### Step 1: Linked-app copies + +For each of Learn + Deliver, pull a fresh linked copy on the downstream domain. + +**Atom gap**: `commcare_linked_app_copy` is documented in checklist-schema.yaml's atom_gaps but not yet built. The DomainLinkRMIView supports this (via the `create_app_copy` method on apps) but the wire shape needs probing. + +**V1 manual fallback**: prompt operator to do the linked-app pull via HQ UI at /a//apps/, then enter the new app ids back into the cohort.yaml. + +Once atoms ship: +``` +commcare_linked_app_copy({ + upstream_domain: hq_master_domain, + downstream_domain: hq_downstream_domain, + master_learn_app_id: , +}) +# returns new app_id +``` + +Rename copies to include cohort_id (e.g. "Connect Interviews (Learn) 08TRS"). Atom gap. + +Release a build for each: +``` +commcare_make_build({domain: hq_downstream_domain, app_id: }) +commcare_release_build({domain: hq_downstream_domain, app_id: , build_id: }) +``` + +### Step 2: Lookup table rows + +Append the cohort's schedule: + +``` +commcare_lookup_table_append_rows({ + domain: hq_downstream_domain, + table_id_or_tag: "interview_schedule", + rows: schedule.map(s => ({ + cohort_id: cohort_id, + previous_interview: s.previous, + next_interview: s.next, + frequency_days: String(s.frequency_days), + })), +}) +``` + +### Step 3: User-field choice (cohort_id) + +**Manual step (prompt operator):** +> "Visit /a//users/commcare/user_data/ and add '' as a choice for the cohort_id user field. (Atom not yet built.)" + +### Step 4: Connect opportunity + +``` +connect_create_opportunity({ + organization_slug: connect_pm_org, + program_id: connect_program_id, + name: cohort_id, # checklist step 3.2: just the cohort_id + short_description: opp_short_description, + long_description: opp_long_description, + country: "Nigeria", + currency: "NGN", + learn_app: { domain: hq_downstream_domain, cc_app_id: , passing_score: 4 }, + deliver_app: { domain: hq_downstream_domain, cc_app_id: }, + hq_server: "https://www.commcarehq.org/", + api_key: , + start_date: start_date, # cannot be updated later + end_date: end_date, + max_users: max_users, +}) +``` + +Capture the new opp_id. + +### Step 5: Payment unit + +``` +connect_create_payment_unit({ + organization_slug: connect_pm_org, + opportunity_id: , + name: `${cohort_id} interview completed successfully`, + amount: payment_amount_ngn, + max_total: schedule.length, # one payment per interview in the schedule + max_daily: 1, # checklist step 4.3 +}) +``` + +### Step 6: Opp verification flags + +``` +connect_set_verification_flags({ + organization_slug: connect_pm_org, + opportunity_id: , + flags: { gps: false }, # checklist step 6: GPS off +}) +``` + +### Step 7: Delivery type + active + +``` +connect_update_opportunity({ + organization_slug: connect_pm_org, + opportunity_id: , + delivery_type: "interview", + is_test: false, +}) + +connect_activate_opportunity({ + organization_slug: connect_pm_org, + opportunity_id: , +}) +``` + +### Step 8: Conditional alert + +**Manual step (prompt operator):** +> "Visit /a//messaging/conditional/add/ and create a Conditional Alert: +> - Name: ' Payment Conditional Alert' +> - Case type: commcare-user +> - Filter: cohort_id = '' AND session_completion = 'session completed' +> - Action: submit Connect Survey form with delivery unit configured for payment trigger +> (Atom not yet built — 3-form combined POST.)" + +### Step 9: OCS interview-node routing + +Per cohort, the Dynamic Router Bot's StaticRouterNode `keywords` need to include this cohort's interview_ids. Modify the existing node's params: + +``` +# Get current pipeline +pipeline = ocs_get_chatbot_pipeline_id({experiment_id: ocs_bot_id}) +# (read the router node's current keywords, add this cohort's interview_ids, save) +``` + +**Atom gap for V1**: `ocs_update_pipeline_node` not built — only `ocs_add_pipeline_node`. For V1, the StaticRouterNode template already has `keywords: ["default"]`; cohort-specific routing is documented as manual until the update atom ships. + +## Idempotency + +Steps that detect existing state (opp by name, payment unit by name, lookup row by exact match) skip re-create. Operator can re-run the skill if a single step failed. + +## MCP Tools Used + +- `commcare_lookup_table_append_rows`, `commcare_release_build` +- `connect_create_opportunity`, `connect_create_payment_unit`, `connect_set_verification_flags`, + `connect_update_opportunity`, `connect_activate_opportunity`, `connect_list_delivery_types` +- `ocs_get_chatbot_pipeline_id` + +## Manual fallbacks + +V1 manual steps surface in `manual-steps.md`: +- Linked-app copy + rename (atom gap) +- cohort_id user-field choice add (atom gap — hidden-JSON form) +- Conditional alert create (atom gap — 3-form combined POST) +- OCS router-node keywords update (atom gap) + +## Change Log + +| Date | Change | Author | +|------|--------|--------| +| 2026-05-21 | Initial V1 — atom matrix complete except 4 deferred items | ACE team | diff --git a/skills/interview-domain-bootstrap/SKILL.md b/skills/interview-domain-bootstrap/SKILL.md new file mode 100644 index 00000000..b243b0b8 --- /dev/null +++ b/skills/interview-domain-bootstrap/SKILL.md @@ -0,0 +1,252 @@ +--- +name: interview-domain-bootstrap +description: > + One-time-per-project-space plumbing for Connect Interviews launches. + Stands up the master + downstream HQ domains, the per-domain + Connections / Data Forwarding / UCRs / Repeaters / Inbound APIs / + Lookup tables, plus the OCS Dynamic Router Bot. Driven by a small + domain.yaml spec. Manual fallback for the two not-yet-automated + steps (conditional alert + custom user data field). Implements the + per-domain section of docs/connect-interviews/checklist-schema.yaml. +disable-model-invocation: true +--- + +# Interview Domain Bootstrap + +Spin up a fresh CommCare HQ project space pair (master + downstream) wired for the Connect Interviews program. One-shot, idempotent where possible, prints manual prompts where the atom matrix is incomplete. + +## Slash command + +``` +/ace:interview-domain-bootstrap +``` + +## Input + +Single YAML spec. Example: + +```yaml +master_domain: ace-interviews-master # HQ slug for the upstream +downstream_domain: ace-interviews-test # HQ slug for the downstream +hq_owner: ace@dimagi-ai.com # Web user for both domains + +# OCS team that will host the Dynamic Router Bot. Defaults to env OCS_TEAM_SLUG. +ocs_team_slug: connect-ace + +# Connect side +connect_pm_org: ai-demo-space # PM-side Connect org slug +connect_program_name: "ACE Interviews Test" +connect_program_delivery_type: interview # Slug from connect_list_delivery_types +country: Nigeria +currency: NGN + +# UCR expressions to push from master to downstream (one of these is +# required for each Inbound API and Configurable Form Repeater) +ucr_expressions: + - { name: "Register User OCS", id: } # see notes + - { name: "Trigger OCS Bot", id: } + - { name: "Session Completion API", id: } + - { name: "24 hr Expiry API", id: } +``` + +Most ids are resolved at runtime — operator only needs the names. + +## Products + +- `ACE//bootstrap/state.yaml` — what got created (ids, urls) +- `ACE//bootstrap/manual-steps.md` — list of manual steps remaining (conditional alerts + cohort_id user field) + +## Process + +The skill walks the per_domain section of `checklist-schema.yaml` top to bottom. + +### 1. HQ domain creation + +For each of `master_domain` and `downstream_domain`: + +1. Check if it exists by attempting `commcare_list_apps({domain})`. If 401 with "Authorization Required" → domain doesn't exist or no access; create it. +2. `commcare_create_domain({hr_name: })`. Slug must be ≤25 chars. +3. **Manual step (prompt operator):** wait for accounts@ to provision Pro Edition tier on both domains. Skill blocks here until operator confirms `commcare_list_connections({domain})` returns 200 (not 404 — the DATA_FORWARDING privilege gate). + +### 2. Linked-domain relationship + +`commcare_link_domains({upstream_domain: master, downstream_domain: })`. Pro-gated. + +### 3. Master-side content (one-time) + +The team's tech-design says master holds: +- Master Learn + Deliver apps +- 4 UCR expressions (Register User OCS, Trigger OCS Bot, Session Completion API, 24 hr Expiry API) +- Lookup table `interview_schedule` (schema only — no cohort rows) +- Custom user data field definition (cohort_id) + +**Manual step (prompt operator):** +> "Confirm the master domain has the 4 UCR expressions configured. UCR expression creation requires the HQ admin UI (no automation atom yet — see docs/connect-interviews/probe-report.md atom_gaps)." + +If the master is already fully configured (e.g. it's a clone of the team's `connect-interviews` master), skip; otherwise the operator does this by hand. + +For the lookup table — automated: + +``` +commcare_create_lookup_table({ + domain: master_domain, + tag: "interview_schedule", + fields: [ + { field_name: "cohort_id" }, + { field_name: "previous_interview" }, + { field_name: "next_interview" }, + { field_name: "frequency_days" }, + ], +}) +``` + +(Cohort rows are added per-cohort via /ace:interview-cohort-create.) + +### 4. Downstream-side per-domain plumbing + +#### 4a. Subscription + +Confirmed in step 1 (operator-attested). + +#### 4b. Connections + +Two Connections per downstream domain: + +``` +commcare_create_connection({ + domain: downstream_domain, + name: "Connect Interviews", + url: "https://connect.dimagi.com/api/receiver/", + auth_type: "basic", + username: , + plaintext_password: , +}) + +commcare_create_connection({ + domain: downstream_domain, + name: "OCS Interviews Bot", + url: "", + auth_type: "api_key", + plaintext_custom_headers: '{"Authorization": "Token "}', +}) +``` + +Credentials come from `domain.yaml` (encrypted with op:// references — never inline). + +#### 4c. Data forwarding (form forwarder) + +``` +commcare_create_repeater({ + domain: downstream_domain, + repeater_type: "FormRepeater", + connection_settings_id: , + name: "Connect Interviews", + request_method: "POST", + format: "form_json", +}) +``` + +#### 4d. Configurable Form Repeaters + +These reference the UCR expressions (pushed via linked_domain before this step): + +``` +commcare_create_repeater({ + domain: downstream_domain, + repeater_type: "FormExpressionRepeater", + connection_settings_id: , + name: "OCS User Registration", + configured_filter: { type: "named", name: "Register User OCS" }, + configured_expression: { ... }, # named UCR ref +}) + +commcare_create_repeater({ + domain: downstream_domain, + repeater_type: "FormExpressionRepeater", + connection_settings_id: , + name: "Trigger Bot", + configured_filter: { type: "named", name: "Trigger OCS Bot" }, + configured_expression: { ... }, +}) +``` + +#### 4e. Inbound APIs + +Two: + +``` +commcare_create_inbound_api({ + domain: downstream_domain, + name: "Session Completion API", + filter_expression_id: , + backend: "json", +}) + +commcare_create_inbound_api({ + domain: downstream_domain, + name: "24 hr Expiry API", + filter_expression_id: <24hr-expiry-ucr-id>, + backend: "json", +}) +``` + +#### 4f. Lookup table push + +If the downstream's `interview_schedule` table doesn't exist (per `commcare_get_lookup_table`), create it. Cohort rows are added per-cohort. + +#### 4g. Custom user data field + +**Manual step (prompt operator):** +> "Visit /a//users/commcare/user_data/ and add a custom field 'cohort_id' with empty choices. (User Fields create atom not yet built — hidden-JSON form pattern; see verification doc.)" + +### 5. OCS-side Dynamic Router Bot + +Clone or create a per-domain bot from the ACE Interviews Stub Template: + +``` +ocs_clone_chatbot({ + template_id: , + new_name: "Connect Interviews — ", +}) +``` + +This carries forward the StaticRouterNode + LLM + custom action wiring from the V1 stub template. + +### 6. Conditional alert (per-domain initial) + +**Manual step (prompt operator):** +> "Conditional alerts are created per-cohort. For V1, the per-cohort skill prompts you to create each one in the HQ UI at /a//messaging/conditional/add/. (Conditional alert create atom not yet built — the create flow is a 3-form combined POST with dynamic fields; deferred to V1.5.)" + +## Idempotency + +Steps 1, 2, 3, 4b-f, 5 are idempotent: skill checks existence before creating, skips on conflict. + +## Errors and recovery + +- 401 on a Pro-gated endpoint → "subscription not yet provisioned; ask accounts@". +- 404 on a domain → "domain doesn't exist; check the slug or run step 1". +- UCR expression FK not found → "push UCRs from master first; see manual step in §3". + +## MCP Tools Used + +- `commcare_create_domain`, `commcare_link_domains`, `commcare_create_lookup_table`, + `commcare_create_connection`, `commcare_create_repeater`, + `commcare_create_inbound_api`, `commcare_list_apps` +- `ocs_clone_chatbot` (or `ocs_create_chatbot` + structural atoms for from-scratch) +- `connect_create_program` (if Connect program doesn't yet exist for this domain pair) + +## Manual fallbacks + +These are atom gaps deferred to V1.5: +- Subscription provisioning (out-of-band — accounts@) +- UCR expression creation (manual via HQ admin UI) +- Custom user data field creation (manual — hidden-JSON form) +- Conditional alert creation (manual — 3-form combined POST) + +The skill produces `manual-steps.md` listing exactly what the operator still needs to do. + +## Change Log + +| Date | Change | Author | +|------|--------|--------| +| 2026-05-21 | Initial V1 — atom matrix complete except 4 deferred items | ACE team | diff --git a/skills/interview-opp-verify/SKILL.md b/skills/interview-opp-verify/SKILL.md new file mode 100644 index 00000000..745b8c9e --- /dev/null +++ b/skills/interview-opp-verify/SKILL.md @@ -0,0 +1,149 @@ +--- +name: interview-opp-verify +description: > + Read-only verifier for a configured Connect Interviews opportunity. + Walks docs/connect-interviews/checklist-schema.yaml, calls the read + atoms, and grades each rule pass / fail / unverifiable. Cross-system + consistency checks (e.g. OCS custom action target URL == HQ Inbound + API URL) are included. +disable-model-invocation: true +--- + +# Interview Opportunity Verify + +Walk every rule in `docs/connect-interviews/checklist-schema.yaml` against a live opp and produce a verdict report. + +## Slash command + +``` +/ace:interview-opp-verify +``` + +Or with explicit args: + +``` +/ace:interview-opp-verify --opp --org --domain --bot +``` + +## Input + +Resolved from the Connect opp URL: `/a//opportunity//`. Skill fetches the opp, then derives: +- HQ downstream domain (from opp.learn_app.domain) +- OCS bot id (from connections + bot list — defaults to env OCS_INTERVIEWS_TEMPLATE_ID if there's only one) + +## Products + +- `ACE//verify//report.md` — human-readable verdict report +- `ACE//verify//verdict.yaml` — machine-readable per-rule pass/fail +- Exit code 0 if all green, 1 if any fail, 2 if any unverifiable items exist + +## Process + +The skill loads `docs/connect-interviews/checklist-schema.yaml` (in-repo) and iterates every item, calling the named `verify.atom` with the appropriate args. + +### Sections walked (in order): + +1. **per_program** — Connect program + LLO org membership + OCS team +2. **per_domain** — all the HQ plumbing + the OCS bot's structural pieces +3. **per_cohort** — apps, lookup rows, opp config, payment unit, conditional alert, OCS interview nodes +4. **per_user** — cohort_id is set on this cohort's FLWs (V1: only checks the opp's invited users) + +### Verdict shapes: + +- ✅ **pass** — atom returned matching state per the rule +- ❌ **fail** — atom returned and state mismatched +- ⚠️ **unverifiable** — atom doesn't exist yet (gap) OR returned an error we can't classify +- ⊘ **out_of_band** — rule explicitly marked as "humans must attest" (e.g. subscription tier) + +### Cross-system checks + +Some rules check consistency across systems. The verifier resolves IDs cohesively before grading: + +- `ocs-bot-has-completion-action.target_url_matches: "@inbound-api-session-completion.url"` — verifier reads the HQ Inbound API URL, then checks the OCS custom action's `server_url + api_schema operationId path` resolves to the same URL. +- `opp-learn-app-linked.value: "@app-learn-copied.app_id"` — verifier reads the new linked-app id from the cohort spec, checks the opp's `learn_app.cc_app_id` matches. + +### Report format + +```markdown +# Interview Opp Verify — + +**Generated:** 2026-05-22 (run-id 20260522-0030) +**Opportunity:** https://connect.dimagi.com/a//opportunity// +**HQ domain:** **OCS bot:** + +## Summary + +| Pass | Fail | Unverifiable | Out-of-band | Total | +|---|---|---|---|---| +| 38 | 2 | 5 | 3 | 48 | + +## Per-section + +### per_program (3 items) +✅ connect-program-exists — "ACE Interviews Test" found +⊘ llo-orgs-accepted-into-program — out_of_band (ACE-owned program has no external LLOs) +⊘ ocs-team-for-program — out_of_band (OCS_TEAM_SLUG=connect-ace confirmed by env) + +### per_domain (24 items) +... + +### per_cohort (25 items) +... + +## Action items + +For each fail: +- **conditional-alert-payment** — alert " Payment Conditional Alert" not found on /a//messaging/conditional/. Create manually (atom not yet built). + +For each unverifiable: +- **ocs-interview-nodes-per-cohort** — atom gap (ocs_edit_pipeline_structure). Manually inspect the bot at and confirm each interview_id in the cohort's schedule has a router target. +``` + +## Process detail + +1. **Resolve identifiers.** Given the opp URL, call `connect_get_opportunity` to fetch the opp; extract `learn_app.domain` as the downstream HQ domain. + +2. **Load the schema.** Read `docs/connect-interviews/checklist-schema.yaml` from the plugin install dir; iterate per_program / per_domain / per_cohort / per_user items. + +3. **Per item:** + - If `verify.atom` is `null`, mark `unverifiable` with the item's notes. + - If `verify.rule.type` is `out_of_band`, mark `⊘`. + - Otherwise, call the atom with the rule's args (substituting `@.field` references resolved from previously-graded items). + - Apply the rule type: `exists`, `name_matches`, `property_equals`, `filter_matches`, etc. + - Record the verdict. + +4. **Write the report + verdict YAML.** + +## Atom usage map (read-side only) + +| Schema section | Atoms | +|---|---| +| per_program | `connect_list_programs`, `connect_get_program` | +| per_domain HQ | `commcare_list_apps`, `commcare_list_connections`, `commcare_list_inbound_apis`, `commcare_get_lookup_table`, `commcare_list_users` (for user fields presence) | +| per_domain OCS | `ocs_list_chatbots`, `ocs_get_chatbot`, `ocs_get_chatbot_pipeline_id` | +| per_cohort | `commcare_list_apps`, `commcare_get_lookup_table_rows`, `connect_get_opportunity`, `connect_list_payment_units`, `ocs_get_chatbot_pipeline_id`, `ocs_get_chatbot` | +| per_user | `commcare_list_users`, `commcare_get_user` | + +## Unverifiable rules (gap-tagged) + +Per `checklist-schema.yaml § atom_gaps`, these rules currently grade `unverifiable`: + +- `ucr-*` rules — no `commcare_list_ucr_expressions` atom yet (linked-domain push verification deferred) +- `repeater-*` rules — no `commcare_list_form_repeaters` atom yet (only create) +- `custom-user-data` rule (cohort_id field exists) — no `commcare_list_user_fields` atom yet +- `conditional-alert-payment` rule — no `commcare_list_conditional_alerts` atom yet +- `ocs-interview-nodes-per-cohort` rule — no atom to traverse router targets + +For V1 these grade `⚠️ unverifiable` with action-item prompts to operator. V1.5 ships the read atoms to fill these. + +## Exit codes + +- `0` — all rules pass or out_of_band +- `1` — at least one fail +- `2` — no fails but some unverifiable (operator should review action items) + +## Change Log + +| Date | Change | Author | +|------|--------|--------| +| 2026-05-21 | Initial V1 — read-only verifier; ~80% of rules implementable with current atoms | ACE team |