feat(secrets): add entitled_secret_keys to AgentCredential schema - #1332
Open
ptone wants to merge 4 commits into
Open
feat(secrets): add entitled_secret_keys to AgentCredential schema#1332ptone wants to merge 4 commits into
ptone wants to merge 4 commits into
Conversation
added 4 commits
August 28, 2026 22:56
… P2 PR1) Add a nullable JSON column `entitled_secret_keys` to the AgentCredential Ent schema, recording which secret key names a session is entitled to fetch via the future POST /api/v1/agent/secrets endpoint. NULL vs empty semantics (fail-closed): - NULL (nil): no entitlement ever recorded — pre-migration credential or a start that failed between token generation and secret resolution. The endpoint must fail closed with a loud log on NULL. - [] (empty array): entitled to zero secrets — a valid state. The entitled set is session-scoped: each lifecycle event (start/restart) mints a fresh token and records a fresh entitled set on the new credential. On token refresh, the entitled keys are copied from the old credential to the new one (PR 3). JTI binding: entitlement is bound to the exact token presented via the credential's token_jti_hash. Pre-migration tokens lack ScopeAgentSecretFetch, so NULL rows are unreachable from any token that can call the endpoint. Changes: - pkg/ent/schema/agentcredential.go: new Optional JSON field - pkg/store/models.go: EntitledSecretKeys field on AgentCredential struct - pkg/store/store.go: UpdateAgentCredentialEntitledKeys on interface - pkg/store/entadapter/credential_store.go: implementation + converter - pkg/store/entadapter/credential_store_test.go: NULL/empty/set/overwrite/ErrNotFound - pkg/ent/*: codegen output (334 insertions)
Add agentID parameter to UpdateAgentCredentialEntitledKeys so the store scopes the update to the credential's owning agent. This prevents a hash-computation bug from silently writing entitlement onto a different agent's credential — the mismatch produces ErrNotFound rather than a cross-agent entitlement grant. The write path now matches the read path's discipline: scoping holds by construction rather than by the caller being careful. Adds cross-agent guard test: update with wrong agent ID must fail.
The three-state model (nil / empty / populated) requires that an empty slice serializes as [] not as absent. With omitempty, JSON marshaling drops the field entirely for both nil and empty-slice, collapsing two semantically distinct states into one on the wire.
nil and empty carry different semantics on this field: nil means entitlement was never recorded (bookkeeping bug, fail closed), empty means entitled to zero secrets (valid). omitempty erases that distinction because encoding/json omits both nil and empty slices. Nothing marshals AgentCredential to JSON today (measured 2026-08-28). The comment addresses the future reader adding the first marshal path.
This was referenced Aug 28, 2026
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.
Summary
Adds
entitled_secret_keysnullable JSON column to theAgentCredentialEnt schema, recording which secret key names a session is entitled to fetch via the futurePOST /api/v1/agent/secretsendpoint (#127 P2, PR 1 of 3).Migration: existing rows receive NULL, not []
Ent Migrate() uses
ALTER TABLE ADD COLUMN entitled_secret_keys TEXT NULL- SQL standard behavior: existing rows receive NULL as the default, not an empty array. Measured:Optional()which maps toNullable: trueinpkg/ent/migrate/schema.gocheck()function in the generated create builder does NOT require the fieldCreateAgentCredential(the existing create path) does not set the field -> NULLUpdateAgentCredentialEntitledKeyssets it -> called after secret resolutionConsequence: pre-migration credentials correctly read as NULL (unknown entitlement / bookkeeping failure), not
[](entitled to nothing). The loud-NULL error fires on these rows if they ever reach the endpoint - which they cannot, because pre-migration tokens lackScopeAgentSecretFetch.Design properties
token_jti_hash.UpdateAgentCredentialEntitledKeysrequires bothjtiHashandagentID. The store scopes the update to the credential's owning agent, preventing a hash-computation bug from silently writing entitlement onto a different agent's credential.ScopeAgentSecretFetch(PR 3), so NULL rows are unreachable from any token that can call the endpoint.Test plan
set_keys_on_existing_credentialset_empty_keys(entitled to zero secrets) - verifies empty != nilNULL_before_update(pre-migration state) - verifies nil defaultnot_found_returns_ErrNotFound_for_unknown_hashnot_found_returns_ErrNotFound_for_wrong_agent(cross-agent guard) - cross-agent write preventionoverwrite_existing_keysEntitledSecretKeysRoundTrip- full create-update-read with field integritygo build ./...cleango test ./pkg/store/entadapter/suite passesPart of #127.