@terreno/mcp 57.9.0 → 57.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/docs/upgrades/57.10.1.md +54 -0
  2. package/dist/docs/versioned/next/README.md +1 -0
  3. package/dist/docs/versioned/next/explanation/README.md +4 -0
  4. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
  5. package/dist/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
  6. package/dist/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
  7. package/dist/docs/versioned/next/explanation/ai-observability.md +101 -0
  8. package/dist/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
  9. package/dist/docs/versioned/next/explanation/example-coverage.md +3 -1
  10. package/dist/docs/versioned/next/explanation/loop-engineering.md +1 -1
  11. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
  12. package/dist/docs/versioned/next/explanation/typescript-configuration.md +1 -1
  13. package/dist/docs/versioned/next/explanation/versioning-policy.md +1 -1
  14. package/dist/docs/versioned/next/how-to/README.md +6 -0
  15. package/dist/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
  16. package/dist/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
  17. package/dist/docs/versioned/next/how-to/ai-feature-development.md +156 -0
  18. package/dist/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
  19. package/dist/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
  20. package/dist/docs/versioned/next/how-to/circleci.md +5 -3
  21. package/dist/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
  22. package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -1
  23. package/dist/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
  24. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
  25. package/dist/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
  26. package/dist/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
  27. package/dist/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
  28. package/dist/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
  29. package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
  30. package/dist/docs/versioned/next/reference/README.md +3 -0
  31. package/dist/docs/versioned/next/reference/admin-config.md +2 -0
  32. package/dist/docs/versioned/next/reference/admin-frontend.md +216 -0
  33. package/dist/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
  34. package/dist/docs/versioned/next/reference/ai-harness.md +1881 -0
  35. package/dist/docs/versioned/next/reference/ai.md +251 -25
  36. package/dist/docs/versioned/next/reference/api.md +46 -0
  37. package/dist/docs/versioned/next/reference/blocks.md +178 -0
  38. package/dist/docs/versioned/next/reference/environment-variables.md +1 -0
  39. package/dist/docs/versioned/next/reference/mcp-server.md +17 -0
  40. package/dist/docs/versioned/next/reference/test.md +11 -2
  41. package/dist/docs/versioned/next/reference/ui.md +325 -1
  42. package/dist/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
  43. package/dist/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
  44. package/dist/docs/versioned/next/tasks/ai-observability.md +233 -104
  45. package/dist/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
  46. package/dist/tools.d.ts +4 -0
  47. package/dist/tools.d.ts.map +1 -1
  48. package/dist/tools.js +37 -0
  49. package/dist/tools.js.map +1 -1
  50. package/package.json +3 -2
  51. package/src/docs/upgrades/57.10.1.md +54 -0
  52. package/src/docs/versioned/next/README.md +1 -0
  53. package/src/docs/versioned/next/explanation/README.md +4 -0
  54. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +2 -2
  55. package/src/docs/versioned/next/explanation/agent-ui-asks.md +299 -0
  56. package/src/docs/versioned/next/explanation/agent-ui-blocks.md +67 -0
  57. package/src/docs/versioned/next/explanation/ai-observability.md +101 -0
  58. package/src/docs/versioned/next/explanation/durable-agent-harness.md +531 -0
  59. package/src/docs/versioned/next/explanation/example-coverage.md +3 -1
  60. package/src/docs/versioned/next/explanation/loop-engineering.md +1 -1
  61. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +29 -1
  62. package/src/docs/versioned/next/explanation/typescript-configuration.md +1 -1
  63. package/src/docs/versioned/next/explanation/versioning-policy.md +1 -1
  64. package/src/docs/versioned/next/how-to/README.md +6 -0
  65. package/src/docs/versioned/next/how-to/agent-ui-asks.md +730 -0
  66. package/src/docs/versioned/next/how-to/agent-ui-blocks.md +104 -0
  67. package/src/docs/versioned/next/how-to/ai-feature-development.md +156 -0
  68. package/src/docs/versioned/next/how-to/build-a-durable-workflow.md +232 -0
  69. package/src/docs/versioned/next/how-to/charts-and-dashboards.md +2 -0
  70. package/src/docs/versioned/next/how-to/circleci.md +5 -3
  71. package/src/docs/versioned/next/how-to/observe-llm-calls.md +197 -0
  72. package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -1
  73. package/src/docs/versioned/next/how-to/ship-a-new-task-version.md +76 -0
  74. package/src/docs/versioned/next/how-to/upgrade-terreno.md +1 -1
  75. package/src/docs/versioned/next/implementationPlans/agent-ui-asks.md +688 -0
  76. package/src/docs/versioned/next/implementationPlans/agent-ui-blocks.md +10 -6
  77. package/src/docs/versioned/next/implementationPlans/ai-observability.md +475 -157
  78. package/src/docs/versioned/next/implementationPlans/durable-agent-harness.md +340 -0
  79. package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -1
  80. package/src/docs/versioned/next/reference/README.md +3 -0
  81. package/src/docs/versioned/next/reference/admin-config.md +2 -0
  82. package/src/docs/versioned/next/reference/admin-frontend.md +216 -0
  83. package/src/docs/versioned/next/reference/agent-ui-asks.md +1059 -0
  84. package/src/docs/versioned/next/reference/ai-harness.md +1881 -0
  85. package/src/docs/versioned/next/reference/ai.md +251 -25
  86. package/src/docs/versioned/next/reference/api.md +46 -0
  87. package/src/docs/versioned/next/reference/blocks.md +178 -0
  88. package/src/docs/versioned/next/reference/environment-variables.md +1 -0
  89. package/src/docs/versioned/next/reference/mcp-server.md +17 -0
  90. package/src/docs/versioned/next/reference/test.md +11 -2
  91. package/src/docs/versioned/next/reference/ui.md +325 -1
  92. package/src/docs/versioned/next/tasks/agent-ui-asks.md +213 -0
  93. package/src/docs/versioned/next/tasks/agent-ui-blocks.md +2 -1
  94. package/src/docs/versioned/next/tasks/ai-observability.md +233 -104
  95. package/src/docs/versioned/next/tasks/durable-agent-harness.md +183 -0
@@ -0,0 +1,197 @@
1
+ # Observe LLM calls
2
+
3
+ Register observability on the backend. Registration **is** the feature flag. Then run product iteration in [Develop an AI feature](ai-feature-development.md).
4
+
5
+ ## Register
6
+
7
+ ```typescript
8
+ import {access} from "./access";
9
+
10
+ const priceMap = parseObservabilityPriceMap(process.env.AI_OBS_PRICE_MAP_JSON);
11
+
12
+ server.register(new ObservabilityApp({
13
+ accessControl: access,
14
+ aiService,
15
+ plugins: [localPlugin, langfuseAdapter, otelSink].filter(Boolean),
16
+ control: {
17
+ datasets: process.env.AI_OBS_DATASETS_PRIMARY ?? "local",
18
+ experiments: process.env.AI_OBS_EXPERIMENTS_PRIMARY ?? "local",
19
+ prompts: process.env.AI_OBS_PROMPTS_PRIMARY ?? "local",
20
+ reviewQueue: "local",
21
+ },
22
+ priceMap,
23
+ sampleRate: Number(process.env.AI_OBS_SAMPLE_RATE ?? 0),
24
+ }));
25
+ ```
26
+
27
+ For an admin playground that accepts a user-supplied provider key, add
28
+ `requestAiServiceFactory: ({apiKey, modelId}) => ...`. The playground route passes
29
+ `x-ai-api-key` to that factory only when the server-wide `aiService` is absent. Never persist or
30
+ log the key. `GET /ai/observability/status` exposes `playgroundAi.source` so admin UIs can block
31
+ **Run once** with a host-specific hint (`request-key`) instead of surfacing the backend **503**
32
+ missing-key title as a server misconfiguration.
33
+
34
+ Validate the parsed object before registration: each model needs non-negative numeric
35
+ `inputPerMTok` and `outputPerMTok`. The example implementation is
36
+ `example-backend/src/utils/observabilityConfig.ts`; malformed values fail startup with the
37
+ variable name.
38
+
39
+ Keep existing `LangfuseApp` if you already use Langfuse keys. The Langfuse **adapter** must reuse `getLangfuseClient()` and must not start a second OpenTelemetry SDK.
40
+
41
+ Boot **fails** if `experiments.primary !== datasets.primary`, if `reviewQueue` is `langfuse`, or if a primary’s plugin is missing.
42
+
43
+ Pass the same `access` object you use for `TerrenoApp` and `AdminApp` so
44
+ `GET /ai/observability/status` permissions match `/admin/config` screen filtering and every
45
+ observability route enforces RBAC.
46
+
47
+ ### Example consumer roles
48
+
49
+ The example backend seeds two optional roles in `example-backend/src/rbacRoles.ts`. They are
50
+ **not** assigned automatically — attach them in the admin **Roles** UI or on `User.roles`.
51
+
52
+ | Role | Grants |
53
+ | --- | --- |
54
+ | `aiObservabilityViewer` | `admin:access` plus `list` and `read` on all six observability resources |
55
+ | `aiObservabilityOperator` | `admin:access` plus every approved action on those resources |
56
+
57
+ Compose your own roles the same way: grant `admin: ["access"]` for the admin shell, then add
58
+ only the `aiPrompt` / `aiTrace` / `aiReview` / `aiDataset` / `aiEvaluator` / `aiExperiment`
59
+ actions each persona needs. Prompt `folder` and `description` stay consumer-defined metadata —
60
+ Terreno does not ship domain-specific folder names or an in-app approval workflow.
61
+
62
+ ## Env
63
+
64
+ | Variable | Role |
65
+ | --- | --- |
66
+ | `AI_OBS_PROMPTS_PRIMARY` | `local` or `langfuse` (default `local`) |
67
+ | `AI_OBS_DATASETS_PRIMARY` | default `local` |
68
+ | `AI_OBS_EXPERIMENTS_PRIMARY` | must equal datasets |
69
+ | `AI_OBS_SAMPLE_RATE` | `0`–`1`, default `0` (live evals off) |
70
+ | `AI_OBS_PRICE_MAP_JSON` | `{ "gemini-2.5-flash": { "inputPerMTok": 0.1, "outputPerMTok": 0.4 } }` |
71
+ | `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` | existing Langfuse plugin |
72
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | optional OTLP trace sink |
73
+
74
+ ## App generate calls
75
+
76
+ Pass identity and the production prompt ref so traces feed the SOP loop:
77
+
78
+ ```typescript
79
+ await aiService.generateText({
80
+ prompt: text,
81
+ promptLabel: "production",
82
+ promptName: "example-summarize",
83
+ sessionId: req.header("x-ai-session-id"),
84
+ userId: req.user?.id,
85
+ });
86
+ ```
87
+
88
+ Opt out of tracing with `skipTrace: true`. Sink failures are logged and never fail the generate. `AIRequest` is still written.
89
+
90
+ ## Admin
91
+
92
+ Phase 1 admin ships **Prompts**, **Traces**, and **Review**. Datasets, Evaluators, and
93
+ Experiments screens arrive in phase 2. **Open in Langfuse** appears in phase 3 when the
94
+ Langfuse plugin is registered. Review is hidden when the local plugin is off.
95
+
96
+ On **Traces**, **Run multi-stage trace test** (local sink only) runs two schema-backed
97
+ `generateJsonObject` calls, a deterministic tool stage, and a final schema-backed combine
98
+ call, then opens the nested trace. Each LLM span input includes `outputSchema`. It uses the
99
+ server `aiService` when one is configured; otherwise it sends the admin's saved key as
100
+ `x-ai-api-key` to `requestAiServiceFactory`, and the button stays disabled with a hint until
101
+ that key exists.
102
+
103
+ ## Run the example locally
104
+
105
+ 1. Set `MONGO_URI` to a replica set and the auth secrets from
106
+ `example-backend/.env.example`.
107
+ 2. Run `bun run backend:seed`. The idempotent seed creates `examples/example-summarize`
108
+ v1 with `production` pointing to v1 and candidate v2, plus the human
109
+ `correctness-human` evaluator, automatic `schema-assert` evaluator, and
110
+ two-item `example-gold` dataset. It also creates `chat-safety-synthetic` (12 proofread
111
+ two-person chats), `chat-safety-screen` v1 and v2, and the `chat-safety-agreement`
112
+ evaluator. On a database that already booted, run Admin → Scripts →
113
+ `seedChatSafetyDataset` as a wet run, or
114
+ `cd example-backend && bun run script seedChatSafetyDataset --wet`.
115
+ 3. Start `bun run backend:dev` and `bun run frontend:web`, then sign in as the seeded
116
+ admin.
117
+ 4. Open **AI Observability → Prompts** to inspect or save a new immutable version. Move
118
+ `production` explicitly; the seed never overwrites an existing label.
119
+ 5. Configure `GOOGLE_VERTEX_PROJECT` or `GEMINI_API_KEY`, then sign in with Better Auth
120
+ and call the seeded route:
121
+
122
+ ```bash
123
+ API=http://localhost:4000
124
+ COOKIE_JAR="$(mktemp)"
125
+ curl -sS -c "$COOKIE_JAR" \
126
+ -H "Content-Type: application/json" \
127
+ -d '{"email":"admin@example.com","password":"testpassword123"}' \
128
+ "$API/api/auth/sign-in/email" >/dev/null
129
+
130
+ curl -X POST "$API/ai/example-summarize" \
131
+ -b "$COOKIE_JAR" \
132
+ -H "Content-Type: application/json" \
133
+ -H "x-ai-session-id: docs-walkthrough" \
134
+ -d '{"text":"Terreno records local nested traces for every AI call."}'
135
+ ```
136
+
137
+ 6. Open its `example-summarize` trace, send it to Review, score **correct**, and submit
138
+ until the queue reports **Queue clear**.
139
+
140
+ The example app also calls this route from the UI: **Todos → Summarize** sends the user's todo
141
+ list, so each press produces a trace with the production prompt reference, `userId`, and a
142
+ per-app-run `x-ai-session-id`. Without server credentials the screen forwards the Gemini key
143
+ saved in **Profile** as `x-ai-api-key`; otherwise the route answers **503**.
144
+
145
+ The route sends `text` as the user prompt. The labelled registry version supplies the
146
+ system prompt and the route uses `TemperaturePresets.LOW`.
147
+
148
+ The example registers the local plugin even when no Langfuse keys exist. Set
149
+ `AI_OBS_PRICE_MAP_JSON` to calculate USD cost; unlisted models show tokens with no cost,
150
+ never `$0`.
151
+
152
+ ## Create a prompt and pin production
153
+
154
+ Admin-only routes live under `/ai/observability`. Create the prompt in a folder, save later edits as `vN+1` (v1 stays immutable), then move `production` — the response names the outgoing version for the confirm modal.
155
+
156
+ ```bash
157
+ # 1. Create v1 in a folder
158
+ curl -X POST "$API/ai/observability/prompts" \
159
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
160
+ -H "Content-Type: application/json" \
161
+ -d '{"folder":"examples","name":"example-summarize","system":"Summarize {{text}}","template":"{{text}}","type":"text"}'
162
+
163
+ # 2. Pin production (first pin has no outgoing version)
164
+ curl -X POST "$API/ai/observability/prompts/example-summarize/labels" \
165
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
166
+ -H "Content-Type: application/json" \
167
+ -d '{"label":"production","version":1}'
168
+
169
+ # 3. Save as v2, then move production (response.outgoingVersion is 1)
170
+ curl -X POST "$API/ai/observability/prompts/example-summarize/versions" \
171
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
172
+ -H "Content-Type: application/json" \
173
+ -d '{"system":"Summarize {{text}} in one sentence","template":"{{text}}","type":"text"}'
174
+ curl -X POST "$API/ai/observability/prompts/example-summarize/labels" \
175
+ -H "Authorization: Bearer $ADMIN_TOKEN" \
176
+ -H "Content-Type: application/json" \
177
+ -d '{"label":"production","version":2}'
178
+ ```
179
+
180
+ `GET /ai/observability/prompts?folder=examples&search=sum&include=usage7d` lists folder matches with 7-day call/cost rollups. A prompt with no `production` label returns `production: "—"`. Playground `POST /ai/observability/prompts/:name/playground` compiles `{{var}}`, runs one `AIService` call, and does not create a version.
181
+
182
+ The example backend registers `requestAiServiceFactory` but no server `aiService`, so status reports
183
+ `playgroundAi.source: "request-key"`. In **AI Observability → Prompts → Playground**, save a Gemini
184
+ API key on **Profile** first; the admin UI blocks **Run once** with that hint until the key loads
185
+ from storage and is sent as `x-ai-api-key`. **Traces → Run multi-stage trace test** uses the same
186
+ saved key and is blocked the same way.
187
+
188
+ ## Review a trace
189
+
190
+ 1. Use the seeded human `correctness-human` evaluator. If you skipped `bun run backend:seed`,
191
+ install it once with `POST /ai/observability/evaluators/templates/correctness-human`; a
192
+ duplicate install returns 409.
193
+ 2. Enqueue traces: `POST /ai/observability/traces/review` with `{evaluatorId, traceIds, reason: "manual"}`.
194
+ 3. List oldest-first: `GET /ai/observability/review?status=pending` (response includes per-status counts).
195
+ 4. Open an item: `GET /ai/observability/review/:id` returns evaluator dimensions plus `given` / `wrote` panels (variable labels when present, otherwise raw keys).
196
+ 5. `POST /ai/observability/review/:id` with `action: "submit"` writes dimension-keyed scores through every ScoreSink and marks the item `done` (it leaves the pending list). `skip` and `assign` move status to `skipped` / `in_progress`.
197
+
@@ -48,6 +48,7 @@ bun test --only-failures <path>
48
48
  | --- | --- |
49
49
  | `api-ci` | `bun run api:test` |
50
50
  | `ai-ci` | `cd ai && bun run test:ci` |
51
+ | `blocks-ci` | `bun run blocks:test` |
51
52
  | `ui-ci` | `bun run ui:test` |
52
53
  | `rtk-ci` | `bun run rtk:test` |
53
54
  | `syncdb-ci` | `bun run syncdb:test` |
@@ -81,7 +82,7 @@ MONGO_URI="mongodb://127.0.0.1:27017/terreno-e2e?replicaSet=rs0" \
81
82
  | `e2e` `auth` | `login` `signup` `consents` `forgot-password` `reset-password` `verify-email` |
82
83
  | `e2e` `app` | `todos` `profile` `realtime` `ai-chat` `pdf` |
83
84
  | `e2e` `admin-core` | `admin` `admin-home` `admin-form` `admin-todo-crud` `admin-title-update-depth` |
84
- | `e2e` `admin-table` | `admin-table-search-filter` `admin-table-bulk-actions` `admin-custom-screens` `admin-comms-back` |
85
+ | `e2e` `admin-table` | `admin-table-search-filter` `admin-table-bulk-actions` `admin-custom-screens` `admin-comms-back` `harness-approvals` `harness-intake` |
85
86
  | `e2e` `syncdb` | `syncdb-load-delta` `syncdb-offline` `syncdb-conflicts` `syncdb-storage` `syncdb-chaos` |
86
87
 
87
88
  Full local suite: `bun run frontend:e2e`. Nightly load (`e2e-load` / `syncdb-loadlab`) is
@@ -0,0 +1,76 @@
1
+ # Ship a new task version
2
+
3
+ Change a durable harness task without breaking runs that already started on the old code.
4
+ Rules: [Versioning](../reference/ai-harness.md#versioning). Why:
5
+ [Version pinning](../explanation/durable-agent-harness.md#version-pinning).
6
+
7
+ ## When to bump `version`
8
+
9
+ Bump it on any change an in-flight task could notice:
10
+
11
+ - A phase is renamed, removed, or added between existing phases.
12
+ - The shape of `state`, `input`, or the result changes.
13
+ - A phase's side effects or `replay` policy change.
14
+
15
+ A fix that keeps the same phases, state shape, and side effects can ship under the same
16
+ version.
17
+
18
+ ## Steps
19
+
20
+ 1. Copy the definition and bump `version`. Keep the old one unchanged.
21
+
22
+ ```typescript
23
+ export const intakeV1 = defineTask<IntakeInput, IntakeStateV1, IntakeResult>({
24
+ name: "clinic.intake",
25
+ version: 1,
26
+ // ...the old phases, untouched
27
+ });
28
+
29
+ export const intakeV2 = defineTask<IntakeInput, IntakeStateV2, IntakeResult>({
30
+ name: "clinic.intake",
31
+ version: 2,
32
+ // ...the new phases
33
+ });
34
+ ```
35
+
36
+ 2. Register both versions. Point new work at v2.
37
+
38
+ ```typescript
39
+ const harness = await Harness.open({registry: [intakeV1, intakeV2]});
40
+ await harness.start();
41
+
42
+ await harness.createTask(intakeV2, {patientId});
43
+ ```
44
+
45
+ Tasks created on v1 finish on v1's phases and v1's `abort` handler. New tasks run on
46
+ v2. Parent tasks that call `rt.createTask(intakeV1, ...)` keep making v1 children
47
+ until you change that call.
48
+
49
+ 3. Deploy. Every process that runs the harness needs the same registry.
50
+
51
+ 4. Wait until no v1 task is left in flight. Check in a Mongo shell:
52
+
53
+ ```javascript
54
+ db.harnesstasks.countDocuments({
55
+ name: "clinic.intake",
56
+ version: 1,
57
+ status: {$in: ["pending", "running", "waiting", "interrupted"]},
58
+ });
59
+ ```
60
+
61
+ Resolve stuck `interrupted` v1 tasks with `harness.resolveInterrupted`, or stop them
62
+ with `harness.abort`.
63
+
64
+ 5. When the count is 0, remove `intakeV1` from the registry and deploy again.
65
+
66
+ ## If you remove a version too early
67
+
68
+ `Harness.start()` throws an `APIError` with code `harness-config-invalid` and claims
69
+ nothing. Its `detail` reads:
70
+
71
+ ```text
72
+ Harness.start: in-flight tasks use task versions this registry does not register: clinic.intake@1 (4 tasks). Register those definitions (keep old versions until their tasks finish) or resolve the tasks first.
73
+ ```
74
+
75
+ Fix: add the old definition back to `registry` and redeploy. Its tasks resume from their
76
+ last checkpoint.
@@ -38,7 +38,7 @@ Maintainers watching the next SDK beta use `track-upstream-expo` / [`track-upstr
38
38
 
39
39
  ### Backend vs frontend packages
40
40
 
41
- From `publish-on-tag.yml`: **backend** `api`, `test`, `admin-backend`, `ai`, `api-health`, `comms`, `feature-flags`, `mcp`. **frontend** `ui`, `rtk`, `syncdb`, `admin-frontend`, `admin-spa`.
41
+ From `publish-on-tag.yml`: **backend** `api`, `test`, `admin-backend`, `blocks`, `ai`, `api-health`, `comms`, `feature-flags`, `mcp`. **frontend** `ui`, `rtk`, `syncdb`, `admin-frontend`, `admin-spa`. `blocks` holds contracts that both `ai` and `ui` depend on, so it moves with the backend step.
42
42
 
43
43
  ## 4. RTK → syncdb is not a version bump
44
44