Repository navigation
Releases: Dashtid/subvectors
Release list
v0.7.0 - two environments, one subject
Three things happened in this release. The github-aws work that had sat on main
since 2026-09-03 ships (suite 0.6.0 -> 0.9.0). The corpus's observed count moves
for the first time since August, 11 -> 16, on tokens a real GitHub Actions job
minted - and one of the two probes found a subject collision. And a suite that
Microsoft's documentation had quietly falsified is corrected, with the
contradiction between Microsoft's own two pages encoded rather than papered over.
Totals: 14 suites, 163 vectors, 16 observed / 147 documented.
% is not escaped: two environments, one subject (observed)
GitHub documents that a : inside a metadata value is minted as %3A, so an
environment named Production:V1 produces
repo:OWNER/REPO:environment:Production%3AV1. The corpus had encoded that rule
on 2026-09-02 and recorded the open question beside it: nothing says % itself
is escaped. Probed on 2026-10-08 in a scratch repository: two environments were
created through the REST API, Production:V1 and Production%3AV1, and GitHub
listed them as two distinct environments with two ids. One job in each, same run.
Both minted the byte-identical sub. Only the environment claim, which carries
the raw name, and environment_node_id tell them apart.
So a trust policy correctly pinned to Production:V1 admits every job of its
twin - and GitHub documents that "anyone that can edit workflows in the
repository can create environments via a workflow file", with no protection rules
on the new environment. A required-reviewers bypass that no wildcard advertises.
- New:
gh-aws-environment-colon-twin-collides(dangerous). Mitigation stated in
the vector: pin AWS'senvironmentcondition key alongsidesub, or keep:
out of environment names. - Promoted on the same transcript:
gh-aws-environment-colon-encoded-exact,
gh-aws-environment-colon-literal-pin-denies.
job_workflow_ref stays name-based under immutable subjects (observed)
The same run answered the second open question. A repository created that day
minted the immutable owner@id/repo@id subject with no opt-in - the format
GitHub scheduled for 2026-07-15 - and in the same token job_workflow_ref,
workflow_ref and repository carried no ids at all, for a direct job and for a
workflow_call job alike. The immutable format changes sub and nothing else.
- New:
gh-aws-jwr-key-stays-name-based-under-immutable-sub(caution): a
job_workflow_refpin keeps every property of a name-based pin on an
immutable-subject repository. - Promoted:
gh-aws-immutable-subject-classic-pattern(the new-repository
observation).
A new observation method, github-actions-oidc-claims, records exactly what it
proves: the issuer side - what GitHub puts in the token. The AWS match of that
string stays documented, and every promoted vector's evidence says which half
is which. Four committed transcripts under observations/2026-10-08/ carry the
verbatim claim sets; the signed tokens were never printed. The guards in
tests/test_observations.py extend to the new method: each promoted vector's
subject context and claim shapes are checked against the committed token, and the
account-id scan exempts only the token's own check_run_id / run_id claims.
Microsoft falsified gitlab-azure-flexible 0.1.0. Corrected (0.2.0, one new vector)
That suite shipped on Microsoft listing sub as the only claim a GitLab flexible
federated identity credential may reference - hence a path-reuse squatter that
"no flexible expression can exclude". Microsoft's flexible-FIC page (ms.date
2026-09-18) now lists project_id (eq) for GitLab and says the expression "must
match the sub and project_id claims" when the sub is mutable. Its separate
"Mutable subjects" page (ms.date 2026-07-28) says sub plus one or more of
project_id / namespace_id / user_id, "regardless of whether sub starts with
project_path or project_id". Two Microsoft pages, two rules.
All five judgments are corrected (the lever exists; a sub-only expression on a
path-based sub no longer satisfies the documented rule), the immutable-gap tag
moves off the GitLab squatter vector (its id is kept stable for citations; the
catalog's example is now the Terraform Cloud vector, whose gap still holds), and
one vector is added: gl-flex-and-project-id-excludes-squatter (safe, no-match),
the same squatter token refused by and claims['project_id'] eq '20'. What the
two pages disagree on is recorded and asserted nowhere.
AWS's ten GitHub condition keys, graded (github-aws 0.7.0, six vectors)
AWS's condition-keys reference documents exactly ten GitHub-specific keys for role
trust policies, every one "Available in session: No". The corpus graded three; it
now grades all ten plus sub and aud.
- AWS's own example pins a GitHub handle, one row below the statement that actor
IDs are immutable:gh-aws-docs-actor-name-pin-recycled(dangerous),
gh-aws-actor-id-pin-survives-rename. - Three keys carry no repository at all.
refis the bare branch, so an exact
refs/heads/mainpin admits main of every repository registered against the
provider;workflowis the YAMLname:, chosen by whoever writes the file.
Wildcard-free, so a review grading by*objects to nothing:
gh-aws-ref-key-alone-omits-the-repository,
gh-aws-workflow-name-pin-attacker-chosen. repository_owner_idis immutable and org-wide - the breadth of
gh-aws-org-wide-wildcard-repowithout the wildcard that makes it visible:
gh-aws-repository-owner-id-alone-is-org-wide.- AWS's six-key example evaluated as a block:
gh-aws-all-docs-github-tab-example, held at caution.
New vocabulary tag: select-claim-key.
Immutable-format completeness (github-aws 0.8.0, three vectors)
Verified verbatim from GitHub's reference and changelog: repositories created
before 2026-07-15 keep the classic format unless they opt in; renames and
transfers after that date switch.
gh-aws-rename-flips-format-and-breaks-the-policy(caution): a routine rename
flips the subject format and the policy stops matching, with neither the
repository settings nor the policy edited.gh-aws-format-tolerant-wildcard-admits-prefix-orgs(dangerous): the obvious
repair,repo:octo-org*/octo-repo*:..., each*meant only to absorb an
optional@ID. IAM's*is not delimiter-aware, so the boundary becomes any
account starting withocto-orgholding any repo starting withocto-repo.gh-aws-immutable-custom-sub-keeps-the-ids(dangerous): the immutable twin of
the repo-only customized sub - still admits everything, now rename-proof.
Two GCP vectors claimed an error-free rejection, wrongly
gh-gcp-name-pin-breaks-on-rename said deployments stop "with no error";
gh-gcp-classic-sub-immutable-break said the credential is "silently rejected".
GCP refuses a credential whose attribute condition is false and says so
("Otherwise, the credential is rejected"; the response text is "The given
credential is rejected by the attribute condition"). What is silent is write
time, as on Azure, where the same wording was corrected on 2026-08-25 by a
one-off sweep phrased for Azure - which is how these survived it.
test_no_vector_claims_an_error_free_rejection now runs on every suite and was
checked against both the pre-fix GCP and the pre-08-25 Azure wording.
github-gcp 0.1.0 -> 0.1.1. No verdict moved.
Three corpus invariants, now tested
A self-consistency audit came back clean and found that none of its three checks
was being tested. Now in tests/test_vectors.py, each mutation-tested:
cross-referenced vector ids resolve; no two vectors disagree on an identical
token and condition; no vector is an exact duplicate of another.
Housekeeping
pyproject.tomldeclares[project.urls]. Nine releases had shipped with none,
so the PyPI page carried no link to the repository, the vectors or the issues.- README links that PyPI rendered as dead relative paths are absolute, and the
README now cites CVE-2026-82856 - another scanner shipping the set-operator miss
this corpus grades - as evidence that it is a bug class. scripts/coverage.pywrites the README with LF on every platform.- The upstream scoreboard is re-dated: four PRs open, none merged or reviewed by
a maintainer as of 2026-10-08. The three Checkov PRs stay open past the
previously recorded 2026-10-15 close-by; the measured reasoning is in
BACKLOG.md and ROADMAP.md.
v0.6.0 - the sweep closes
This release closes the last of the audit-sweep leads. Two findings: a defect in
the evaluator that decides whether a GCP vector passes, and a tranche covering a
GitHub encoding rule whose own documented example turns out to be an org-wide pin.
The CEL string decoder lost a character
cel.py decoded string literals with three sequential str.replace passes.
Checked against the CEL spec's own ESCAPE production, that is wrong three ways —
and the first is a wrong answer, not a limitation.
A double quote needs no escape inside a single-quoted string, so a literal holding
backslash-backslash-quote is legal and means backslash-quote. Replacing \\ → \
first and \" → " second made the second pass consume the backslash the first
had just written, silently returning a bare quote. In the oracle that decides
whether a vector passes, a lost character can make a vector pass for the wrong
reason.
Two more: ten escape forms were passed through as backslash-plus-character
(\n \t \r \a \b \f \v \?, backtick, \xHH, \uHHHH, \UHHHHHHHH, octal), and
an invalid escape raised nothing, so this evaluator would happily evaluate an
expression GCP itself refuses to save.
Fixed by scanning the literal once, left to right — the structural point being that
a single pass cannot re-read its own output, so no reordering of the replace chain
could have been correct. Raw (r-prefixed) literals are now supported, which is the
idiomatic way to write a regex inside matches(). Triple-quoted and bytes literals
get a refusal that names them.
No verdict moved. No shipped vector contains a backslash in a CEL expression.
Six of the seven new tests were confirmed to fail against the pre-fix decoder.
GitHub's %3A rule, and the example that ships an org-wide pin
GitHub's OIDC reference: "Any : within the metadata values will be replaced with
%3A in the subject claim." An environment named Production:V1 mints
repo:octo-org/octo-repo:environment:Production%3AV1.
Four vectors, because the interesting part is the chain, not the rule:
- The literal pin fails closed. Pinning the name as read off the GitHub UI
produces five colons against a subject with four. Nothing matches, every
deployment is refused, and the failure gives no hint of its cause — policy and
environment name look identical to a reader. - The obvious repair opens the door.
environment:Production*admits the
intended environment and every other one with that prefix. Protection rules are
configured per environment, so the compensating control the original pin leaned
on does not extend to whatever the wildcard picked up. The encoding surprise
causes the over-permission. - GitHub's own second example omits the repository. Printed as a condition
value to copy:"sub": "environment:production%3Aeastus:repository_owner:octo-org".
The encoding is correct and the pin never names the repository, so every repo in
the org with an environment of that name satisfies it. Wildcard-free, so a scanner
grading conditions by looking for*sees an exact pin and flags nothing.
New vocabulary tag percent-encoded.
Not shipped, recorded as a probe. The documented rule is a single-character
substitution, not percent-encoding — nothing says % is itself escaped. If it is
not, an environment named Production%3AV1 mints the same subject as one named
Production:V1, and a pin on either admits the other. GitHub documents no character
allowlist for environment names, so that is an inference, and this corpus does not
ship inferences.
One grade that stated its assumption as a fact
gh-aws-environment-subject-exact asserted the compensating control outright while
its sibling states the same thing as an assumption. The judgment catalog's rule for
the family is explicit — safe when protection rules exist, caution otherwise — so
the grade stays safe and the reason now names what it rests on.
Corpus
14 suites / 151 vectors; github-aws 0.6.0. Provenance unchanged at
11 observed. 389 tests.
Install
pip install subvectors==0.6.0
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
v0.5.2 - four findings, four sources
Four findings from an audit sweep, each verified against a primary source before
it was acted on. Two were errors in this corpus; one is an error in AWS's
documentation; one is a grammar bug that rejected a shape GitHub documents how to
mint.
The corpus was wrong about GitLab
It asserted that no ref_protected guard is expressible in a single-key sub
condition. It is. GitLab's ALLOWED_SUB_CLAIM_COMPONENTS
(app/models/project_ci_cd_setting.rb) is project_path, project_id, ref_type, ref, ref_protected, environment_protected, deployment_tier, configurable per
project through ci_id_token_sub_claim_components.
Two texts carried the false claim and are corrected. The sharper consequence is on
the Azure side: gl-flex-matches-any-branch concluded that because Azure flexible
FIC supports only the sub claim, no protection guard is possible. The Azure
limitation is real — the conclusion was not. Moving ref_protected inside the
sub routes around it. (pipeline_source has no such route; it is not an allowed
component, so that half of the claim stands.)
AWS's documented CircleCI policy does not work as written
AWS's OIDC condition-keys page shows, verbatim:
"oidc.circleci.com/org/12345:aud": "sts.amazonaws.com"CircleCI documents the same claim as "The audience. By default, this is
ORGANIZATION_ID, a string containing a UUID that identifies the job's project's
organization." Copy AWS's example, run a job with a default token, and the aud
StringEquals is false: the ANDed block fails and the credential is refused
however correct the project-id pin is.
Fails closed, so nothing is over-permitted — an availability break in a documented
example, silent at the STS call rather than at policy creation. New vector:
circleci-aws-docs-aud-contradiction-denies.
The grammar rejected a documented subject
parse_repo_segment required a trailing :, so repo:octo-org/octo-repo returned
None. GitHub's sub-customization docs give include_claim_keys: ["repo"] as the
template that "lets you grant cloud access to all the workflows in a specific
repository, across all branches/tags and environments" — which mints exactly that
bare form. Fixed; wildcards in the owner and repo positions are still rejected.
New vector gh-aws-repo-only-customized-sub-admits-everything grades it: a
wildcard-free StringEquals pin that admits every branch, tag, environment and
pull_request of the repository. The over-permission lives in what the subject
omits, so a scanner grading conditions by looking for * in the pattern sees
nothing to flag.
And one promise this repo could not keep
Found while fixing the above. github.py claimed any value containing * or ?
"is not a valid subject here and returns None". Only the owner and repo positions
exclude wildcards; repo:octo-org/octo-repo:* has always returned a concrete
segment. The promise was narrowed rather than the parser — a consumer grading the
corpus's most common dangerous pattern wants to know which repository it names.
Both behaviours are now pinned by tests.
Corpus
14 suites / 147 vectors; github-aws 0.5.2, circleci-aws 0.2.0. Provenance
unchanged at 11 observed. 378 tests.
Install
pip install subvectors==0.5.2
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
v0.5.1 - right check, real citations
A correctness release for the corpus's claims about a third party, plus one
vector that had been living as a sentence.
Checkov claims: right check, real citations
The corpus makes three claims about Checkov's GitHub-OIDC checks. All three were
re-read against checkov 3.3.16 / d8aec9dba before this release, and all three
were wrong in some way:
gh-aws-org-wide-wildcard-repois an AWS vector and attributed its claim to
CKV_AZURE_249— the Azure check. The AWS checks areCKV_AWS_358and
CKV_AWS_393. Both fall through to the samegh_repo_regexand pass
octo-org/*deliberately: the inline comments call it "a pass with a
warning", though no warning mechanism exists.- Two of the three claims carried no citation at all.
- The third pointed at a
mainblob path, which moves.
Each claim now carries a commit-pinned permalink with a line number — read out of
the pinned commit before it was written — and a version stamp.
az-fic-pull-request-danger also discloses that checkov#7627 proposes changing
the behaviour it describes, so the claim is explicitly version-scoped rather than
quietly perishable.
A measured claim with nothing to audit
The StringLike half of the case-sensitivity result existed only as a sentence
inside another vector's observation.evidence: measured on 2026-08-29, but with
no vector of its own and no transcript. That is the same shape that forced the
0.3.3 narrowing, so it is now gh-aws-stringlike-case-mismatch-rejected —
documented, cited, and in the harness default set, where one run will promote it
with a transcript like everything else. The observed vector's prose no longer
asserts the companion result on evidence this repository cannot show.
Corpus
github-aws suite 0.5.1, 40 vectors; 14 suites / 145 vectors overall.
Provenance unchanged at 11 observed.
Install
pip install subvectors==0.5.1
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
v0.5.0 - the set-operator tranche
ForAllValues: under an Allow fails open. AWS says not to use set operators
on single-valued context keys at all — this tranche shows what happens when you do.
The tranche
Seven vectors covering the ForAllValues: / ForAnyValue: qualifiers, plus
matcher support, a condition.qualifier schema property, and two new judgment
patterns. github-aws suite 0.5.0; the corpus is now 14 suites / 144 vectors.
AWS's own guidance is blunt:
Do not use condition set operators
ForAllValuesorForAnyValuewith
single-valued context keys. Using condition set operators with single-valued
context keys can lead to overly permissive policies.
Three results:
1. ForAllValues under an Allow fails open. It "also returns true if there
are no context keys in the request". So a trust policy scoped to a deployment
environment with ForAllValues:StringEquals admits every job that references no
environment — the ordinary case. ForAnyValue returns false on the same token, and
so does the plain operator. Same intent, same token, opposite verdict: the qualifier
alone decides whether an absent claim opens or closes the door.
2. Neither qualifier tightens a poisoned OR-list. ForAllValues reads as "the
subject must satisfy all the listed values", which would neutralise a loose value.
It does not. The qualifier ranges over the values the request supplies, not the
values in the policy — and a sub claim is single-valued. The policy's values are
still ORed and the loosest one still sets the boundary.
3. A correct aud pin does not rescue it. Context keys in a Condition block
are ANDed, but a clause that returns true on an absent claim contributes nothing to
a conjunction. The block ends up no stronger than its aud pin alone — and an aud
pin admits every repository on the issuer.
Matcher
condition.qualifier accepts ForAllValues or ForAnyValue on the AWS string
consumers. Set semantics are general: a list-valued claim gets real set evaluation,
not just the single-valued case. Python's all() / any() over an empty sequence
reproduce AWS's documented asymmetry exactly, so the implementation is three lines
and the comment is longer than the code.
Provenance
Unchanged at 11 observed, all in the AWS tranche, each linking a committed
transcript under
observations/.
The new vectors are documented, cited to AWS's single-vs-multivalued context keys
page and read against it before any were written.
Install
pip install subvectors==0.5.0
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
v0.4.2 - AWS validates only the first value
AWS's role-creation guardrail validates only the first value of a GitHub-OIDC
sub condition. A scoped-to-all * is invisible to it in any other position.
The finding
iam:CreateRole was probed five ways, every result recorded as a committed
transcript under
observations/:
Trust policy sub condition (StringLike) |
Result |
|---|---|
["repo:octo-org/octo-repo:ref:refs/heads/main", "repo:octo-org/*"] |
accepted |
["repo:acme/x", "*"] |
accepted |
["*", "repo:acme/x"] |
rejected |
["*"] |
rejected |
| no condition at all | rejected |
Rows 2 and 3 hold the same two values in opposite order and get opposite
verdicts. AWS is not scanning the list for a non-scoped value — if it were, row 3
would pass on its second element. It reads element [0] and stops.
All four rejections return the identical MalformedPolicyDocument, saying the
policy "must evaluate, using StringEquals, StringLike or StringEqualsIgnoreCase,
token.actions.githubusercontent.com:sub or ...:job_workflow_ref which is not
scoped to all". That message describes a stricter check than AWS performs: the
2023 condition-less class is closed at creation, while a * smuggled into any
non-leading position of an OR-list passes.
Why it matters: IAM evaluates multiple values of one condition key with a logical
OR, so the loosest value sets the trust boundary at authentication time
regardless of where it sits in the list. Creation-time validation and run-time
evaluation disagree about which values matter.
This is the same first-value-only defect the corpus fed upstream to Checkov
(#7665) — here in the cloud
provider's own validator.
Provenance
Unchanged from 0.4.0 at 122 documented / 11 observed; github-aws suite
0.4.2. The flagship vector gh-aws-multivalue-loose-value-poisons-list now carries
all five creation probes plus the policy-simulator evaluation in its evidence, each
naming its transcript.
Harness
scripts/observe_aws.py gained a collision guard: ad-hoc probe labels are derived
from a hash of (operator, values), --label names a run explicitly, and
write_transcript refuses to overwrite a transcript that records a different
probe. Re-running the same probe still overwrites. Found the hard way — two ad-hoc
probes collided on one filename during this session and the first result survived
only because it had already been committed.
Install
pip install subvectors==0.4.2
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0. (0.4.1 exists in git history only.)
v0.4.0 - transcript-backed observations
The AWS tranche is now transcript-backed, and AWS's own creation-time guardrail
turns out to be shallower than its error message implies.
Every observed vector now carries its raw record
observation.evidence is prose, and prose loses the detail you did not know
mattered. This release adds an optional observation.transcript to the schema
and a committed transcript for every observed vector, under
observations/ —
the exact request document, the verbatim response, the argv, the CLI version and
the derived verdict. A reader can now audit any observed vector without an AWS
account and without re-running anything. Account IDs are scrubbed before
anything is written.
Provenance: 122 documented / 11 observed (was 127/6). github-aws at
suite version 0.4.0.
The finding: AWS's creation guardrail checks existence, not scope
iam:CreateRole, probed three ways on 2026-08-31 with the condition operator
recorded (all transcripts committed):
Trust policy sub condition |
Result |
|---|---|
StringLike ["repo:octo-org/octo-repo:ref:refs/heads/main", "repo:octo-org/*"] |
accepted |
StringLike ["repo:acme/x", "*"] |
accepted |
| no condition at all | rejected — MalformedPolicyDocument |
The rejection message says a GitHub-OIDC trust policy must evaluate
...:sub or ...:job_workflow_ref "which is not scoped to all". But under
StringLike a bare * value is scoped to all — and AWS accepted it as one
value of an OR-list. So the creation-time guardrail checks that a sub condition
exists, not that its values are scoped. The 2023 condition-less class is
closed at creation; the multi-value loose-value hole is open.
IAM evaluates multiple values of one condition key with a logical OR, so the
loosest value sets the trust boundary regardless of how tight its neighbours are.
Newly observed
Five vectors promoted from documented, all confirmed on the same run:
gh-aws-pull-request-subject, gh-aws-branch-prefix-collision,
gh-aws-single-char-wildcard, gh-aws-exact-branch-stringlike, and
gh-aws-stringequals-treats-star-literally — the cleanest operator-semantics
pair in the corpus, since repo:octo-org/* admits an entire organization under
StringLike and matches nothing at all under StringEquals.
The six already-observed vectors were re-run and re-evidenced against
aws-cli/2.36.33, so the whole AWS tranche rests on one dated, reproducible
session.
Harness
scripts/observe_aws.py
now writes a transcript for every call — success, disagreement or error — and
covers the role-creation probe. That probe refuses to run on an implied
condition operator: it is either derived from the named vector or passed
explicitly, and either way it lands in the transcript. It confirms before
creating anything, always deletes the role, and records the get-role
verification. 25 offline tests cover everything but the network hop.
Install
pip install subvectors==0.4.0
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
v0.3.0 - first observed vectors
The first observed vectors reach PyPI.
0.2.1 shipped on 2026-08-25, five days before the first observed promotion — so
pip install subvectors has been serving a corpus with zero observation blocks
while the README claimed live-AWS provenance. This release closes that gap.
Provenance
Six vectors moved from documented (derived from primary documentation) to
observed (recorded from a live exchange), each carrying an observation block
with method, date, evidence and tool version. All six were recorded against live
AWS via the IAM policy simulator on 2026-08-29/30, aws-cli/2.36.33:
gh-aws-case-mismatch-rejectedgh-aws-org-wide-wildcard-repogh-aws-repo-wide-wildcard-suffixgh-aws-ref-wildcard-spans-nested-branchgh-aws-branch-wildcard-zero-width—*matches the empty string, so
...refs/heads/main*admitsmainitself as well asmain2. Previously an
interpretation of "any combination of characters"; now measured.gh-aws-multivalue-loose-value-poisons-list— confirmed twice: the policy
simulator returnsallowedfor an unrelated repo'spull_requestsubject
against a two-value list whose second value is org-wide, andiam:CreateRole
accepts a trust policy carrying that list (and one carrying a literal*
among its values) while rejecting a condition-less policy with
MalformedPolicyDocument. AWS's creation-time guardrail blocks the 2023
sub-less class but does not inspect the other values of an OR-list.
Current split: 127 documented / 6 observed.
Corpus
14 suites — 133 vectors across 5 issuers (github, gitlab, bitbucket,
circleci, terraform-cloud) and 6 consumer semantics (aws-stringlike,
aws-stringequals, aws-all, azure-fic-exact, azure-fic-flexible,
gcp-cel). github-aws is at suite version 0.3.2.
Also in this release: docs/OBSERVED-PROMOTION.md, the runbook defining what
observed requires and how to promote a vector, plus scripts/observe_aws.py,
which drives corpus vectors through aws iam simulate-custom-policy.
Install
pip install subvectors==0.3.0
The wheel ships the entire corpus. The vectors are CC0; everything else is
Apache-2.0.
subvectors 0.2.1
subvectors 0.2.1 - corpus truth fix
Corrects the classic-Azure-FIC claim that a subject mismatch fails the
token exchange with no error. Creation is unvalidated; the exchange
returns AADSTS700213. Provenance (133 documented / 0 observed) is now
generated into the README instead of asserted, and CI gates it.
subvectors 0.2.0
subvectors 0.2.0 - the corpus ships in the package
First PyPI release. The wheel carries the entire vectors/ tree (14 suites,
133 vectors, JSON Schema, CC0 corpus LICENSE) plus the zero-dependency
reference matcher and the new subvectors.corpus loader. Consumers pin a
versioned corpus artifact instead of vendoring JSON by hand.