Skip to content

[AAASM-5529] ✅ (test): Bind quick-start enforcement claims to their proving controls - #317

Merged
Chisanan232 merged 9 commits into
mainfrom
v0.0.1/AAASM-5529/test/quickstart_drift_gate
Aug 13, 2026
Merged

[AAASM-5529] ✅ (test): Bind quick-start enforcement claims to their proving controls#317
Chisanan232 merged 9 commits into
mainfrom
v0.0.1/AAASM-5529/test/quickstart_drift_gate

Conversation

@Chisanan232

@Chisanan232 Chisanan232 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

Binds every sentence of the Python quick-start to a control that proves it, or to an explicit allow-list entry saying why it makes no claim.

Revision 4. Review closed R1/R3/R4 and found two structural issues plus a stale reference in this body. All fixed below.

R3-1 — the justification rule was inverted relative to risk

A written justification was required only when the sentence matched the enforcement vocabulary — only for sentences an author had already been warned about. A sentence that evaded the vocabulary, which is the entire reason the scan was inverted, passed with a bare category constant.

The bare constant is now available only to structurally non-prose lines (tab labels, table rows, migration import pairs, bare link items), matched by a pattern. Every other entry carries a justification unique to that sentence — 34 of them.

R3-3 — matched == 1 was structurally unreachable above 1

The scan was keyed by sentence, so duplicates collapsed before anything counted them and the check could only ever see 0 or 1, while the message printed it matched {n} as though n > 1 were reachable. Section attribution was last-write-wins for the same reason.

Occurrences are now a list of (text, section). Measured — duplicating a bound true sentence into an inverting section now fails:

it matched 2 (sections: ['## What just happened', '## What NOT to expect'])

Splitter

Closing markup between a terminator and the following space is kept with its sentence, so **Tool calls were governed.** is its own unit instead of running into the claim it introduces — binding the glued pair covered both halves. A backtick is deliberately excluded from that trailing class: inline code such as \phi.*`` was otherwise read as a sentence end and split mid-sentence. Four bindings were re-derived and two bold labels are now bound in their own right.

Tickets — this body previously named the wrong one

The mode claim now names AAASM-5766 and the breadth claim AAASM-5768. Both previously named AAASM-5536, a docs-CI-tooling ticket that will prove neither.

Correcting this body: revision 3 of it said the mode claim was registered "naming AAASM-5529's own undelivered mode-probing check." That was stale — and naming 5529 is exactly what the self-reference guard forbids. Worth fixing in a PR about documentation drift.

What "green" means here, and what a written justification is not

Green means the page's unproven claims are enumerated, not that the page is true. The count is the most useful number this PR produces:

Repo Claims bound Registered unproven
python-sdk 14 8
go-sdk 7 1
node-sdk 11 1

An unproven claim names the ticket that will resolve it, and may not name AAASM-5529 — a self-reference resolves to a closed issue on merge.

Accepted residual, stated rather than implied: "written justification" is enforced as non-empty, ≥40 characters, and not byte-identical to another entry. That is all it is. The check cannot separate a justification written per sentence from one templated with a substitution — one differing token satisfies distinctness — and no check can separate either from noise. It is worth having only because it makes the two cheapest ways of waving something through, an empty gesture and a verbatim copy-paste, visible in review. Uniqueness here should not be read as implying bespoke.

The three residuals — the honest boundary of what this gate buys

  1. No conjunction list can be complete. A semicolon does the same job with no conjunction at all, and passes. so is worth catching because it is ordinary prose that reads as reassurance — the register documentation over-claims in — but chasing ;, and and would flag large amounts of benign text for very little gain. The rule catches the shapes that hide a claim, not every shape that could.
  2. The tab-label route is the one place a structural token legitimately carries rendered text. Three narrowings have taken the bare constant from 44 / 13 / 10 entries to 20 / 4 / ~10, and each remaining route is stranger than the last — a payload smuggled through a tab caption is conspicuous in a way a link item or table row was not. It is a narrowed hole, not a closed one.
  3. Justification quality is length and distinctness only. See the note above: the check cannot separate written-per-sentence from templated-with-a-substitution, and neither from noise.

so — corrected from the previous revision

I had excluded so from the conjunction list as consequential rather than adversative. The grammar was right and the question was wrong: the risk is polarity change, and "we don't do X so Y covers it" is a reassurance — the highest-risk shape, not the lowest. so is now flagged when the clause before it is negated and the clause after is not.

Measured across the three repos: 7 live allow-listed sentences contain " so " (python 2, go 3, node 2); the refined rule flags none of them and catches the payload in all three. My earlier count of two was python-only.

R4 additions

  • The bare allow-list constant is now near-empty. It previously asked whether a sentence started with structure, so a link item or table row was waved through on its first characters while its anchor text — rendered prose a reader sees — went unexamined. [Every tool request is permitted to proceed and its outcome captured for later analysis](x.md) passed. The pattern is fully anchored now and admits only lines that are entirely scaffolding.
  • The contrastive-conjunction rule applies to every allow-list entry, in all three repos. It previously existed only in Go, and in Go it was gated on a case-sensitive marker phrase — so "A LIMITATION disclaimer…", the wording that shipped one round earlier, evaded it. so is excluded from the conjunction list as consequential rather than adversative; both attack payloads (because, but) are still caught.

Type of Change

  • ✨ New feature (a CI gate)

Breaking Changes

  • No

Related Issues

  • Related JIRA ticket: AAASM-5529 (Epic AAASM-5526)
  • Registered unproven against: AAASM-5661, AAASM-5766, AAASM-5768

Testing

Every check demonstrated able to fail, by mutation. Each mutation asserts it applied before measuring and each restore is sha256-verified:

R1  three appended sentences using no vocabulary term  -> 1 failed, 69 passed
R3  claim in the formerly excluded section             -> 1 failed, 69 passed
R4  bound claim commented out, false one in its place  -> 2 failed, 68 passed
R3-1 evasive claim waved through with the bare const   -> 1 failed, 80 passed
R3-3 bound sentence duplicated into an inverting sec.  -> 1 failed, 80 passed
restored                                               -> 81 passed
Gate Result
pytest test/unit/test_quickstart_claim_bindings.py 81 passed
pytest test/unit 1238 passed, 4 skipped
ruff check / format --check clean
mypy (cache cleared) no issues in 192 files

F7 — enforcement scope

$ gh api repos/ai-agent-assembly/python-sdk/branches/main/protection --jq '... required_status_checks ...'
NONE

main declares no required status checks, and quick-start claim bindings is in no aggregation — python-sdk has no ci-success.yml, and CI Success is a job inside ci.yaml, which a separate workflow file cannot join. Visible on the PR; not merge-blocking. AAASM-5677's scope.

Pre-existing red on main, untouched

pre-commit run --all-files fails on scripts/check_contact_metadata.py (ruff-format) on pristine remote/main. Does not block commits (staged-files-only hook); no CI runs --all-files.

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated if needed — quick-start prose deliberately unchanged
  • All tests passing

…proves it

docs/quick-start.md §"What just happened" carries the Python quick-start's
enforcement claims, and nothing connected them to the negative controls. A
claim could be added, reworded, or left standing after the behaviour beneath
it changed, and no gate would notice.

Each claim is now bound to the controls that prove it. The claim list is
parsed from the document and the control names are extracted from the
negative-control module's AST, so neither side is a transcribed constant
checking another transcribed constant: adding a claim, rewording one,
renaming a control, or renaming the exception the document names each fail
here.

Claim 1 is registered as deliberately unproven, naming AAASM-5661. Every
control installs a fake native core that the documented configuration does
not have, so binding it to one would launder that gap into evidence.

The SDK symbols a claim names are resolved lazily rather than imported at
module scope. Imported at module scope, a rename becomes a collection error
that aborts before the assertion meant to catch it can run — the same
inverted-order defect the round-1 review of this ticket found in all three
SDKs.

Refs AAASM-5529
ci.yaml's paths allow-list excludes docs/**, so a PR that only rewords a
quick-start claim gets no CI at all — which is exactly the change the
claim-binding gate exists to catch. quickstart-tabs-check.yml already
triggers on docs/quick-start.md, so the gate runs there.

The negative-control module and the exceptions package are added to the
trigger paths as well. ci.yaml already covers both under test/**/*.py and
agent_assembly/**/*.py; the entries here are what make a renamed control or
a renamed exception re-run this workflow too.

What the two jobs prove is deliberately different, and neither covers the
other: drift-check round-trips the §3 tabs as text and never parses,
imports or executes a snippet; claim-bindings gates the prose claims in
§"What just happened" against named controls.

Refs AAASM-5529
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ts in a region

Review found the central promise breaks in under a minute. Three defects,
all in this file:

F1 — the gate asked whether the region CONTAINED a binding's quote, so a
sentence could carry unlimited extra unbound claims as long as one fragment
survived. Python bound at list-item granularity, so an appended sentence rode
free. Quotes are now whole sentences compared with ==, and exactly one
binding may match a sentence.

F6 — only two regions were scanned, so claims elsewhere were invisible.
Notably "the other modes add network/kernel interception" at the foot of the
page, which is a named acceptance check of this very ticket. The scan now
covers the whole document minus two named allow-lists, each entry carrying a
reason. An allow-listed sentence must still be present verbatim, so an entry
cannot silently cover a reworded claim.

F4 — kind=LIFECYCLE was an unchecked one-word bypass: relabel a claim, drop
its controls, no ticket needed, green. It was also already live in-tree, on a
binding whose unproven_reason named no ticket. The field is removed rather
than fixed; every claim now needs controls or a ticketed reason.

F5 — the vocabulary was three alternatives, so a claim phrased around them
was not treated as a claim. Widened to match the Go gate's, plus throws,
routed, intercepts, governed, verified, protection and bypass.

Refs AAASM-5529
Replacing a fenced block with a space glued the sentence before a code
sample to the sentence after it. Measured on this document: 18 such pairs.

A glued pair is fragment containment one level up — a binding quoting it
would cover two claims at once, and the second could then be reworded or
negated without the gate noticing. Found while fixing the same bug in the
Go and Node gates, then checked for here rather than assumed absent.

No binding changed: none of the 18 pairs carries enforcement vocabulary
today. The hole was latent, not live.

Refs AAASM-5529
Node's four Windows CI legs failed the equivalent gate: git checks the doc
out with CRLF, so the paragraph split never fires and the section collapses
into one "sentence" that matches no binding.

This repo's CI is Linux-only, so the bug is latent here rather than live.
Normalised anyway — a gate whose result depends on the checkout's line
endings is not a gate. Verified against a CRLF copy of the document.

Refs AAASM-5529
…no keyword filter

Review appended three plain sentences to the quick-start, the last of them
"Tool bodies always execute; the policy result is recorded alongside them" —
the negation of the product — and this gate stayed green. None of the three
used any of the 21 vocabulary terms. Widening 3 -> 21 closed the instance,
not the class: a keyword allow-list cannot be completed, because whoever
adds the claim picks the words after reading the list.

R1 — the vocabulary no longer gates anything. Every one of the document's 88
sentences must be bound to a control or allow-listed by exact text. The
vocabulary survives as a severity hint in the failure message, and as the
trigger for a stricter allow-list rule: waving through a sentence that reads
like a claim costs a written justification, not a category.

R3 — section exclusions are gone. An excluded section was a black hole: the
guard checked the heading still existed and said nothing about its contents,
so a claim inserted into "## Next steps" was never scanned. A positive
control now asserts that section is in the scan.

R4 — HTML and MDX comments are stripped, as fences already were, and for the
same reason: a reader cannot see them. Leaving them in let a bound claim be
commented out of the rendered page while the gate still counted it.

R5 — an unproven_reason may no longer name AAASM-5529, the ticket this
module implements. The mode=auto registration did, and would have resolved
to a closed issue the moment this merged, with nothing noticing. Repointed
at AAASM-5536, and the reason now states plainly that no ticket owns proving
the claim and the expected resolution is qualification.

R6 — the splitter no longer emits bare list markers as sentences, treats "!"
as text so mkdocs "!!! note" survives intact, handles "?" as a terminator,
splits table rows and list items as units, and strips front matter, which
Python lacked where Go and Node had it.

Inverting the default immediately found five claims every keyword-gated
revision was blind to, including the page's own central promise ("whose tool
calls pass through the Agent Assembly policy gate") and an unbounded breadth
claim ("governs whichever agent framework you already use"). Four are now
registered unproven against AAASM-5661 and AAASM-5536; the teardown claim,
previously exempt under the removed `kind` field, is bound to a real control.

Refs AAASM-5529
…nd count occurrences

R3-1 — the two-tier rule was inverted relative to risk. A written
justification was required only when the sentence matched the enforcement
vocabulary, i.e. only for sentences an author had already been warned about.
A sentence that EVADED the vocabulary — the entire reason the scan was
inverted — passed with a bare category constant. The bare constant is now
available only to structurally non-prose lines (tab labels, table rows,
migration import pairs, bare link items), matched by a pattern; every other
entry carries a written justification unique to that sentence.

R3-3 — the scan was keyed by sentence, so duplicates collapsed before
anything counted them and "matched == 1" could only ever be 0 or 1, while
the message printed "it matched {n}" as though n > 1 were reachable.
Occurrences are now a list of (text, section). Measured: duplicating a bound
true sentence into a "## What NOT to expect" section now fails with
"it matched 2 (sections: ['## What just happened', '## What NOT to
expect'])". Section attribution was last-write-wins for the same reason and
is now per-occurrence.

Splitter — closing markup between a terminator and the following space is
kept WITH its sentence, so "**Tool calls were governed.**" is its own unit
rather than running into the claim it introduces. Binding the glued pair
covered both halves at once. A backtick is deliberately excluded from that
trailing class: inline code such as `phi.*` was otherwise read as a sentence
end and split mid-sentence. Four bindings were re-derived and two bold
labels are now bound in their own right.

Tickets — the mode claim now names AAASM-5766 and the breadth claim
AAASM-5768. Both previously named AAASM-5536, a docs-CI-tooling ticket that
will prove neither.

Accepted residual, stated rather than implied: "written justification" is
enforced as non-empty, at least 40 characters, and distinct across entries.
No gate can tell a justification from noise; length and uniqueness only make
an empty gesture and a copy-paste visible.

Refs AAASM-5529
…tences that turn

R4-1 — the structural pattern was the new bare-constant route. It asked
whether a sentence STARTED with structure, not whether it was ONLY
structure, so a link item, a table row or a bold link was waved through on
its first characters while its anchor text — rendered prose a reader sees —
went unexamined. It covered 44 of 78 entries here. Measured: both
"[Every tool request is permitted to proceed and its outcome captured for
later analysis](x.md)" and the table-row form passed with the bare constant;
both now fail.

The pattern is fully anchored and admits only a bare mkdocs tab label. The
other 24 entries — migration import pairs, Source attributions, Next-steps
link items — now carry written justifications.

R4-3 — the contrastive-conjunction rule existed only in Go; it is ported
here. A sentence that turns mid-way can under-claim and over-claim at once,
and a justification arguing "this only says what the product does NOT do"
cannot be trusted for one that does. Both attack payloads are rejected: the
reviewer's "because" form and the "but" form the Go entry had.

"so" is deliberately excluded from the conjunction list. It is consequential
rather than adversative, and the two sentences it flagged here turn in the
direction they started. Excluding it costs neither payload, and controls for
both are recorded in the falsification run.

Refs AAASM-5529
I excluded "so" from the contrastive list on the grounds that it is
consequential rather than adversative. The grammar was right and the
question was wrong: the risk "so" carries is POLARITY CHANGE between
clauses, and on that axis it is the highest-risk connector, not the lowest.
"We do not do X but Y" is a concession; "we do not do X so Y covers it" is a
REASSURANCE, and reassurance is the register documentation over-claims in.

The payload that was green in all three: "Network-layer interception is not
enabled by default, so the in-process adapter verifies every outbound
request before it leaves the host instead." The second clause is an unbound
affirmative capability claim, and false — no SDK adapter inspects outbound
network requests, which is why AAASM-5766 exists.

"so" is now flagged only when the clause before it contains a negation and
the clause after it does not. That is the one shape a limitation-plus-
reassurance takes, and the payload is the only negative-to-positive case
among the live sentences.

Measured across the three repos: 7 live allow-listed sentences contain
" so " (python 2, go 3, node 2) and the refined rule flags none of them,
while the payload fails in all three. My earlier count of two was
python-only; the reviewer's six was also short, and node's figure needed
reading ALLOWED at runtime because a regex over the source recovers only 31
of its 50 keys.

Refs AAASM-5529
@sonarqubecloud

Copy link
Copy Markdown

@Chisanan232
Chisanan232 merged commit 5c69855 into main Aug 13, 2026
24 checks passed
@Chisanan232
Chisanan232 deleted the v0.0.1/AAASM-5529/test/quickstart_drift_gate branch August 13, 2026 14:13
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