feat(server): natural-key endpoints for target controls (7/7)#194
Draft
abhinav-galileo wants to merge 2 commits intoabhi/rfc-1-1-pr6-tenant-enforcementfrom
Draft
feat(server): natural-key endpoints for target controls (7/7)#194abhinav-galileo wants to merge 2 commits intoabhi/rfc-1-1-pr6-tenant-enforcementfrom
abhinav-galileo wants to merge 2 commits intoabhi/rfc-1-1-pr6-tenant-enforcementfrom
Conversation
Add two write-path endpoints so client-facing consumers can enable or
disable a control on a target using the natural identity
(target_type, external_id) without first looking up Agent Control's
internal integer target_id:
- PUT /api/v1/targets/{target_type}/{external_id}/controls/{control_id}
- DELETE /api/v1/targets/{target_type}/{external_id}/controls/{control_id}
PUT is a desired-state endpoint. It lazily creates the target row with
empty metadata if absent, upserts the attachment to the requested
enabled flag (default true), and returns 200 with the current
attachment summary. DELETE is idempotent: 204 when the target, the
attachment, or both are absent in the caller's tenant. Both endpoints
preserve cross-tenant non-disclosure — a control that exists only in
another tenant returns the same 404 shape as a control that does not
exist anywhere.
Supporting changes:
- Path-safe identifier contracts. Introduce constrained-string aliases
TargetTypeStr (^[a-z][a-z0-9_]{0,63}$) and ExternalIdStr
(^[A-Za-z0-9._-]{1,255}$) in agent_control_models.target and apply
them at the request model layer plus the new endpoints' path
parameters. Adds an additive alembic migration
(a3e7b2f1c4d5) with matching CHECK constraints at the DB layer so a
bypass of the model layer cannot store path-breaking values.
- Race-safe service helpers. get_target_by_natural_key for raw lookup,
ensure_target_by_natural_key using INSERT ... ON CONFLICT DO NOTHING
+ re-select, and upsert_target_control_attachment using
ON CONFLICT DO UPDATE SET enabled = EXCLUDED.enabled.
- Observability. INFO log on lazy target creation carrying tenant_id,
target_type, external_id, and the new target id.
Existing target_id-based routes are unchanged.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Adds the read-side complement to PR7's natural-key attach/detach:
GET /api/v1/targets/{target_type}/{external_id}/controls
Returns 200 with the list of attachments for the target in the caller's
tenant, or 200 with an empty list when the target does not exist yet.
The empty-list shape matches desired-state semantics: clients rendering
a controls panel for a log_stream treat "target not yet created" the
same as "target exists but no controls attached". A 404 would add no
useful signal because a subsequent PUT creates the target lazily
anyway.
Introduces a new response model ListTargetControlsByNaturalKeyResponse
that echoes the caller's natural key back (target_type, external_id)
instead of exposing the internal int target_id. Callers already know
the natural key they used; they do not need the surrogate.
Path-parameter charset guards from PR7 apply; invalid target_type or
external_id values are rejected before the handler runs.
Regenerated TS SDK.
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 two write-path endpoints so client-facing consumers can enable or disable a control on a target using the natural identity
(target_type, external_id)without ever holding Agent Control's internal integertarget_id:PUT /api/v1/targets/{target_type}/{external_id}/controls/{control_id}DELETE /api/v1/targets/{target_type}/{external_id}/controls/{control_id}Stacks on PR6. Part of the RFC 1.1 series.
Semantics
PUT (desired-state attach)
(target, control)attachment to the requestedenabledflag (defaultstrue)200with the current attachment summaryINFOon lazy target creationDELETE (idempotent detach)
204whether the attachment existed and was removed, never existed, or the target row itself doesn't exist in the caller's tenantCross-tenant non-disclosure
404 CONTROL_NOT_FOUNDas a control that doesn't exist anywherePath-safe identifier contracts
New constrained-string aliases in
agent_control_models.target:TargetTypeStr—^[a-z][a-z0-9_]{0,63}$(slug: lowercase, starts with letter)ExternalIdStr—^[A-Za-z0-9._-]{1,255}$(URL-safe unreserved minus tilde)Applied at three layers for defense-in-depth:
CreateTargetRequest,TargetSummary)pattern=on the new routesCHECKconstraints via additive migrationa3e7b2f1c4d5Race safety
ensure_target_by_natural_keyusesINSERT ... ON CONFLICT DO NOTHING+ re-selectupsert_target_control_attachmentusesINSERT ... ON CONFLICT DO UPDATE SET enabled = EXCLUDED.enabled200with the sametarget_id; noIntegrityErrorsurfaces as 5xxtest_targets_natural_key.py::test_ensure_target_by_natural_key_race_safeand::test_upsert_attachment_race_safeWhat's intentionally unchanged
target_id-based routes — kept as-is for backward compatibilityPOST /targets— still strict (409 on duplicate; not made idempotent as part of this PR)GET /targets/{type}/{id}orGET /targets/{type}/{id}/controlsroutes; we deliberately scope this PR to the write pathOut-of-scope fix bundled
One small hygiene fix also included:
evaluators/contrib/galileo/pyproject.tomlwas pinningmypy>=1.8.0with no upper bound, which resolved tomypy==1.20.2on recent lock refreshes. That version only shipscp314wheels, breaking local dev for anyone on Python 3.12/3.13. Capped to<1.20. Unblocks the repo-wide pre-push hook.Test plan
make check(models + server + engine + sdk + evaluators + lint + typecheck)make sdk-ts-generate-check(after regeneration)make sdk-ts-lint/make sdk-ts-typecheck/make sdk-ts-testendpoints/targets.py95%,services/targets.py99%,models/target.py100%Reviewer focus points
server/src/agent_control_server/endpoints/targets.py— two new handlers at the bottom of the routerserver/src/agent_control_server/services/targets.py— three new helpers (get_target_by_natural_key,ensure_target_by_natural_key,upsert_target_control_attachment)server/alembic/versions/a3e7b2f1c4d5_targets_identifier_check_constraints.py— CHECK constraints ontargets.target_typeandtargets.external_idmodels/src/agent_control_models/target.py— constrained-string aliasesserver/tests/test_targets_natural_key.py— covers lazy create, idempotent PUT, idempotent DELETE, tenant isolation (9a/b/c), control-not-in-tenant 404s, path-charset guards, and concurrency