Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ describe('a head CEL claims is read through `vars`, the route a `$`-named head t
expect(refusalOf('{record.true}')).not.toContain('the guarded form');
});

it('an index anywhere in the path leaves it unguarded — `has()` refuses one at run time', () => {
it('an index in the path leaves it unguarded — a step-at-a-time guard would end a `has()` on it, which `has()` refuses at run time', () => {
expect(refusalOf('{rows.0.name}')).toContain("source: 'rows[0].name' }");
expect(refusalOf('{rows.0.name}')).not.toContain('the guarded form');
});
Expand Down
10 changes: 6 additions & 4 deletions packages/spec/src/automation/flow-value-slot-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,12 @@ type Write = (source: string) => string;

/**
* A `has()` guard for a path of keys — the absent-variable and absent-key
* remedy. `has()` takes a field selection over names alone: an index anywhere
* in its argument (`has(a[0].b)`, `has(vars["$x"].y)`) is refused when it
* runs, so a path with a numeric segment, a `$`-named head or a keyword
* segment gets no guard.
* remedy. `has()` refuses an index as the last step of its argument
* (`has(a[0])`, `has(vars["$x"])`) when it runs; an index before the last
* step is a presence test like any other (`has(a[0].b)`). The guard tests
* every step of the path, and a numeric segment, a `$`-named head or a keyword
* segment is read by index, so one of its tests would end on that index: such
* a path gets no guard.
*
* The guard tests one step at a time, from `vars`, which holds only the
* variables the run has bound: `has(vars.source) && has(vars.source.id) ?
Expand Down
Loading