Target limits
Every command a target runs is bounded by the data.* settings: whether it may change data, how many
records it returns, how long it may run, and whether it runs at all. This page covers those settings and where each
value comes from, the limits a target receives as TargetLimits, and what the runtime does when a run reaches a
limit. Targets covers the rule files, the stages and writing a target of your own.
The data settings
| Setting | Unset | What it decides |
|---|---|---|
data.allowWrites |
false |
Whether a command may change data: with false, every operation but read is refused |
data.maxResults |
1,000 | The most records one command returns; the rest are cut off and Truncated is true |
data.timeout |
30 seconds | How long one execution may run |
data.dryRun |
false |
With true, commands are planned and checked, and none runs |
A value comes from a file first: the settings of a named rule set, then the settings document,
intentsharp.settings.json, in the client's document store. When two named sets set one of the first three, the
narrowest value applies. Then comes code, TargetSettings in TargetRuntimeOptions.Settings, and last the value in the
table. Code values are a default for when the files say nothing, never a ceiling over a file. A file removes a limit
only with unlimited, and code only with MaxResultsUnlimited or Timeout.InfiniteTimeSpan; zero never removes one.
The settings reference lists every setting.
CommandCheck.Settings shows each value and where it came from:
// Code values for the data settings. Each one applies only where no file sets that setting.
var runtime = new TargetRuntime(client, target, new TargetRuntimeOptions
{
GuardrailSets = ["customers-read"],
Settings = new TargetSettings { MaxResults = 50, Timeout = TimeSpan.FromSeconds(10) },
});
CommandCheck check = await runtime.CheckAsync(plan);
foreach (var (name, value) in check.Settings.OrderBy(s => s.Key, StringComparer.Ordinal))
Console.WriteLine($"{name} = {value.Value?.ToJsonString()} ({value.Source})");
data.allowWrites = false (FailClosed)
data.dryRun = false (LibraryDefault)
data.maxResults = 200 (File)
data.timeout = "00:00:10" (Code)
Writes
A second rule file, the set customers-write, allows reads and writes on Customers and sets data.allowWrites:
intentsharp: rules/1
set: customers-write
checkpoint: data
target: sqlite
rules:
- id: customers-only
subject: data.entities
allow: [Customers]
message: Use only the Customers table.
- id: read-and-write
subject: data.operations
allow: [read, write]
message: Read or write rows; nothing else.
settings:
data.allowWrites: true
// The rule file customers-write.rules.yaml allows writes to the Customers table.
var runtime = new TargetRuntime(client, target, new TargetRuntimeOptions { GuardrailSets = ["customers-write"] });
// The model maps CustName to FullName and Region to State by meaning; the values travel as parameters.
var signup = new { CustName = "Ada Moreno", Region = "NY", Total = 0 };
CommandOutcome written = await runtime.RunAsync("Add this signup to the Customers table.", signup);
Console.WriteLine($"{written.Plan.Command} ({written.Result?.Affected ?? 0} row)");
Result.Affected is the count the target reports. Under customers-read, which allows only reads, a command that
changes data is refused, and the model is told the rule's message.
Limits a target receives
The runtime hands the resolved values to the target as TargetExecution.Limits, a TargetLimits, so a target can
stop early: AllowWrites, MaxResults and Timeout, each of the last two null when a file or code removed it. A dry
run never reaches the target, since the runtime does not call ExecuteAsync for one. A target whose technology counts
whole seconds rounds a shorter time up to one second, never down to no limit.
The JSON-collection target of Targets runs a checked filter, stops at the result limit and stops when its token is cancelled:
/// <summary>
/// Runs a checked filter: the documents of the collection that match <c>where</c>, each with the fields of
/// <c>project</c>, at most <see cref="TargetLimits.MaxResults"/> of them, with <see cref="TargetResult.Truncated"/> set
/// when more matched. The records are copies.
/// </summary>
/// <exception cref="TargetOperationException">
/// The collection does not exist (<see cref="TargetOperationExceptionKind.SchemaMismatch"/>), or the command is not a filter
/// (<see cref="TargetOperationExceptionKind.Refused"/>).
/// </exception>
public ValueTask<TargetResult> ExecuteAsync(TargetExecution execution, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(execution);
var (filter, refusals) = ReadFilter(execution.Command.Command);
if (filter is null)
throw new TargetOperationException($"The filter cannot be run: {string.Join(" ", refusals)}") { Kind = TargetOperationExceptionKind.Refused, Target = Name };
List<JsonObject> documents;
lock (_gate)
{
if (!_collections.TryGetValue(filter.Collection, out var list))
throw new TargetOperationException($"The collection \"{filter.Collection}\" does not exist.") { Kind = TargetOperationExceptionKind.SchemaMismatch, Target = Name };
documents = list.ToList();
}
var records = new List<JsonObject>();
var truncated = false;
foreach (var document in documents)
{
ct.ThrowIfCancellationRequested();
if (!Matches(document, filter.Where)) continue;
if (execution.Limits.MaxResults is { } max && records.Count >= max)
{
truncated = true;
break;
}
records.Add(Project(document, filter.Project));
}
return new ValueTask<TargetResult>(new TargetResult { Records = records, Truncated = truncated });
}
When a run reaches a limit
The runtime enforces both limits itself, so a target that ignores them still cannot run without one.
- It cuts the records to
data.maxResults, andTruncatedis true. - At
data.timeout, sub-second values included, it cancels the token the target received and stops waiting. The call throwsTargetOperationExceptionwhoseKindisTimeout. - A target that does not stop when its token is cancelled is abandoned: the runtime logs a warning and the call throws as above. The runtime does not wait for the work the target goes on doing and cannot stop it, so a target should stop when its token is cancelled.
- A
TimeoutExceptionthe target throws itself reaches you as it was thrown.