Skip to content

Standardize check-path policy operators (_neq, typed _gt/_lt) + honest SDK check type #371

Description

@EricAndrechek

Follow-up from #358 (which enforced _in on the filter + check paths and closed #224).

Today the check/insert path deliberately honors only _eq and _in; #358 added config-load + API-boundary rejection of _neq/_gt/_lt and of a mixed _eq+_in pair on check. That was the right minimal call, but it leaves the operator vocabulary asymmetric between filter (all five operators) and check (two), and it leaves the SDK type more permissive than the server. This issue tracks standardizing that — split into three independent pieces because they carry very different risk.

Background: why check is scoped narrow (not arbitrary)

The comparison happens in a different place on each path:

  • filter pushes >/</!= down to ClickHouse as SQL, so the column's type drives a correct typed comparison.
  • check evaluates in Go, in-process, via fmt.Sprint string equality (internal/api/ingest.go, valueInSet). _eq/_in reduce cleanly to string equality/membership; ordered operators do not"9" > "100" is true lexically, false numerically.

So this is not "wire up the missing operators" — the ordered operators need a typed-comparison implementation that does not exist on the insert path today.

Scope

  • Add _neq to the check path. Cheap and safe: it's _eq inverted on the same fmt.Sprint string form — "inserted value must not equal the claim value," reject-only, no auto-inject. Rounds out the equality family with near-zero risk. Drop the _neq branch from the validateRolePerms check-path rejection and add enforcement in processRecord + tests.

  • Split PolicyCheck from PolicyFilter in the SDK. Today check reuses PolicyFilter (clients/ts/src/types.ts), so TypeScript accepts check: { amount: { _gt: "…" } } (and both _eq + _in on one column) that the server rejects at .set() / .validate() with a 400. Give check its own type reflecting the enforced schema (_eq / _in today, _neq once the box above lands) so the types stop advertising operators the server refuses. Low-risk type-honesty fix; the server stays source of truth.

  • Scope _gt / _lt on the check path — gated on real demand. Legit multi-tenant/quota patterns exist ("inserted amount ≤ your tier's cap," "timestamp ≥ a claim floor"), but this requires type-aware ordered comparison (numeric / date / string) on the insert path — string comparison would be a latent correctness-and-security bug in a guard. This piece supersedes the Policy filter/check: _in accepted but never enforced; check honors only _eq #224/fix(policy): enforce _in on filter and check paths #358 decision to reject those operators, so treat it as a deliberate design change, not a gap-fill. Don't start without a concrete use case.

Notes

  • The API boundary already enforces whatever validateRolePerms decides (Store.PutValidate, plus the POST /v1/admin/policy/validate dry-run), so any operator change here is picked up by the SDK/gh callers for free — no separate endpoint work.
  • Keep the access-control.mdx operator table + field-reference rows in sync with whatever ships (per AGENTS.md §Documentation Sync).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/policyAccess control policies (Hasura-style)area/sdkTypeScript SDK (clients/ts/)enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions