Skip to content

feat(parser): expose per-command groups in parsed.calls#24

Merged
srnnkls merged 1 commit into
mainfrom
feat/parsed-calls-join
Jun 27, 2026
Merged

feat(parser): expose per-command groups in parsed.calls#24
srnnkls merged 1 commit into
mainfrom
feat/parsed-calls-join

Conversation

@srnnkls

@srnnkls srnnkls commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Problem

The flat parsed.commands / parsed.targets lists can't say which target a
given command acts on. So cat README && rm .env drops a read verb (cat) and
a secret (.env) into the same flat lists — a rule keying off "a read verb
and a secret target" fires even though nothing reads the secret. The join is
inexpressible because the relationship was flattened away before the rule sees it.

Fix

Add parsed.calls: each invocation grouped with its own command,
subcommand, action, targets, and flags. A rule can now match a single
call that both runs a read verb and targets a secret:

tool_input: parsed: calls: list.MatchN(>0, {
	command: _readVerb
	targets: list.MatchN(>0, _secretFile)
	...
})

This is parse-time pre-joining, not a relational match. The engine still
matches by subsumption; list.MatchN(>0, {command, targets}) over calls is an
ordinary existential over one struct's fields, so it stays within what
subsumption can verify. The flat lists are unchanged and remain deny-safe.

Tests

  • internal/parser/bash_calls_test.go — per-command grouping (compound lines,
    read-of-secret, subcommand + flags).
  • internal/contract/calls_join_test.go — end-to-end subsumption proof mirroring
    the evaluator's When.Subsume(input, …): fed real parser output, the flat
    shape matches both cat .env and cat README && rm .env; the join matches
    only the genuine read.
  • #Parsed schema gains calls? (open-typed per the R5 rule); the bidirectional
    drift test pins it in sync with parser.Parsed.

The author-facing doc for the field lives with the GUIDE in #23 (GUIDE.md isn't
on main yet); it'll land there rather than duplicate the file into this PR.

The flat parsed.commands/targets lists cannot bind a verb to the target it
acts on, so `cat README && rm .env` puts a read verb and a secret into the
same lists and a rule keying off both over-matches. parsed.calls groups each
invocation with its own command, subcommand, action, targets, and flags, so a
rule can match a single call that both runs a read verb and targets a secret.

This is parse-time pre-joining, not a relational match: the engine still
matches by subsumption, and list.MatchN(>0, {command, targets}) over calls is
an ordinary existential over one struct's fields. calls_join_test pins it —
the flat shape matches both `cat .env` and `cat README && rm .env`; the join
matches only the genuine read.
@srnnkls
srnnkls merged commit 017b894 into main Jun 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant