From d3abc1c8096768d9959335475b7172589a2bb46e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:44:24 +0000 Subject: [PATCH] docs(spec): the guardOf docblock and its test title state the has() rule that holds now has() refuses an index only as the last step of its argument; an index before it is a presence test. The guard still skips a path with a numeric, `$`-named or keyword segment, because its step-at-a-time chain would end one test on that index. No behaviour change. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA --- .../src/automation/flow-value-slot-template.test.ts | 2 +- .../spec/src/automation/flow-value-slot-template.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/spec/src/automation/flow-value-slot-template.test.ts b/packages/spec/src/automation/flow-value-slot-template.test.ts index 605c23497ac..3d486fcbccd 100644 --- a/packages/spec/src/automation/flow-value-slot-template.test.ts +++ b/packages/spec/src/automation/flow-value-slot-template.test.ts @@ -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'); }); diff --git a/packages/spec/src/automation/flow-value-slot-template.ts b/packages/spec/src/automation/flow-value-slot-template.ts index dbff1e7810d..462c75fd917 100644 --- a/packages/spec/src/automation/flow-value-slot-template.ts +++ b/packages/spec/src/automation/flow-value-slot-template.ts @@ -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) ?