Support APPMAP_CUSTOMER_ID (managed entitlement) - #2391
Open
dividedmind wants to merge 2 commits into
Open
Conversation
Managed deployments identify a customer through a B2B agreement rather than an authenticated session. The IDE extension passes that identifier down to subprocesses as APPMAP_CUSTOMER_ID; report it on every event so usage can be attributed without a signed-in user. The property sits with the other common.* defaults in buildDefaultConfiguration, so it applies to both backends and to every event, and base.properties / APPMAP_TELEMETRY_PROPERTIES can still override it. Blank and whitespace-only values read as unset rather than being reported as an empty string. The identifier is not a credential and grants nothing on its own. Assisted-by: Claude:claude-opus-5[1m]
In a managed deployment, entitlement is settled by a B2B agreement and the subprocess receives APPMAP_CUSTOMER_ID instead of an API key. Report that state explicitly rather than warning about a missing license key, which is cosmetic noise for a user who was never expected to sign in. Entitlement is not a credential and never substitutes for one. When an API key is present it is still checked, so the real key wins for authentication and the check stays visible to the server, which usage tracking depends on. Entitlement only decides whether a bad outcome is fatal: a key that is missing, rejected or unverifiable is still reported, but does not fail a `required` check for an installation a contract already entitles. Blank values read as unset. Entitlement is reported whenever a customer ID is set, whatever the key turns out to be, since an entitled installation that says nothing about it cannot be told apart from one where the ID was never configured. The message carries the telemetry machine id, as the valid-key message has since 1acab71: the rationale given there - correlating telemetry with user logs for troubleshooting - applies at least as strongly here. Restructuring for that also fixes three warts. The invalid-key throw used to sit inside the try block that catches check failures, so a required check against an invalid key reported "Failed to check license key" over its own error before rethrowing it. A failed check warn()ed the error object itself, printing a backtrace nobody needs; it now reports the underlying message on the one line. And a fatal problem was both warned about and thrown, reporting it twice once the caller surfaced the error; licenseProblem now does one or the other. checkLicense switches from the imported `warn` to the global console.warn behind an eslint-disable. The import resolves against Node's real console, which is not the one jest installs, so the messages escape test buffering; writing to the global lets the new tests spy on it and lets TEST_SILENT=false attribute each message to its source line. Assisted-by: Claude:claude-opus-5[1m]
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for managed-entitlement via APPMAP_CUSTOMER_ID across the telemetry and CLI layers, so enterprise installations can be attributed (telemetry) and treated as entitled (CLI checkLicense) without requiring authentication against getappmap.com.
Changes:
- Add
common.customeridto telemetry default properties whenAPPMAP_CUSTOMER_IDis set to a non-blank value. - Update CLI license checking to report entitlement when a customer ID is present and to make missing/rejected/unverifiable keys non-fatal for entitled installs.
- Add unit tests covering customer ID telemetry behavior and the new
checkLicenseentitlement matrix.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/telemetry/tests/client.spec.ts | Adds coverage to ensure common.customerid is omitted/trimmed/treated as unset appropriately. |
| packages/telemetry/src/client.ts | Reads and trims APPMAP_CUSTOMER_ID, conditionally emitting common.customerid in default telemetry properties. |
| packages/telemetry/README.md | Documents APPMAP_CUSTOMER_ID behavior and clarifies it is attribution-only (not a credential). |
| packages/cli/tests/unit/lib/checkLicense.spec.ts | New tests covering entitled/unentitled states across missing/valid/invalid/unreachable key checks and required vs optional behavior. |
| packages/cli/src/lib/customerId.ts | Introduces a small helper to read/trim APPMAP_CUSTOMER_ID and treat blank as unset. |
| packages/cli/src/lib/checkLicense.ts | Implements entitlement reporting and adjusts failure handling to be non-fatal when entitled, while still checking API keys when present. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
In enterprise deployments where licensing is already settled by a B2B agreement, requiring every user to authenticate against getappmap.com is pure friction: it fails outright under network restrictions and creates audit review burden for a flow that grants nothing the contract hasn't already granted. Administrators can now set a customer ID through the IDE's organization-configuration channels, which passes it down to CLI subprocesses as
APPMAP_CUSTOMER_ID.This is the appmap-js half of that feature, and it is deliberately small — until now the CLI simply ignored the variable. Two things change: telemetry reports it, and
checkLicensetreats it as entitlement.It is not a cryptographic license key and must never be described as an enforcement mechanism. The code is open source, so any client-side check is a business-process boundary rather than a security boundary. The identifier is not a secret and grants nothing on its own.
Companion PRs, which share the naming and the semantics:
appMap.customerIdappmap-intellij-plugin#959common.customeridtelemetrybuildDefaultConfigurationreadsAPPMAP_CUSTOMER_IDand addscommon.customeridalongside the othercommon.*defaults, so it lands on every event, on both backends, from every entry point. Blank and whitespace-only values read as unset rather than being reported as an empty string, andbase.properties/APPMAP_TELEMETRY_PROPERTIEScan still override it.Note this lives in
@appland/telemetry, so it applies to every consumer of the package rather than only to the CLI. The CLI picks it up for free —packages/cli/src/cli.ts:51configures the shared default client.The property name is deliberately all lower-case with no separator, matching the existing
common.extname/common.ideversionstyle. The other two implementations depend on that spelling.Entitlement in
checkLicenseA customer ID entitles the installation on its own, so the licensing check reports that state instead of warning about a key the user was never expected to have.
Entitlement is not a credential and never substitutes for one. When an API key is present it is still checked — the real key wins for authentication, and the check stays visible to the server, which usage tracking depends on. Entitlement only decides whether a bad outcome is fatal: a key that is missing, rejected or unverifiable is still reported, but does not fail a
requiredcheck for an installation a contract already entitles.Entitlement is reported unconditionally whenever a customer ID is set, before anything is said about the key. An entitled installation that stayed silent about it would be indistinguishable from one where the customer ID was never configured — exactly the thing an administrator is trying to confirm.
What the command prints:
Entitled to AppMap as customer acme-corp at machine id <id>.Valid license for alice at machine id <id>.The machine id is on the entitlement message for the same reason 1acab71 put it on the valid-key message — correlating telemetry with user logs for troubleshooting. That applies at least as strongly here, since attributing usage to a customer is the whole point of this deployment mode.
Incidental fixes
Found while restructuring
checkLicense; none are related to customer IDs.throwsat inside thetrythat catches check failures, so a required check against an invalid key reportedFailed to check license keyover its own error before rethrowing it.warn()ed the caught error object, printing a backtrace nobody needs. It now reports the underlying message on the one line.licenseProblemnow does one or the other.Notes for review
checkLicenseswitches from the importedwarnto the globalconsole.warnbehind a file-leveleslint-disable no-console. This is the one stylistic oddity in the diff and it is deliberate: most of this package importswarnfrom'console', which sidesteps the lint rule because the rule only matches the global — but that import also resolves against Node's real console rather than the one jest installs, so the messages escape test buffering and land on raw stderr, uncaptured. Writing to the global lets the tests spy on it with a plainjest.spyOn(console, 'warn')and letsTEST_SILENT=falseattribute each message to its source line. A shared output helper owning that suppression would be better than either, but that is a package-wide change (~53 files) and not one for this PR to make.One contract change: the error thrown for a missing key is now the warning text rather than
'License key is required', so what you see is what gets thrown. All three call sites userequired = false, so nothing observable changes today.Testing
packages/cli/tests/unit/lib/checkLicense.spec.tsis new — 13 cases covering entitled and unentitled across no key, valid key, rejected key and unreachable check, each split into the warns-when-not-required and throws-without-warning-when-required outcomes, plus blank-value handling and the both-present case that pins "the key is still checked".Three cases added to
packages/telemetry/tests/client.spec.tsfor present, absent and blank customer IDs.yarn verifyis clean on both packages.