> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Constraints and Scheduling Rules

> Declare business invariants once and have every write checked, including no-overlap and within-shift rules over time intervals

> [!NOTE]
>
> These examples describe the current Bijection CLI and SDK source. Use builds
> from the same repository revision for these APIs. The public CLI and npm SDK
> are released independently and may lag this reference.

A *constraint* is a rule about your data that holds after every transaction.
You declare it in the schema; Bijection checks it in the final state of every
transaction that changes its inputs, whichever function, import or
administrative edit made the change. A write that breaks a constraint is
refused whole, with `ConstraintViolation`. Intermediate states inside one
transaction may break it as long as the final state holds.

## Rules over a view

The ordinary form is a predicate every row of a local view satisfies:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export const creditLimit = defineConstraint({
  relation: CustomerCredit,
  predicate: (row) => q.lte(row.reserved_minor, row.credit_limit_minor),
});

export default defineSchema(
  { customers, orders, CustomerCredit },
  { constraints: { credit_limit: creditLimit } },
);
```

The schema key is the constraint's name, in snake\_case. Constraints read only
tables your deployment owns: an observation synced from another system is never
refused by a local rule.

## Time intervals

`v.timeInterval()` stores a half-open period `{ start, end }` in exact epoch
microseconds, with `start` before `end`. Bijection refuses any other value
wherever the interval is stored, passed or returned. Index its `start` to order
rows by it:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bookings: defineTable({
  job_id: v.id("jobs"),
  resource_id: v.id("resources"),
  during: v.timeInterval(),
}).index("by_resource_start", ["resource_id", "during.start"]),
```

`q.overlaps(a, b)` and `q.contains(a, b)` compare intervals in views and
predicates. Touching intervals, such as 08:00–12:00 and 12:00–16:00, do not
overlap.

## No-overlap and within rules

Two constraint forms hold scheduling rules directly on start-ordered indexes.
The index's fields before `during.start` are the partition, here one resource:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const constraints = {
  // A resource holds one booking at a time.
  no_double_booking: defineConstraint({
    disjoint: { table: bookings, index: "by_resource_start", interval: "during" },
  }),
  // Shifts never overlap.
  disjoint_shifts: defineConstraint({
    disjoint: {
      table: availability,
      index: "by_resource_start",
      interval: "during",
      where: { kind: "shift" },
    },
  }),
  // Every booking lies within its resource's shifts.
  within_a_shift: defineConstraint({
    within: {
      table: bookings,
      index: "by_resource_start",
      interval: "during",
      container: {
        table: availability,
        index: "by_resource_start",
        interval: "during",
        where: { kind: "shift" },
      },
    },
  }),
};
```

A `within` rule needs a `disjoint` rule on the same container rows. Adjacent
containers cover the boundary they share, so a booking from 11:00 to 13:00 fits
two shifts that meet at 12:00. Removing or shortening a shift that still holds a
booking is refused.

Each changed booking is checked against its neighbours in the index, not the
whole table, and two transactions that try to place overlapping bookings at the
same moment cannot both succeed. A check that cannot decide within its bounds
refuses with `ConstraintUndetermined` instead of letting the write through.

## Suggesting slots and rescheduling

`scheduleWeights` declares soft rules as exact integer weights: lateness per
started hour past a job's due time, moving a booking, changing its resource.
`scheduling.slots(ctx, ...)` in a query returns free, scored slots for a job
within at most 31 days and 64 resources, and updates as bookings commit. It
reports `is_complete: false` when it stops at a bound. A slot reserves nothing;
book it with your ordinary mutation and the constraints decide.

For larger changes, `jobShop` gives the interval-shaped problem and candidate
validators for a customer solver, and `verifySchedule` rechecks a candidate's
hard rules and recomputes its objective from the same weights before you apply
it.
`partitionJobShop(problem)` splits a problem into groups of jobs that can share
a resource. Solve, verify and apply each group on its own: the groups share no
resource, so their objectives add up, and a booking that changed since capture
refuses only its own group.

The [scheduling example](https://github.com/bijectionhq/bijection/tree/main/examples/scheduling)
adds skill, release-time, duration and precedence rules as view predicates and
expands recurring shift patterns from IANA time zones on a daily cron.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.