Skip to content

feat(subscription): SDK factory + pipeline - #95

Open
pl wants to merge 4 commits into
pl/spec-migrationfrom
pl/resource-subscriptions
Open

feat(subscription): SDK factory + pipeline#95
pl wants to merge 4 commits into
pl/spec-migrationfrom
pl/resource-subscriptions

Conversation

@pl

@pl pl commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Adds subscriptions (scheduled insight/dashboard delivery) — second of the two Wave 2 greenlit resources.

Identity

Title-marker pattern (iac:subscriptions:<key> in title) as designed. A subscription references exactly one insight or dashboard by key (resolved to an id at execute time; validation enforces exactly-one + same-run declaration). Email/Slack targets, recurrence, and AI-summary fields round-trip as plain fields.

Delivery safety (your warning — handled + verified)

send_test_now defaults to true on the API and delivers immediately on save. The client forces it false on every create, update, and the pull tag-back — apply never delivers as a side effect. Live-verified the deliveries list stayed empty (count 0) across create and update. Note: applying an email subscription still invites its recipients (inherent product behavior) — verification used example.com throwaways; documented in the SDK.

⚠️ Surprise / constraint — please sanity-check

The server caps title at 100 chars, and title also carries the identity+hash marker (~53 + key length). Effective budget: title + key ≲ 45. Other free-text fields aren't viable carriers (prompt is mutually exclusive with insight/dashboard; invite_message is email-only and user-facing; summary_prompt_guide is AI-only). I didn't catch this in the identity investigation (used short values). It's not silent loss — validation now fails fast with an actionable message when title+marker would exceed 100 (pre-empting the API 400) — but it's a real usability limit on title length. Flagging in case you'd rather revisit the carrier; the resource is fully functional for normal-length titles.

Because a STAMP-length smoke key can't round-trip through pull within that budget, subscriptions are excluded from the smoke seed (like singletons, with a header note) and verified in isolation.

Shared infra (dedupe with #94)

This branch reintroduces two commits identical to the annotations PR (#94): feat(apply): expose dashboard ids by key in ApplyContext (needed for dashboard refs) and fix(pull): tolerate dependency kinds absent from a --kind subset. If both merge, they dedupe trivially.

Live verification (project 806)

create → no-op re-apply (hash projection correct) → edit → single update → cross-resource ref (insight resolved to id) → orphan left alone → hand-built subscription (no marker) untouched → kind-scoped prune deletes managed only. Delete is soft-delete via PATCH {deleted:true} (DELETE → 405).

Scope: subscription:read / subscription:write (+ insight:* / dashboard:*). Gates green (typecheck / typecheck:examples / test 307 / lint).


Stacks on #81 (base pl/spec-migration). Retarget to main when #81 merges.

🤖 Generated with Claude Code

pl and others added 4 commits July 24, 2026 00:51
Add dashboardIdByKey / dashboardKeyByServerId to ApplyContext, populated by the
dashboard executor (create/update/unchanged) and the plan display pass, mirroring
the existing insightIdByKey wiring. Lets resources that reference a dashboard by
key — annotations (scope: dashboard) and subscriptions — resolve the server id
at execute time and render the key in plans.

No behavior change for existing resources; the maps are additive.

Note: identical to the same commit on the annotations branch (#94). If both
merge, the ApplyContext addition dedupes trivially.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull orders the targeted `--kind` subset with topoOrder and its comment already
claimed it "tolerates missing edges" — but topoOrder threw when a target's
dependsOn pointed outside the input set. So `pull --kind subscriptions` (which
depends on insights + dashboards) failed unless you also named those kinds.

Add a `lenient` option to topoOrder that drops out-of-set edges instead of
throwing, and use it from pull. Apply still uses strict mode.

Note: identical to the same commit on the annotations branch (#94); dedupes
trivially if both merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add subscriptions list/create/retrieve/partial_update to the openapi allowlist
and regenerate src/generated/api.d.ts. No destroy — subscriptions soft-delete
via PATCH { deleted: true } (the DELETE verb is 405).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the subscriptions resource (scheduled insight/dashboard delivery): SDK
factory, Zod-validated client, title-marker identity, cross-resource refs,
pull codegen, unit tests, two examples.

Identity: the endpoints/surveys marker pattern, carried in `title`. A
subscription references exactly one insight or dashboard by key (resolved to
an id at execute time via ApplyContext; dependsOn insights + dashboards);
validation enforces exactly-one-ref and same-run declaration. Email/Slack
targets, recurrence, and AI-summary fields round-trip as plain fields. Slack
`integrationId` round-trips and is hashed, but is environment-specific
(documented as non-portable).

Delivery safety (per the coordinator's warning): `send_test_now` defaults to
TRUE on the API and fires an immediate delivery on save. The client forces it
false on every create AND update (and the pull tag-back), so apply never
delivers as a side effect. Live-verified the subscription's deliveries list
stayed empty (count 0) across create and update. Note: applying an *email*
subscription still invites its recipients (inherent product behavior) — use
real addresses only when intended; verification used example.com throwaways.

Surprise / constraint: the server caps `title` at 100 chars, and `title` also
carries the identity+hash marker (~53 + key length). Effective budget is
title + key <= ~45. Other free-text fields aren't viable carriers (`prompt` is
mutually exclusive with insight/dashboard; `invite_message` is email-only and
user-facing). Validation now fails fast with an actionable message when
title + marker would exceed 100 (no silent loss; the API 400 is pre-empted).
Because a STAMP-length smoke key can't round-trip through pull within that
budget, subscriptions are excluded from the smoke seed (like singletons) and
verified in isolation instead.

Live-verified on project 806: create -> no-op re-apply (hash projection
correct) -> edit -> single update -> cross-resource ref (insight resolved to
id) -> orphan left alone -> hand-built subscription (no marker) untouched ->
kind-scoped prune deletes managed only. Delete is soft-delete via PATCH
{deleted:true} (DELETE -> 405).

Scope: subscription:read / subscription:write (+ insight/dashboard scopes).

Stacks on #81 (pl/spec-migration); shares the ApplyContext dashboardIdByKey
and lenient-pull commits with #94 (dedupe if both merge). Retarget to main when
#81 merges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pl added a commit that referenced this pull request Jul 23, 2026
Records the subscription title-length constraint (100-char cap shared with
the marker) and the carrier-investigation lesson it taught.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant