Skip to content

feat(annotation): SDK factory + pipeline - #94

Open
pl wants to merge 5 commits into
pl/spec-migrationfrom
pl/resource-annotations
Open

feat(annotation): SDK factory + pipeline#94
pl wants to merge 5 commits into
pl/spec-migrationfrom
pl/resource-annotations

Conversation

@pl

@pl pl commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Adds annotations as a managed resource (first of the two Wave 2 greenlit resources).

Identity

Content-marker pattern (iac:annotations:<key> in content, the only free-text field) — as designed in the identity investigation.

Marker visibility (the UI check you asked for)

Checked the frontend render paths:

  • Chart tooltip / overlay (the primary surface, where annotations appear on insights) renders content through LemonMarkdown, which strips the trailing HTML comment → the marker is invisible there.
  • The annotations management list scene renders content as plain React JSX → the marker shows as literal trailing text there (a secondary admin view).
  • hidden: true (recommended for deploy markers) removes the annotation from both surfaces entirely.

So the cosmetic cost is limited to the annotations list scene, and avoidable with hidden. Annotations are operational metadata (lower bar than alert names) — noted as a known cost per your call.

Scope + refs

scope project / organization / dashboard_item / dashboard is declarative; date_marker, emoji, creation_type (USR/GIT), hidden round-trip as plain fields. Scoped annotations reference an insight (dashboard_item) or dashboard (dashboard) by key, resolved to ids at execute time; validation enforces exactly-one-ref-per-scope and same-run declaration.

Supporting infra

  • feat(apply): expose dashboard ids by key in ApplyContext — adds dashboardIdByKey / dashboardKeyByServerId (populated by the dashboard executor + plan pass), mirroring insightIdByKey. Needed for scope: dashboard refs; subscriptions (next PR) needs the same field — heads up for a trivial dedupe if both merge.
  • fix(pull): tolerate dependency kinds absent from a --kind subsetpull --kind annotations (deps on insights+dashboards) threw in topoOrder even though pull resolves refs at render time. Added a lenient mode to topoOrder (drops out-of-set edges) that pull uses; apply stays strict. Exposed by annotations (first pull-capable resource with deps a user would pull alone).

Live verification (project 806)

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

Surprise

Pull derives an annotation's key by slugifying content (annotations have no name), so a pulled annotation's key follows its content. The smoke fixture sets content == key so the key round-trips for the trap cleanup.

Scope: annotation:read / annotation:write (+ insight:* / dashboard:* for scoped annotations). Gates green (typecheck / typecheck:examples / test 307 / lint). Smoke wiring verified in isolation.


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

🤖 Generated with Claude Code

pl and others added 5 commits July 24, 2026 00:32
Add annotations list/create/retrieve/partial_update to the openapi allowlist
and regenerate src/generated/api.d.ts. No destroy — annotations soft-delete
via PATCH { deleted: true } (the DELETE verb is 405).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the annotations resource: SDK factory, Zod-validated client,
content-marker identity, cross-resource refs, pull codegen, unit tests, two
examples.

Identity: the endpoints/surveys marker pattern, carried in `content`
(iac:annotations:<key>) — the only free-text field. `scope`
project/organization/dashboard_item/dashboard is declarative; `date_marker`,
`emoji`, `creation_type` (USR/GIT), and `hidden` (hidden_in_user_interface)
round-trip as plain fields. Scoped annotations reference an insight
(dashboard_item) or dashboard (dashboard) by key, resolved to ids at execute
time via ApplyContext (dependsOn insights + dashboards); validation enforces
exactly-one-ref-per-scope and same-run declaration.

Marker visibility (the coordinator's UI check): the chart tooltip/overlay
renders content through LemonMarkdown, which strips the trailing HTML comment
— so the marker is invisible on charts, the primary surface. The annotations
management *list* scene renders content as plain JSX, so the marker shows as
literal trailing text there. `hidden: true` (recommended for deploy markers)
removes the annotation from both surfaces entirely. Known cosmetic cost on the
list scene only; annotations are operational metadata (lower bar than alert
names) — greenlit.

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

Scope: annotation:read / annotation:write (+ insight/dashboard scopes for
scoped annotations).

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

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 annotations` (which
depends on insights + dashboards) failed unless you also named those kinds,
even though pull resolves cross-resource references at render time via the
optional-import path.

Add a `lenient` option to topoOrder that drops out-of-set edges instead of
throwing (and gates readiness only on in-set deps), and use it from pull. Apply
still uses strict mode. Exposed by the annotations resource — the first
pull-capable resource with dependencies that a user would plausibly pull alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a project-scoped hidden deploy-marker annotation to the smoke seed, plus
the SMOKE_KINDS entry and smoke-cleanup wiring. Content == key so pull (which
derives an annotation's key by slugifying its content — annotations have no
`name`) round-trips the key and the trap cleanup finds the row.

Verified in isolation: seed apply -> pull -> tag-back -> apply --dry-run no-op
-> cleanup by key. As with the other new resources, the full smoke.sh run stays
red at the pull step on the pre-existing `actions` pull gap on pl/spec-migration
(lands on pl/pull-actions).

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