diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fd1dc..d339f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,5 @@ - Goal-first BTrace observability skill suite with cloud/SRE workflows. - Provider-neutral eval corpus and repository validation gate. - `btrace-legacy-libs-migration` skill for moving retired `libs`/profile packaging into an extension. +- Moved `btrace-extensions-and-permissions` into `btrace-development`; extension authoring is build-time work, not incident diagnosis. +- `btrace-extension-authoring` skill for designing a new extension against a target library. diff --git a/evals/btrace-observability.json b/evals/btrace-observability.json index e06fb9c..87e8e44 100644 --- a/evals/btrace-observability.json +++ b/evals/btrace-observability.json @@ -167,5 +167,23 @@ "grantAll=true" ], "rubric": "Recognise that libs= no longer loads anything and the missing classes are the symptom. Direct to one extension project with a single source set and declared services, not separate API and impl modules. Keep permission grants in permissions.properties rather than extensions.conf. Include verification by attaching and taking a trace." + }, + { + "id": "extension-authoring-target-library", + "prompt": "I want to trace a third-party job scheduler library from BTrace. Its listener class is only on the application classpath and some methods only exist in newer versions. How should I structure the extension?", + "requiredSkills": [ + "btrace-extension-authoring" + ], + "mustMention": [ + "Object", + "MethodHandleCache", + "Impl", + "version" + ], + "mustAvoid": [ + "publicLookup", + "grantAll=true" + ], + "rubric": "Analyse before scaffolding. Keep target types out of the service signature and hand the object across as Object. Use @ExternalType only where the signature is nameable at compile time, and MethodHandleCache for version-variant members because its failures are not cached. Name the implementation Impl or ship a provider declaration." } ] diff --git a/plugins/btrace-development/skills/btrace-extension-authoring/SKILL.md b/plugins/btrace-development/skills/btrace-extension-authoring/SKILL.md new file mode 100644 index 0000000..4cb676a --- /dev/null +++ b/plugins/btrace-development/skills/btrace-extension-authoring/SKILL.md @@ -0,0 +1,55 @@ +--- +name: btrace-extension-authoring +description: Use when designing a new BTrace extension for a target library, framework, or runtime, deciding how a probe should reach target types safely, or choosing between @ExternalType and hand-written method handles. +--- + +# Extension Authoring + +Start in analysis only. Establish which target entry points are public and stable, which versions +must be supported, and which types a probe actually needs, then say what the extension would look +like before creating any files. Report use of internal or version-sensitive target APIs as a risk +at this point rather than after scaffolding. + +Derive the service surface from the probe-facing use cases, because everything downstream follows +from it. Service methods take and return only primitives, `java.*` types, and the extension's own +types; a target object crosses the boundary as `Object` and is resolved inside the implementation. +Keeping target types out of the service signature is what lets the extension load when the target +library is absent or a different version than expected. + +Model an individual target type with `@ExternalType`, whose processor emits a companion adapter +with cached method handles. It fits a narrow shape: the whole signature must be nameable at +compile time, so any method that takes or returns a target-library type is out of reach; overloaded +names are rejected outright; fields, constructors, `instanceof`, and non-public members are not +supported; and members of packages a named module does not export are inaccessible. Check a target +method against that list before assuming the annotation can express it. + +Note also that static dispatch resolves the owning class through the thread context class loader +while virtual dispatch uses the receiver's defining loader. Prefer virtual dispatch on a handed-off +receiver where a choice exists, because a context loader is whatever the application thread happens +to have set, and is frequently wrong under application servers, OSGi, and plugin loaders. Class +resolution is not cached, so a missing target class throws on every call rather than once, and it +surfaces at the probe call site rather than at load. + +Use `ClassLoadingUtil` and `MethodHandleCache` for everything the annotation cannot express, and +for anything that varies across target versions. That cache stores successful lookups and never +stores failures, so catching a lookup failure is a genuine capability check that degrades to a +reduced feature set and recovers if the class appears later. `@ExternalType` offers no such catch +point, so version-variant members belong on the hand-written path. + +Nothing in the metadata describes which target versions an extension supports; that is author +discipline. Because binding is per method and per class, a member missing from one version fails +only where it is used. Hand-written stubs of the target API under the test source set give unit +tests realistic fixtures and let a version matrix be exercised without the real dependency. + +Name the implementation exactly `Impl`, or declare it in +`META-INF/services/`. Separating `api` and `impl` packages is reasonable and +requires the provider declaration, because the fallback is an exact name and nothing else. Extend +`Extension` when the service needs the extension context, initialization, or cleanup on detach; a +stateless service is better without it, since instantiation fails when no context is available. + +Verify by inspecting the packaged artifacts, installing, attaching, and taking one trace that +exercises the injected service against the real target library rather than a stub. + +Packaging mechanics, permission scanning, and the shared project layout are covered by +`btrace-extensions-and-permissions`. Fat agents for deployment are covered by the +`btrace-observability` plugin's startup and packaging guidance. diff --git a/plugins/btrace-observability/skills/btrace-extensions-and-permissions/SKILL.md b/plugins/btrace-development/skills/btrace-extensions-and-permissions/SKILL.md similarity index 100% rename from plugins/btrace-observability/skills/btrace-extensions-and-permissions/SKILL.md rename to plugins/btrace-development/skills/btrace-extensions-and-permissions/SKILL.md diff --git a/plugins/btrace-observability/README.md b/plugins/btrace-observability/README.md index e809901..2f41f5b 100644 --- a/plugins/btrace-observability/README.md +++ b/plugins/btrace-observability/README.md @@ -35,7 +35,6 @@ All of these are bundled in this one plugin and may be combined for an incident: | `btrace-runtime-access` | SSH, Docker, Kubernetes, sidecars, and cloud runtime boundaries. | | `btrace-probe-lifecycle` | Attach, output capture, verification, reconnect, and removal. | | `btrace-data-safety` | Sensitive output, production load, permissions, and scope escalation. | -| `btrace-extensions-and-permissions` | Metrics exporters, extensions, and minimal permission grants. | | `btrace-mcp-operations` | Local BTrace MCP server workflows. | | `btrace-startup-and-packaging` | Launch-time agents, fat agents, immutable images, and distroless workloads. | | `btrace-legacy-libs-migration` | Retired `libs`/profile packaging, and moving it into an extension. | diff --git a/plugins/btrace-observability/skills/btrace-legacy-libs-migration/SKILL.md b/plugins/btrace-observability/skills/btrace-legacy-libs-migration/SKILL.md index 41ef08e..b83143e 100644 --- a/plugins/btrace-observability/skills/btrace-legacy-libs-migration/SKILL.md +++ b/plugins/btrace-observability/skills/btrace-legacy-libs-migration/SKILL.md @@ -49,5 +49,5 @@ it into a no-op returning defaults and makes an unfinished migration look comple fails, `btrace -le ` reports why. For deployments that cannot manage separate extension artifacts, load `btrace-startup-and-packaging` -for fat agents. For choosing permissions and deciding whether an extension is warranted at all, -load `btrace-extensions-and-permissions`. +for fat agents. Deciding whether an extension is warranted at all, and choosing its permissions, is +covered by the extension authoring guidance in the `btrace-development` plugin. diff --git a/plugins/btrace-observability/skills/btrace-observability/SKILL.md b/plugins/btrace-observability/skills/btrace-observability/SKILL.md index cd3642b..f3f2b50 100644 --- a/plugins/btrace-observability/skills/btrace-observability/SKILL.md +++ b/plugins/btrace-observability/skills/btrace-observability/SKILL.md @@ -20,7 +20,6 @@ and combine the specialist skills below as needed. | Kubernetes, Docker, SSH, bastions, or cloud runtime access | `btrace-runtime-access` | | Deploying, verifying, reconnecting, or stopping a probe | `btrace-probe-lifecycle` | | Sensitive data, production load, permissions, or risk | `btrace-data-safety` | -| Extensions, metrics exporters, or permission grants | `btrace-extensions-and-permissions` | | AI/MCP-guided local diagnostics | `btrace-mcp-operations` | | Immutable images, no attach, or launch-time deployment | `btrace-startup-and-packaging` | | Legacy `libs`/profile packaging that must become an extension | `btrace-legacy-libs-migration` |