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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"url": "https://github.com/jjackson"
},
"metadata": {
"version": "0.13.276"
"version": "0.13.277"
},
"plugins": [
{
"name": "ace",
"source": "./",
"version": "0.13.276",
"version": "0.13.277",
"description": "AI Connect Engine — orchestrates the CRISPR-Connect lifecycle from idea through app building, Connect setup, LLO management, and closeout"
}
]
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ace",
"version": "0.13.276",
"version": "0.13.277",
"description": "AI Connect Engine — orchestrates the CRISPR-Connect lifecycle from idea through app building, Connect setup, LLO management, and closeout",
"author": {
"name": "Jonathan Jackson",
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ All notable changes to the ACE plugin will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the plugin follows [semantic versioning](https://semver.org/spec/v2.0.0.html).

## 0.13.277 — 2026-05-18

**Mirror Vellum's slug/name separation in the Nova architect brief (follow-up to 0.13.274).**

The 0.13.274 fix capped module/deliver-unit *name* length at 40 chars as a workaround for Connect's 50-char `LearnModule.slug` / `DeliverUnit.slug` columns. That treats the symptom — the actual cause is that Nova's default `<learn:module id>` derivation conflates the Connect slug (which feeds those columns) with the human-readable display name (which can be long and rich).

The HQ-side authoring source of truth — `dimagi/Vellum:src/commcareConnect.js`, the Vellum plugin that humans use in the form designer when the `COMMCARE_CONNECT` flag is on — keeps these as two SEPARATE fields. The form's `nodeID` is the slug (user types something short and code-like like `module_1` or `m6_sample_prep`); the `name` element is the display label (any length, any character set). The fixture at `tests/static/commcareConnect/learn_module.xml` confirms: `<module id="module_1"><name>module 1</name>…</module>`. Vellum-authored apps don't hit the slug-length trap because humans naturally pick short identifiers.

Nova's API already supports this split — `connect.learn_module.id` is an optional field on `update_form` — but ACE's brief template never told the architect to set it, so Nova defaulted to `module_<idx>_<slugify(name)>` and inherited the column-overflow risk.

Three brief-template additions to `pdd-to-{learn,deliver}-app/SKILL.md`:

1. **New REQUIRED clause** — every `connect.learn_module` / `connect.assessment` / `connect.deliver_unit` / `connect.task` block MUST include an explicit `id` field (8-20 chars, lowercase, snake_case, stable across renames of the display name). Examples: `m1_background`, `m6_sample_prep`, `wohl_shipment`. This is now the load-bearing rule; the ≤40-char name fallback below becomes defense-in-depth.

2. **New REQUIRED clause (Learn only)** — `connect.learn_module.time_estimate` is in **HOURS**, not minutes. Vellum's plugin help text and Connect's model docstring both say hours; an architect on the LEEP run set 10-20 (intending minutes) which Connect would have stored as 10-20-hour modules. For typical modules this is 1 or 2.

3. **Reframed the ≤40-char name clause** — explicitly labelled FALLBACK, kicks in only when the explicit-id rule above is missed. Removal criterion split: drop the fallback when commcare-connect#1195 lands + `SLUG_LENGTH_LIMIT` bumps in lock-step, but KEEP the explicit-id rule indefinitely — it's a cleanliness invariant matching Vellum's separation, not a workaround.

`docs/learnings/2026-05-17-connect-slug-length-50-char-trap.md` § Generalization extended with the full Vellum source citations + a table of other Vellum-prescribed rules Nova should mirror (`time_estimate` integer-only validator → currently Nova allows decimals via `"type": "number"`; `relevantAttr` for conditional Connect-block display → not currently exposed; `ConnectWorkAreaUpdate` mug type → entire feature missing from Nova's vocabulary).

No test or MCP code changes — purely brief-template updates + doc additions. The structural backstop (`app-release` Step 6 `oversized_slugs` projection gate, shipped in 0.13.274) remains the wall.

## 0.13.276 — 2026-05-18

**Sweep-labs docs follow up connect-labs PR #197 (cascade-based delete gate).**
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.13.276
0.13.277
64 changes: 63 additions & 1 deletion docs/learnings/2026-05-17-connect-slug-length-50-char-trap.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,69 @@ Plus migration. The 50-char default has no operational justification (slug strin

Removal criterion for the ACE-side preventers: drop the brief-template REQUIRED clauses and bump `SLUG_LENGTH_LIMIT` (and any test fixtures relying on it) in lock-step with the Connect column widening.

## Generalization
## Generalization (Vellum-as-source-of-truth)

**The HQ-side authoring source of truth for Connect blocks is the Vellum plugin at `dimagi/Vellum:src/commcareConnect.js`.** That file is the spec Nova's `compile_app` should mirror — and it makes a key separation Nova currently elides: the Connect slug (`<learn:module id="…">`) comes from the form's `nodeID` field (an explicit user-supplied identifier), NOT from a slugified display name. The `name` element is a SEPARATE field that holds the human-readable label.

From `src/commcareConnect.js:117-173` (Learn Module spec):

```javascript
ConnectLearnModule: {
rootName: "module",
childNodes: [
{id: "name", writeToData: true},
{id: "description", writeToData: true},
{id: "time_estimate", writeToData: true},
],
mugOptions: { spec: {
nodeID: { lstring: 'Module ID' }, // ← slug; user-supplied directly
name: { lstring: 'Name', required: true },
description: { ... required ... },
time_estimate: {
... required ...,
validationFunc: mug => /^\d+$/.test(mug.p.time_estimate) ? 'pass' : 'Must be an integer',
help: 'Estimated time to complete the module in hours.',
},
} }
}
```

And the emitter at `dataChildFilter` (lines 89-115):

```javascript
getExtraDataAttributes: () => ({
"xmlns": CCC_XMLNS,
"id": mug.p.nodeID, // ← the slug comes from nodeID, NOT a slugified name
})
```

The Vellum test fixture `tests/static/commcareConnect/learn_module.xml` confirms:

```xml
<module xmlns="http://commcareconnect.com/data/v1/learn" id="module_1">
<name>module 1</name>
<description>...</description>
<time_estimate>2</time_estimate>
</module>
```

Two distinct values: `id="module_1"` (short, code-like, picked as the form's nodeID) and `<name>module 1</name>` (display label, can be long). Vellum-authored apps don't have the slug-length problem because the user picks a short stable identifier for the nodeID and writes their descriptive title in `name`.

**Nova's behavior diverges.** Nova's `compile_app` derives the Connect `id` as `module_<idx>_<slugify(name)>` when the architect doesn't set `connect.learn_module.id` explicitly — conflating two fields Vellum keeps separate. The architect-brief REQUIRED clauses (this learning doc → `pdd-to-{learn,deliver}-app/SKILL.md`) instruct the architect to ALWAYS set `id` explicitly, mirroring what a human Vellum author naturally does.

**Other Vellum-prescribed rules Nova should mirror:**

| Vellum rule | Source | Currently in Nova/ACE? |
|---|---|---|
| `time_estimate` is in **hours**, not minutes | `src/commcareConnect.js:158` ("Estimated time to complete the module in hours") + Connect's `LearnModule.time_estimate` docstring | NOW in `pdd-to-learn-app/SKILL.md` brief template (added 0.13.275) — previously not specified; an architect on the LEEP run set 10-20 (intending minutes) which Connect would have stored as 10-20 hours |
| `time_estimate` MUST match `^\d+$` (positive integer only) | `src/commcareConnect.js:155` (`validationFunc`) | Nova's API schema says `"type": "number"` (allows decimals) — narrower validator should be on the Nova side |
| `connect.deliver_unit.entity_id` / `entity_name` are OPTIONAL XPath fields | `src/commcareConnect.js:240, 249` (`presence: 'optional'`) | Nova has them but the `update_form` deliver_unit runtime auto-fills broken defaults (`voidcraft-labs/nova-plugin#1`) — known upstream bug |
| `relevantAttr` (XPath) for conditional display of the Connect block itself | `src/commcareConnect.js:27-40` (`logicSection`), applied to every Connect mug type | Not exposed in Nova — no current PDD needs it, low priority |
| New mug type `ConnectWorkAreaUpdate` — FLW-driven work-area issue reporting (status, reason, additional_details, photo_evidence) | `src/commcareConnect.js:321-402` | Not in Nova's vocabulary at all — possible new Connect feature we haven't surfaced. File a spec doc when relevant. |

**Why this matters operationally.** Once the architect sets `id` explicitly, the brief's ≤40-char-name fallback becomes a defense-in-depth safety net rather than the primary rule. After commcare-connect#1195 widens the columns to 255 (or whatever), drop the ≤40-char fallback — but KEEP the explicit-id rule, because it's not a workaround. It's a cleanliness invariant matching Vellum's slug/name separation and stays correct regardless of upstream column width.

## Two failure shapes, one bug class

The 2026-05-12 `short_description` 50-char trap and this 2026-05-17 slug-length trap are **the same shape of bug at different boundaries**:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ace",
"version": "0.13.276",
"version": "0.13.277",
"description": "AI Connect Engine - orchestrator for building Connect Opps using AI",
"type": "module",
"scripts": {
Expand Down
76 changes: 52 additions & 24 deletions skills/pdd-to-deliver-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,33 +104,61 @@ plugin (`voidcraft-labs/nova-marketplace`, slash command
operator gets clean diagnostic + auto-recovery instead of "Cannot
make new version" + a CCHQ UI peek. See
`docs/learnings/2026-04-29-nova-connect-marker-bugs.md` § Bug 4.
- **REQUIRED — Keep deliver_unit names short enough that the derived
slug fits Connect's 50-char column.** Insert this paragraph
**verbatim** into the brief, in its own paragraph, prefixed
`REQUIRED:`:

> REQUIRED: Every `connect.deliver_unit.name` you set MUST be ≤ 40
> characters. Nova's `compile_app` derives the `<learn:deliver id>`
> slug from the module slug (which itself comes from the module's
> display name slugified), and Connect's `DeliverUnit.slug` column
> is `SlugField()` with the Django default `max_length=50`. A
> longer module/deliver-unit name slugifies past 50 and triggers
> Postgres `DataError: value too long for type character varying(50)`
> at Connect's sync, which surfaces as an opaque HTTP 500 from
> `connect_create_opportunity` with no diagnostic. Prefer short
> active titles like "Stage 2: Sample Prep + Shipment" over the
> full descriptive form — there's no description field on
> DeliverUnit but the name is what shows up in the deliver-unit
> picker on Connect, so terseness helps readability too.
- **REQUIRED — Set `connect.deliver_unit.id` AND `connect.task.id`
explicitly to short stable identifiers, separately from the human-
readable `name`.** This is the load-bearing constraint; the ≤40-char
name fallback below is just a safety net. Insert this paragraph
**verbatim** into the brief, in its own paragraph, prefixed `REQUIRED:`:

> REQUIRED: Every `connect.deliver_unit` and `connect.task` block
> MUST include an explicit `id` field. The id is the Connect slug —
> it MUST be short (8-20 chars), lowercase, snake_case, code-like,
> and stable across renames of the human-readable name. Examples:
> `shop_registration`, `sample_prep_initial`, `wohl_shipment`. Do
> NOT rely on Nova's default derivation (which slugifies the module
> name) — that conflates the Connect slug with the display name and
> trips Connect's 50-char `DeliverUnit.slug` column on any name that
> slugifies past ~40 chars. The `name` field is a separate, human-
> readable string that can be any length and is what shows up in
> the deliver-unit picker on Connect — terseness is preferred for
> picker readability but not required for correctness once the id
> is set explicitly. Vellum-authored apps (the human-driven
> authoring path in HQ's form designer) separate these into two UI
> fields ("Delivery Unit ID" / "Task ID" and "Name") and humans
> naturally pick short identifiers; Nova's API exposes the same two
> fields but the architect has to set both explicitly because there's
> no UI to nudge the separation. See
> `docs/learnings/2026-05-17-connect-slug-length-50-char-trap.md`
> § Generalization (Vellum-as-source-of-truth) for the full mechanism
> + source citations.

- **REQUIRED — Keep deliver_unit/task names short enough that the
derived slug fits Connect's 50-char column (FALLBACK).** This is
the defense-in-depth fallback for cases where the explicit-id rule
above is missed. Insert this paragraph **verbatim** into the brief,
in its own paragraph, prefixed `REQUIRED:`:

> REQUIRED: If you have not set `connect.deliver_unit.id` /
> `connect.task.id` explicitly per the rule above, the `name` field
> MUST be ≤ 40 characters as a fallback — Nova's default slug
> derivation overflows Connect's 50-char `DeliverUnit.slug` /
> `TaskType.slug` column on longer names and triggers an opaque
> HTTP 500 from `connect_create_opportunity`. Prefer the explicit-id
> rule above (cleaner; lets `name` be any length); this clause
> exists only because architects sometimes skip the id field.

Reproducer + class-level preventer: see
`pdd-to-learn-app/SKILL.md` § REQUIRED — Keep module names short.
The structural backstop is `app-release` Step 6's
`projected_connect_state.oversized_slugs.deliver_units` gate.
Removal criterion: drop this constraint when the upstream
commcare-connect PR widens `DeliverUnit.slug` to `max_length=255`
`pdd-to-learn-app/SKILL.md` § REQUIRED — Set id explicitly. The
structural backstop is `app-release` Step 6's
`projected_connect_state.oversized_slugs.deliver_units` /
`oversized_slugs.task_units` gate. Removal criteria: (a) drop the
≤40-char fallback when the upstream commcare-connect PR widens
`DeliverUnit.slug` to `max_length=255` (already `=100` since a prior
fix) AND `TaskType.slug` to `max_length=255` (dimagi/commcare-connect#1195)
and `SLUG_LENGTH_LIMIT` in `mcp/connect/backends/commcare.ts` is
bumped in lock-step.
bumped in lock-step. (b) KEEP the explicit-id rule even after the
column widens — it's a cleanliness invariant matching Vellum's
slug-vs-name separation, not just a workaround for the column width.
- **REQUIRED — Architect must verify-then-retry every `add_fields`
call.** Nova's `add_fields` has a partial-persistence quirk: a
single call with N items often persists only the first few. The
Expand Down
Loading
Loading