Skip to content

Commit 22679ef

Browse files
Amit Joshiclaude
andcommitted
docs(telemetry): add plugin-owner adoption guide for 1DS telemetry
Walks other plugin owners through the shared telemetry library: five baked-in decisions and their tradeoffs, what is shared vs what each plugin would configure, a step-by-step adoption checklist, local + Kusto verification, and likely concerns. Frames the current cluster as a testing-only setup, with per-plugin clusters as the planned default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a00633 commit 22679ef

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# 1DS Telemetry — A Walkthrough for Plugin Owners
2+
3+
**Date:** 2026-04-29 · **Author:** Amit Joshi
4+
**For:** Folks owning `canvas-apps`, `code-apps`, `mcp-apps`, `model-apps`
5+
**Status:** Work in progress. The cluster currently wired up is a **testing-only** iKey we used to validate the pipeline end-to-end. The plan is for **each plugin to configure its own cluster** before any real adoption.
6+
**Hope:** Share what we built for `power-pages`, hear what you'd want different, and make adoption easy if you're up for it.
7+
**Engineering critique companion:** `2026-04-27-1ds-telemetry-team-presentation.md`
8+
9+
---
10+
11+
## Agenda
12+
13+
| § | Topic | Time |
14+
|---|---|---|
15+
| 1 | What we built | 2 min |
16+
| 2 | A few decisions worth flagging | 5 min |
17+
| 3 | What's shared vs what would live in your plugin | 2 min |
18+
| 4 | If you'd like to adopt — a suggested path | 4 min |
19+
| 5 | How we've been verifying things land | 1 min |
20+
| 6 | Open questions and likely concerns | 1 min |
21+
22+
---
23+
24+
## 1. What we built
25+
26+
- A **shared, Node-only** telemetry library at `shared/telemetry/`.
27+
- **Zero npm dependencies** — built on `https`, `child_process`, `fs`, `crypto`. We wanted to avoid adding an install step to any plugin.
28+
- Four lifecycle events to **Microsoft 1DS / OneCollector**:
29+
- `skill_started` / `skill_completed`
30+
- `script_started` / `script_completed`
31+
- Today, events land in Kusto table **`PowerPlatformExtensionEvent`** via a **testing-only iKey** (routing tuple: `iKey` + `envelope.name="VscodeEvent"`). Per-plugin clusters are the next step — the test cluster was just to prove the pipeline works.
32+
- **Default-on with opt-out.** No first-run prompt — happy to revisit if any of you feel differently.
33+
- **Fail-closed.** A detached child owns the POST so the parent never blocks.
34+
- First adopter is **`power-pages`**, and Kusto landing is verified end-to-end.
35+
36+
> The hope is that you mostly **inherit configuration knobs**, not code.
37+
38+
---
39+
40+
## 2. A few decisions worth flagging
41+
42+
These are choices we landed on for `power-pages`. Each comes with a tradeoff. If any of them feel wrong for your plugin, that's exactly the kind of feedback we'd love before you adopt.
43+
44+
### 2.1 Raw `node:https` instead of the `@microsoft/1ds-*` SDK
45+
46+
- **Reasoning.** We wanted to keep marketplace plugins free of `npm install`. A POC showed identical Kusto landing either way.
47+
- **Tradeoff.** When 1DS evolves the wire format, we'll own the migration ourselves. The surface is small — one envelope builder.
48+
49+
### 2.2 Detached child dispatcher instead of an inline POST
50+
51+
- **Reasoning.** Hooks have a 30 s timeout and sit on the user's critical path. Detached spawn returns in **~50 ms** regardless of network conditions, which felt important for UX.
52+
- **Tradeoff.** If a process supervisor or antivirus kills the child early, the event is silently dropped. We chose simplicity over a retry queue, but we're open to revisiting if your environment makes drops more common.
53+
54+
### 2.3 Strict allowlist instead of a runtime PII scrubber
55+
56+
- **Reasoning.** Allowlists fail safe — a forgotten field doesn't ship. Scrubber regexes can fail open. The allowlist sits in one place (`lib/events.js`) and CI asserts the keyset.
57+
- **Tradeoff.** Adding a new field means editing the builder *and* updating the privacy reference doc. We've found this friction useful, but we'd love to know if it gets in your way.
58+
59+
### 2.4 Default-on with opt-out, not an interactive prompt
60+
61+
- **Reasoning.** An earlier iteration prompted on first run. It cost the very first invocation per machine and added friction users didn't ask for. Given the allowlist already prevents PII, we felt default-on was defensible.
62+
- **What this means for you.** You wouldn't need to add a Phase-1 consent block to your skills. If your plugin's audience expects an opt-in posture, please flag it — we can talk through it.
63+
64+
### 2.5 Sync script instead of git submodule or npm package
65+
66+
- **Reasoning.** Submodules can break for users who clone non-recursively, and a private npm package would force an install step. The sync script is ~30 lines and produces a self-contained plugin.
67+
- **Tradeoff.** Drift if someone hand-edits the synced copy. Convention so far: edit `shared/`, re-run sync, never touch the synced files directly.
68+
69+
---
70+
71+
## 3. What's shared vs what would live in your plugin
72+
73+
### 3.1 Shared (we'd ask you not to edit these in your plugin)
74+
75+
| Thing | Where |
76+
|---|---|
77+
| Library (13 files) | `shared/telemetry/lib/` |
78+
| Privacy / opt-out doc | `shared/telemetry/references/telemetry-consent-reference.md` |
79+
| Sync tool | `shared/telemetry/sync-to-plugin.js` |
80+
| Opt-out env var name | `POWER_PLATFORM_SKILLS_TELEMETRY` |
81+
| Consent file path | `~/.power-platform-skills/telemetry.json` |
82+
83+
### 3.2 Yours to configure
84+
85+
| Thing | What you'd provide |
86+
|---|---|
87+
| iKey + collector URL | Your own — provisioned per plugin, dropped into the synced `scripts/lib/telemetry/ikey.json` |
88+
| Plugin name | A string literal in your hooks + telemetry-runner |
89+
| Plugin version | Already in `.claude-plugin/plugin.json` — hooks just read it |
90+
| Tracked skills | A `{ skill-name: { validatorScript } }` map |
91+
| Hook entry points | Three thin wrappers (~80 lines each) |
92+
| `withTelemetry` adoption | Wrap whichever Node scripts you want instrumented |
93+
94+
> **iKey ownership.** The current `shared/telemetry/ikey.json` carries our **testing iKey** — handy for proving the pipeline lands data in Kusto, but not what anyone should ship with. Before you adopt for real, we'd suggest provisioning your own iKey + Kusto stream so your data and dashboards stay yours. We can help walk through the tenant-side setup if it's new ground.
95+
96+
---
97+
98+
## 4. If you'd like to adopt — a suggested path
99+
100+
We've ballparked this at ~30 minutes for a plugin that already has hooks. Happy to pair on the first one with whoever wants to try.
101+
102+
### Step 1 — Sync the library
103+
104+
```bash
105+
node shared/telemetry/sync-to-plugin.js --target plugins/<your-plugin>
106+
```
107+
108+
You'll get:
109+
110+
```
111+
plugins/<your-plugin>/
112+
├── scripts/lib/telemetry/
113+
│ ├── ikey.json
114+
│ └── lib/ # 13 files
115+
└── references/telemetry-consent-reference.md
116+
```
117+
118+
Worth noting in your CLAUDE.md / AGENTS.md: the synced copy is generated — edits should go in `shared/`, then a re-sync.
119+
120+
> **Heads-up on `ikey.json`.** The synced file initially carries our testing iKey. Before you ship anything, replace it with the iKey + collector URL for the cluster your team owns. (We'll likely add a `--ikey` flag to the sync script to make this less manual — happy to take input on the shape.)
121+
122+
### Step 2 — Define your tracked skills
123+
124+
Somewhere like `scripts/lib/<your-plugin>-hook-utils.js`:
125+
126+
```js
127+
const TRACKED_SKILLS = {
128+
"create-something": { validatorScript: "scripts/validators/create-something.js" },
129+
"deploy-something": { validatorScript: "scripts/validators/deploy-something.js" },
130+
};
131+
132+
function getTrackedSkillFromToolInput(toolInput) {
133+
// see plugins/power-pages/scripts/lib/powerpages-hook-utils.js for a reference
134+
}
135+
136+
module.exports = { TRACKED_SKILLS, getTrackedSkillFromToolInput };
137+
```
138+
139+
> **A small gotcha we hit:** keys are skill names **without** the plugin prefix — `create-site`, not `power-pages:create-site`. The slash-command detector adds the prefix at match time.
140+
141+
### Step 3 — Wire the three hooks
142+
143+
Easiest start is to copy from `plugins/power-pages/hooks/` and adjust two things per file:
144+
145+
| File | Change 1 | Change 2 |
146+
|---|---|---|
147+
| `hooks.json` | (no change needed) | (no change needed) |
148+
| `run-skill-pretool-telemetry.js` | `plugin_name: "<your-plugin>"` | swap the hook-utils require |
149+
| `run-skill-posttool-validation.js` | same | same |
150+
| `run-user-prompt-telemetry.js` | `pluginName: "<your-plugin>"` | swap the hook-utils require |
151+
152+
### Step 4 — Optional: instrument scripts with `withTelemetry`
153+
154+
If there are Node scripts in your plugin you'd like signal on, copy `plugins/power-pages/scripts/lib/telemetry-runner.js` (changing the plugin name string), and then:
155+
156+
```js
157+
const { runInstrumented } = require("./lib/telemetry-runner");
158+
159+
(async () => {
160+
await runInstrumented("deploy-something", async () => {
161+
// your existing script body
162+
});
163+
})();
164+
```
165+
166+
`outcome` is derived from whether the function throws, and the original error is rethrown unchanged.
167+
168+
### Step 5 — Mention the opt-out in your README
169+
170+
Something like:
171+
172+
```
173+
Anonymous telemetry is enabled by default. See
174+
references/telemetry-consent-reference.md for details and opt-out instructions.
175+
```
176+
177+
Linking the synced doc keeps you in sync with future updates without you having to track them.
178+
179+
---
180+
181+
## 5. How we've been verifying things land
182+
183+
Two stages — local first, Kusto second.
184+
185+
### Local — placeholder iKey
186+
187+
If you sync without provisioning a real iKey, the dispatcher writes events to `~/.power-platform-skills/events.jsonl`. Run a tracked skill, then:
188+
189+
```bash
190+
tail -n 5 ~/.power-platform-skills/events.jsonl | jq .
191+
```
192+
193+
You should see your `plugin_name` and the right `skill_name`.
194+
195+
### Kusto — real iKey
196+
197+
The query below is what we ran against the testing cluster (table `PowerPlatformExtensionEvent`). Once you're on your own cluster, swap in your table name — the `EventInfo` shape stays the same.
198+
199+
```kusto
200+
PowerPlatformExtensionEvent // ← your table name once you're on your own cluster
201+
| where TimeGenerated > ago(15m)
202+
| extend info = parse_json(EventInfo)
203+
| where tostring(info.plugin_name) == "<your-plugin>"
204+
| project TimeGenerated, EventName,
205+
plugin = tostring(info.plugin_name),
206+
skill = tostring(info.skill_name),
207+
outcome = tostring(info.outcome)
208+
| order by TimeGenerated desc
209+
```
210+
211+
A couple of things to look for:
212+
213+
1. `EventName` matches one of the four event types.
214+
2. `correlation_id` matches between `_started` and `_completed`.
215+
216+
> **Something we learned the hard way:** `acc:1` from OneCollector is wire-layer ack only — it doesn't mean ingestion succeeded. The Kusto query above is the real check.
217+
218+
---
219+
220+
## 6. Open questions and likely concerns
221+
222+
We'd genuinely like input on these.
223+
224+
**Could I add a custom field?**
225+
Definitely possible — the path is `shared/telemetry/lib/events.js` (allowlist) + the privacy doc + a CI test asserting the new keyset. We've kept the surface small on purpose; if you have fields in mind, let's talk through them and add together.
226+
227+
**My plugin would prefer its own Kusto stream / dashboard owner.**
228+
That's exactly the direction we're heading. The current shared iKey is just a testing setup we used to validate the pipeline; **per-plugin clusters are the planned default**. Practically that means each plugin provisions its own iKey + tenant annotation and drops the values into the synced `ikey.json`. Happy to walk through the tenant-side bits with anyone for whom this is new.
229+
230+
**Will this affect my existing PostToolUse validator?**
231+
It shouldn't. In `power-pages`, telemetry was folded around the existing validator, and the validator's exit code is preserved. If your validator setup looks different, happy to walk through it together.
232+
233+
**What about `--plugin-dir` dev mode?**
234+
Worth flagging: hooks don't register under `--plugin-dir` (Claude Code limitation). The slash-command path covers user-typed `/plugin:skill` invocations, but auto-invoked skills in dev mode aren't captured. End-to-end verification needs a marketplace install.
235+
236+
**Why one consent file across all plugins, not per-plugin?**
237+
Our intuition was that users think of this as "Power Platform Skills telemetry" rather than per-plugin telemetry, so a single opt-out covers everything. If your audience would expect per-plugin consent, we'd love to hear that — it's not a hard call to revisit.
238+
239+
---
240+
241+
## 7. References
242+
243+
| Doc | What it's for |
244+
|---|---|
245+
| `2026-04-20-1ds-telemetry-design.md` | Full internal design spec |
246+
| `2026-04-27-1ds-telemetry-team-presentation.md` | Engineering critique companion |
247+
| `shared/telemetry/README.md` | Field reference + sync command |
248+
| `shared/telemetry/references/telemetry-consent-reference.md` | What's sent, what isn't, how to opt out |
249+
| `plugins/power-pages/hooks/` | Reference impl: all three hooks |
250+
| `plugins/power-pages/scripts/lib/telemetry-runner.js` | Reference impl: `withTelemetry` shim |
251+
252+
> If anything here contradicts the code, the code is the source of truth — please flag it and we'll update the doc.

0 commit comments

Comments
 (0)