update windows signing - #14
Merged
Merged
Conversation
…abilities Two new extension capabilities, both generic and hook-shaped, following the ConfigOverlayProvider pattern: declare in pkg/extension, discover with Capability[T], adapt with panic containment in internal/extensions. IdentityProvider lets an extension answer who the current user is. The adapter bounds the call at two seconds because it sits on the synchronous knowledge-base write path, and the result is read per use, never cached. It fills the Attribution.UserID field that already existed and was never set, and adds user_id to the session index metadata. Email, groups and device id deliberately do not widen Attribution: attribution is stamped on every event that leaves the machine, so carrying them there would export personal data on every write with no separate consent. ProviderRequestDecorator lets an extension add per-request headers to an LLM provider call. Static per-account extraHeaders already covered the fixed case, so this hook exists only for values that change per call, such as a session id. It is installed as client middleware in the OpenAI and Anthropic constructors, and the adapter refuses to overwrite Authorization, x-api-key and the anthropic-* headers rather than trusting the extension. Also closes two gaps in the config work: every configuration mutator is now covered by the locked-key guard, with an AST-based test over all 53 of them so a new mutator that bypasses updateCfgFile fails the suite, the remaining API write paths return 409 config_key_locked, the settings UI marks locked fields as managed instead of only refusing the write, and overlay_applied now also fires when only the lock list moves. Verified: go build ./..., go vet ./... and go test on config, extension, extensions, llm, api, tui and app, with -race on the five core packages. The four internal/llm/agent failures are pre-existing on HEAD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
Third UI-facing hook after the config overlay and the locked keys. UIPolicyProvider lets an extension declare which settings sections are hidden, which are read-only, the label to show on them and an operator banner. Providers are merged by unioning the two sets, with the label and the banner taken from the first provider in load order that sets one; hidden beats read-only. Consumed on all three surfaces: the TUI settings page next to the locked-key marker that landed with CORE-2, the web settings store through a new GET /api/v1/config/ui-policy mirroring the locked-keys endpoint, and desktop preferences, which are the same web bundle under Wails. Hiding is never the only defence. A new nil-by-default SetRestrictedPathSource hook joins hidden and read-only paths to the locked-key check in the config write path, so a hidden section is refused on write even if a client asks anyway. The adapter memoises the merged policy for one second so the per-changed-path check does not fan out to extensions on every write. Verified: gofmt on every changed file, go build ./..., go vet ./... and go test on extension, extensions, api, tui, config and app, with -race on the extension packages, plus the web typecheck and lint. The four internal/llm/agent failures are pre-existing on HEAD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
Two hooks, both generic. Extensions can now subscribe to tool, mcp, skill and provider events alongside the config topic that already existed, through the EventSubscriber interface they already implement. Publish points sit where the event actually happens rather than in one convenient place, and publishing never blocks or fails the operation that triggered it, following the config bus precedent of dropping on a full channel. The payloads carry names, durations and outcomes only: no arguments, no results, no instructions and no credentials ever leave through them. HostServices.Prompts lets an extension run a one-shot prompt, which is the contract the core already offers people through pando -p, expressed in strings and numbers. Any automation extension needs those three lines, and this way the core exposes no sessions, no models and no tool loop to get them. A second candidate hook is refused in ADR 005: a serve flag carrying a job file would be a pass-through whose format exactly one extension can parse, and the core already decided the opposite way by mounting extension commands under a namespace so nothing can shadow a core command. The cost of saying no is one longer command line. Verified: gofmt on every changed file, go build ./..., go vet ./... and go test ./..., which still shows only the four internal/llm/agent failures that were already present on HEAD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
…on system does Every camelCase option an extension asked for was unreachable. Viper folds keys on the way in and the host services looked them up exactly, so an extension asking for baseURL could never find the value a user wrote as baseURL. The doc comment already promised case-insensitive keys throughout; the code did not deliver it. The effect was total rather than cosmetic: the first enterprise extension refused to provision, and because six others declare it as a dependency, none of them loaded at all. Managed mode, private mode and memory sync could not be exercised end to end because of this. Every accessor now resolves through one lookup that folds the requested key the same way the stored one was folded, and the entry's own config table is folded at its top level so reading the raw map agrees too. Nested tables are left alone: those are the extension's data, not its option names. Two spellings of one option no longer shadow each other in silence; the first wins deterministically and the extension's logger says which was kept and which was ignored. The accessor surface was only Bool, String and Int, which is why an out-of-tree extension had hand-rolled the rest over the raw map. Duration, StringSlice, Float64 and Map are now provided and coerce across TOML integers, JSON floats and environment strings. An extension option can now also come from the environment, which a container deployment needs and did not have. One honest gap remains: whether an extension is enabled is a field of the entry rather than an option, and it still needs a file or an overlay. Verified: gofmt, go build ./..., go vet ./..., the targeted tests with -race, and a full test run showing only the four internal/llm/agent failures that were already on HEAD. The regression test fails on the old code. Proved on the real case too: a composed enterprise binary built against this core loads the extension that used to refuse, and its six dependants with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
The token endpoint was exempt from the token check, so anything that could reach a running server could ask it for its own token. That was already wrong; it became reachable in practice now that a control plane drives remote instances over this surface. The exemption survives only where it is defensible: a loopback bind, or a request that is already authenticated by basic auth. An exposed server configured with no basic auth user is refused with a message naming what is missing, rather than quietly handing the token over, and a caller that already holds one can still refresh it. Verified: go build ./..., go vet ./... and the api package tests, with no new failure against the four that were already failing on HEAD. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
The read-only configuration view an extension holds resolves a dotted path against the JSON encoding of the configuration struct, which is not the spelling anyone actually writes. A configuration file, a pushed overlay and a lock list all name a setting the way the documentation does, so a path that named a real setting could come back absent. That was not a cosmetic mismatch. An enterprise extension's drift detection asked for a value, was told it did not exist, and reported a converged installation as diverged; the divergence asked for a reload, the reload republished the applied event, the subscriber checked drift again, and the loop ran at the reload debounce. Measured downstream: two hundred and twenty three identical events in sixty seconds, and every device permanently reporting drift while it was exactly converged. A segment now falls back to a case-insensitive match, with an exact match still preferred, so a configuration that genuinely carries two keys differing only in case is unaffected. This is the same class of defect as the extension option lookup fixed earlier today, in the second of the two places that spell a configuration key. Verified: go build ./..., go vet ./... and the extension packages, with a test that fails on the previous code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqTkMtjq3BciZXamva2FgQ
The Windows CLI shipped unsigned. Signing needs signtool and the Trusted Signing dlib, neither of which has a Linux build, so the exe cross-compiled on the Linux runner now travels through an artifact to a Windows job that signs it and repacks the zip. Authentication is OIDC: the windows-sign job runs in the release environment and mints a token that a federated credential on the pando-github-trusted-signing Entra app accepts, so no signing secret is stored in the repository. The endpoint, account, certificate profile and identity ids come from repository variables. The unsigned zip is uploaded as unsigned-windows, deliberately outside the dist-* pattern the release job globs, so a release cannot ship it by accident. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXcWfFXuK9aWAg8uvMfBgj
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.
No description provided.