Skip to content

feat(ssp-templates): run, created SSPs and forget routes - #526

Merged
gusfcarvalho merged 2 commits into
feat/ssp-templates-4-discoveryfrom
feat/ssp-templates-5-routes
Oct 22, 2026
Merged

gusfcarvalho merged 2 commits into
feat/ssp-templates-4-discoveryfrom
feat/ssp-templates-5-routes

Conversation

@ccf-lisa

@ccf-lisa ccf-lisa Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Place in the stack

SSP templates (feature 2 of ssp-templates.md), PR B5 of 5. It is the second half of the design's B4, split to keep each PR under 1000 lines. Base: B4 (#525, feat/ssp-templates-4-discovery). Below it: B3 (#524), B2 (#523), B1 (#522) and the SSP scope stack (#516–#520).

What it adds

All three routes sit behind the existing /api/admin/ssp-templates group: JWT + admin:manage.

  • POST /api/admin/ssp-templates/{id}/run queues a discovery run of the template (ssp_template_discovery with the template id as args) and answers 202.
    • 404 for an unknown template, 409 for a disabled one.
    • 503 when no job queue is wired, and 500 when the worker is disabled.
    • Runs are unique by args among waiting or running jobs, so a finished run never blocks the next one.
    • worker.Service.EnqueueSSPTemplateDiscovery implements the new templates.SSPTemplateRunEnqueuer, wired through APIServices.SSPTemplateRunEnqueuer in cmd/run.go.
  • GET /api/admin/ssp-templates/{id}/ssps is paginated and newest first. Each item has:
    • key-values and key-hash (a contract addition from review: the forget route's id);
    • ssp-id (null once the SSP was deleted) and the SSP's current title;
    • created-at and last-seen-at;
    • stale: the SSP exists and was last seen more than CCF_SSP_TEMPLATE_STALE_AFTER ago (from B4's config);
    • last-error, skipped-entries and skipped-sample.
  • DELETE /api/admin/ssp-templates/{id}/ssps/{keyHash} forgets an instance whose SSP was deleted, so the next run creates it again. It answers 409 while the SSP exists and 404 for an unknown instance.

Swagger is regenerated.

How it was tested

  • Integration under the embedded Cedar PDP (ssp_template_instances_integration_test.go):
    • run: queued once, disabled 409, unknown 404, contributor 403, no job queue 503;
    • list: key values, key hash, title, stale/fresh/deleted, last error, skipped entries; contributor 403; unknown template 404;
    • forget: 409 while the SSP exists, 404 unknown, contributor 403, 204 for a deleted SSP, and the list shrinks afterwards.
  • Unit: enqueueing with the worker disabled is an error.
  • Gate on this branch:
    • make swag, make lint (only the existing SA1019), make test, go vet -tags integration ./...;
    • integration suites of internal/api/handler, internal/api/handler/templates, ssptemplates, internal/service/worker;
    • make check-diff.

Changed lines excluding generated swagger: 395.

Deviations from design

  • This PR exists only because B4 was split.
  • The instance list is paginated, and adds title, key-hash (contract addition) and skipped-sample to the design's fields.
  • A run requested while the worker is disabled answers 500 instead of a 202 for a run that would never happen.

Open questions for reviewers

  • Should a run requested while the worker is disabled answer 503 rather than 500? Telling that case apart from an enqueue failure needs an error exported from the worker package.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 2403ca96-a1cf-4097-805b-1bc9935c6582

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ccf-lisa Bot and others added 2 commits October 19, 2026 11:50
POST /admin/ssp-templates/{id}/run queues a discovery run (409 when the
template is disabled). GET /admin/ssp-templates/{id}/ssps lists the SSPs the
template created with their key hash, stale flag, last error and skipped
entries. DELETE /admin/ssp-templates/{id}/ssps/{keyHash} forgets an instance
whose SSP was deleted, so discovery creates it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gusfcarvalho gusfcarvalho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ccf-review: APPROVE

no findings.

Stack (gh stack 521): #516 → #517 → #518 → #519 → #520 → #522 → #523 → #524 → #525 → #526

@gusfcarvalho gusfcarvalho added the preview Publish a :pr-<n> preview build label Oct 19, 2026
@gusfcarvalho
gusfcarvalho merged commit 75918b2 into main Oct 22, 2026
40 of 43 checks passed
@gusfcarvalho
gusfcarvalho deleted the feat/ssp-templates-5-routes branch October 22, 2026 08:44
gusfcarvalho pushed a commit that referenced this pull request Oct 22, 2026
## Place in the stack

SSP templates (feature 2 of `ssp-templates.md`), PR **B1 of 5** (B1–B5:
#522, #523, #524, #525, #526), stacked on the SSP scope stack
(#516–#520, top `feat/ssp-scope-5-auto-filters`).

## What it adds

- Models `SSPTemplate` and `SSPTemplateInstance`
(`internal/service/relational/ssp_templates.go`), in both AutoMigrate
lists and both drop lists. Their foreign keys (instance → template `ON
DELETE CASCADE`, instance → SSP `ON DELETE SET NULL`) are added by
`EnsureSSPConstraints`, which is `EnsureSSPScopeConstraints` renamed,
with two keys appended. AutoMigrate here does not enforce gorm FK tags.
- Package `internal/service/relational/ssptemplates`:
- `Build`: validation and normalization. Keys are trimmed, lower-cased
and sorted (1–5). Scope-label groups are normalized the same way (≤10
groups of 1–5 labels). Keys and labels follow `sspscope.Normalize`'s key
rules, so anything a template accepts can be stored as a scope entry.
`min-streams` must be ≥ 1. The title template is required, and the
title, description and prop templates must parse. The labelfilter must
be valid.
- `Service`: list/get/create/update/delete. Profile ids must exist. Keys
cannot change on update (400).
- Admin routes `/api/admin/ssp-templates` (list, create, get, update,
delete), behind `JWTMiddleware` + `pep.Authorize(authz.ResourceAdmin,
authz.ActionManage)`, like `/admin/subject-templates`. JSON is
kebab-case. Swagger is regenerated.
- **Contract addition from review**: template responses also return
`ssp-count` and `last-run-at`.
  - `ssp-count` is the number of instances whose SSP still exists.
- `last-run-at` is a new nullable `last_run_at` column. It stays `null`
until B4's discovery job sets it, and Update preserves it.

Not in this PR: preview (B3), SSP creation (B2/B4), discovery, run,
instances routes (B4).

## How it was tested

- Unit: `ssptemplates/validate_test.go`. It covers normalization,
defaults, and every rejection rule.
- Integration
(`internal/api/handler/templates/ssp_template_integration_test.go`) runs
under the embedded Cedar PDP:
  - an admin creates, lists, gets, updates and deletes templates;
  - changing keys is rejected;
  - a contributor gets 403 on every route;
  - 400/404 cases;
- the FK behaviour and `ssp-count`: deleting an SSP nulls its instance's
`ssp_id` and drops it from `ssp-count`, and deleting a template drops
its instances and keeps its SSPs.
- Gate on this branch:
- `make swag`, `make lint` (only the existing SA1019 in
`internal/api/metrics.go`), `make test`, `go vet -tags integration
./...`;
- integration suites of `internal/api/handler`,
`internal/api/handler/templates`, `internal/service`,
`internal/service/relational`, `internal/service/relational/sspscope`;
  - `make check-diff`.

Changed lines excluding generated swagger: 1000.

## Deviations from design

- The instance model has exactly the design's fields. The
`skipped-entries` count and sample reported by the instances route are
added in B4, with the code that writes them.
- Duplicate scope-label groups (after normalization) are rejected with
400, not merged silently. Duplicate profile ids are de-duplicated.
- An empty filter (`{}` / `{"scope":{}}`) matches everything, so it is
stored as no filter.
- The `enabled` column has no database default (the design has
`default:true`). gorm writes a column default over a false `Enabled` on
create, so a disabled template was stored as enabled. `Build` always
sets `Enabled`, with nil meaning true.
- The list route is paginated (`svc.ListResponse`, `page`/`limit`) like
subject templates.
- The response timestamps are kebab-case (`created-at`/`updated-at`),
unlike subject templates' camelCase ones.

## Open questions for reviewers

- Templates are only checked to parse. Should save also reject templates
that reference a field that is not one of the keys (keys are
lower-cased, so `{{ ._APP }}` would render empty)? The design asks only
for parse errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: ccf-lisa[bot] <286799724+ccf-lisa[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Publish a :pr-<n> preview build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant