This implements issue #50.
Executable rules live in the policy configuration.
The stable commit-status context is knowledge-model-policy.
The knowledge-model-policy status is a required status check on main, so
noncompliance blocks merging. The repository ruleset (protect-main, id
25528020) is the source of truth for that state and can change independently of
this page; re-read it instead of relying on the wording here:
gh api repos/<owner>/<repo>/rules/branches/main \
--jq '.[] | select(.type=="required_status_checks") | .parameters.required_status_checks'Observed on 2026-10-27: the ruleset is active and main lists exactly one
required context, knowledge-model-policy (integration id 15368). This
lightweight metadata check complements the four CI lanes; it adds no
device/campaign lane.
Success means declaration compliance only, not proven execution settings,
knowledge accuracy, independent review, Skill Eval or candidate promotion. The
workflow evaluates trusted code from main, so a green status reports only what
the PR declares; it says nothing about the PR's own code, its execution settings
or its results.
Never request private session logs, secrets or hidden reasoning. This policy is
prospective; do not reconstruct unknown identities or rewrite historical commits.
The exact protected scope is:
src/inferrsi/data/skill_library/**src/inferrsi/data/knowledge_packs/**src/inferrsi/data/repo_packs/**/knowledge/**docs/reference-matrix.md,docs/searchspace-matrix.mddocs/knowledge-base.md,docs/knowledge-operations.md,docs/knowledge-store.mdsrc/inferrsi/catalog.py,src/inferrsi/skill_catalog.py,src/inferrsi/knowledge_operations.pysrc/inferrsi/knowledge/catalog.py,src/inferrsi/knowledge/skill_catalog.py,src/inferrsi/knowledge/knowledge_operations.py, the implementations behind the three root compatibility shells above, which stay protected
Deletions and both rename paths count. Mechanical/generated changes have no
automatic exemption. Running a generator or obtaining an approved-model review
or signature does not replace material authorship. Other source files are not
blanket-protected. Other PRs receive the stable status as Not applicable.
Use exactly one fenced JSON block and disclose every material AI author:
{
"schema_version": 1,
"head_sha": "<current-full-head-sha>",
"human_only": false,
"material_ai_authors": [
{
"tool": "<actual-tool-or-agent>",
"model_id": "<exact-model-id-or-unknown>",
"effort": "<actual-effort-or-unknown>",
"role": "author"
}
]
}
For human-only work set human_only=true and material_ai_authors=[], explicitly
declaring no material AI involvement. Content review still applies. Mixed work
uses false and lists every material AI author. Reviewer/signing provenance stays
on the separate review record. Unknown values stay truthful and do not qualify.
Update head_sha after new commits. Missing/duplicate blocks or fields,
conflicting declarations and stale coverage fail. PR text is never shell input.
| Owner label | Canonical ID | Explicit aliases | Allowed effort |
|---|---|---|---|
| Claude Opus 5.5 | claude-opus-5-5 |
anthropic.claude-opus-5-5 (Bedrock) |
xhigh, max |
| GPT-6-Astra | gpt-6-astra |
None | xhigh, max |
| GPT-6.1 Sol | gpt-6.1-sol |
None | xhigh, max |
| GPT-6.1 Terra | gpt-6.1-terra |
None | xhigh, max |
IDs and effort support were checked on 2026-10-21 against official
Claude IDs,
Claude effort,
GPT-6 Astra and
GPT-6.1 Sol docs.
Actual client settings still need truthful declaration. OpenAI uses
reasoning.effort; Claude uses output_config.effort. Identity is resolved first,
then effort uses explicit set membership. Default, high, ultra and unknown
settings do not qualify. Aliases are never inferred. Suspension also denies aliases.
GPT-6.1 Terra's canonical ID and support for xhigh and max were checked on
2026-11-01 against its official model documentation.
The policy admits only those two efforts for Terra; the model's other supported
efforts do not qualify for knowledge authorship.
The approval owner is @Dong1017. Their own contributions require a separately
designated independent maintainer in trusted independent_approvers; none is
designated initially. A PR cannot designate or approve its own exception.
Retain truthful attribution and add an exception request to the same PR block:
"exception": {"paths": ["<every-protected-changed-path>"], "reason": "<specific-reason>"}An authorized maintainer posts a top-level PR conversation comment:
{
"schema_version": 1,
"pull_request": 123,
"head_sha": "<current-full-head-sha>",
"decision": "approve",
"no_material_involvement": true,
"approvals": [
{"kind": "exception", "paths": ["<every-protected-changed-path>"], "reason": "<exact-request-reason>"}
]
}
CI verifies the actual comment author. Copying a block into the PR body grants nothing. The approver declares no material involvement and cannot be the PR opener or a known commit author/committer. Account checks do not prove agent independence; truthful disclosure and independent designation remain required. Unlinked Git identities cannot be inferred by CI. PR number, head, exact scope (including applicable rename paths) and request reason must match. New commits require renewed approval. Exceptions cannot excuse malformed/missing attribution; explicitly unknown settings may be considered by the approval owner.
policy_paths classifies changes to this mechanism — the policy config, this
checker, the metadata workflow, the runner and this document — and reports them in
each result. Classification is not a gate: a policy-only change does not fail for
lack of a separate approval, and passing it is not a statement about content
quality. Nothing in this mechanism grants an exception: an approval of kind
policy-change, including historical comments, cannot excuse protected knowledge
changes or substitute for knowledge author admission. One comment may still carry
both kinds in approvals; only the exception kind is evaluated. Checks use the
target branch's policy, never the PR's proposed replacement.
Each authorized account's latest comment containing the approval fence replaces
its earlier declarations. Use decision: "revoke" to revoke its grants. Comment
edits/deletions trigger reevaluation; delete all applicable grants or post a
revocation if older grants also exist. Approval is admission metadata, not
independent content review or merge permission.
GitHub commit statuses belong to a SHA/context, not a PR. A head shared by multiple open PRs is ineligible, even if one PR declares compliant authorship or receives a PR-specific exception. The checker reads all open repository PRs, across target branches and API pages, and requires a unique head both before evaluation and before publishing its result. Close the extra PR or use a distinct commit before retrying. An exception cannot bypass head uniqueness. A closed peer no longer shares the head: the remaining open PR is evaluated on its own metadata and receives its own verdict. The checker reads the full open-PR collection once before evaluation and once before publication, with API pagination in both scans. Results are published only from that fresh scan, and only for a target that is still open against the policy branch with an unchanged head; applicable approvals are reread before publication. The collection request count scales with its page count, not page count times PRs.
A merged or closed PR that is no longer part of the sweep receives no new status; its earlier verdict stays historical. Publication never targets a captured head for work whose base advanced or whose head changed. One evaluation pass is followed by a publication pass that re-reads the target. If the head, base, body or approvals moved, or if head ownership changed because a peer closed, the checker reassesses once against the current metadata and the current open-PR collection before writing. A green is published only from a verified premise.
Once a target is confirmed open against the policy branch with a valid head, the
checker immediately publishes pending ("Admission evaluation in progress") to
that head before it assesses changed files, declarations, exceptions or
approvals. Confirming the target necessarily reads the PR body; what the
invalidation precedes is the evaluation of that evidence. A verdict from an
earlier run therefore cannot survive into a window where the current evidence
could reject it. If that pending write fails, the target's success path
stops: the result is invalidation-failed, no evaluation verdict is published,
and the runner retries. Nothing is written over the failed invalidation, because
that write would be the same operation that just failed.
The old result is replaced rather than deleted, so the status history keeps both
the superseded verdict and its replacement. A pending status is not a merge
signal; callers must read the latest status of the current head, not any earlier
entry.
GitHub keeps at most 1,000 statuses for one SHA and context. A write past that bound is rejected with a validation error rather than discarded, and the context name is compared case-insensitively. Because every run writes an invalidation and a verdict, an unchanged head would eventually stop accepting writes.
The checker therefore reads this context's history for the head before writing,
counting case-insensitively, and keeps a reserve of 8 statuses. At or below 990
recorded statuses it evaluates normally; 991 to 999 use the reserve handling
below; 1,000 is saturated. At 991 or above it publishes one budget-exhausted
failure instead of evaluating, so the newest entry is a failure rather than an
earlier success, and reports that a new head is required.
That verdict is terminal: a later sweep recognises it as its own and writes
nothing, so repeated sweeps do not consume the reserve. If that one write
fails transiently, the run reports publication-failed; the next sweep attempts
the same single write once more. HTTP 422 follows the terminal rejection handling
below instead.
A head can also carry statuses from other contexts, so the read is bounded by
both 50 pages and time. Time is split rather than shared as one absolute
deadline. Each target receives an equal part of what is left in the sweep,
capped at 30 seconds, so a target that finishes early returns its unused time
to the later ones. The sweep itself is bounded by 120 seconds per attempt, and
one 15-second reserve is withheld from the sweep so publication still fits after
the last read. Each history request is additionally capped by the share still
remaining. Exhausting the page bound or the target's share is reported as
history-unavailable, which publishes the failure immediately and lets the
remaining targets continue.
The former design gave every target one absolute 60-second deadline: targets are processed serially, so the first ones spent it and each later target then failed closed, and a whole-sweep retry replayed the same prefix.
Retries now carry progress. Each attempt writes the targets it settled to a plan file, and the next attempt inherits them instead of re-invalidating and re-publishing the same verdicts, so an exhausted tail cannot replace a verdict an earlier attempt already published. A carried target is trusted only after one cheap re-read shows it is still open against the policy branch at the same head, base, state and declaration. Any change to those falls back to full evaluation, and the plan is discarded outright when the trusted base differs, because a different base means different loaded code and policy.
Carrying is limited to settled verdicts: compliant, noncompliant and the two
capacity states. Work reported as rerunnable - unreadable evidence, a failed
write, an advanced or entered target - is never inherited, so a genuine failure
is retried and stays visible rather than being optimistically cached.
The premise stored with a verdict must be the one that produced it. A target reassessed during the publication pass is therefore stored with the snapshot, approval comments and ownership that reassessment used, not with the earlier evaluation's evidence; otherwise a changed-then-reverted approval would make the stored premise match again and the stale success would be reused. If the open-PR collection cannot be read, no target is carried, because a target read alone cannot prove it does not share its head.
At or above the cap nothing can be written at all. The run reports
budget-saturated without attempting a write. An earlier success may still be
the head's newest entry and can therefore still satisfy the required context:
this head is blocked from receiving new statuses, not necessarily blocked from
merging, and this checker cannot replace that entry. A new head is the only
recovery, and the result is not retried. If the history cannot be read, the
result is history-unavailable: the failure is published immediately rather than
in the publication pass, so a sweep that is cut off before reaching this target
still replaces the old verdict. If that write fails, the result is
publication-failed and the run retries.
Fail-closed invalidation is not universal. These paths are reported explicitly and must not be described as enforced:
| Situation | Behavior and limitation |
|---|---|
| The target PR cannot be read at all | Its head may already be merged, so no write is attempted. The result is unverified, the run fails, and an earlier green remains pending-free until a run can read the target |
The pending write itself fails |
Result is invalidation-failed (or terminal HTTP 422 rejection below); the earlier verdict still applies to the head and the run fails visibly |
| The final verdict write fails | Result is publication-failed (or terminal HTTP 422 rejection below); the head keeps the pending placeholder, which does not satisfy a required check |
| The PR is closed before publication | No verdict is written. If the target was still open at evaluation time it keeps the pending it received there, and the workflow reports success without a verdict for that head |
| The head advances before publication | No verdict is written. The superseded head keeps its pending; the new head has no status until a run covers it, and the run exits 2 for that retry |
| A merge attempt races the run | The check reads metadata, invalidates, evaluates and publishes without holding any lock. A merge authorized by the previous state can land between the last read and the status write |
| The head has used its reserved status capacity | Result is budget-exhausted: one failure is recorded there and no success is published. The entry is recognised on later sweeps, so nothing further is appended and the state is not retried. Only a new head resumes evaluation |
| The head is already at the 1,000-status cap | Result is budget-saturated: no write is possible, so an earlier success may still be the head's newest entry and this checker cannot replace it. The head is blocked from new statuses, not necessarily from merging; only a new head resumes evaluation |
| The status history cannot be read | The failure is published immediately rather than in the publication pass, so a sweep cut off before reaching this target still replaces the old verdict. If that write also fails, the head keeps the earlier verdict and the result is publication-failed, which retries unless it is a terminal HTTP 422 rejection below |
| GitHub has not yet dispatched the event | Enforcement cannot precede the event that triggers it. A commit or body edit is enforced only after a run for that change has published a result |
pending narrows the window in which an old success describes stale evidence. It
does not close the window between the last metadata read and the status write, and
it cannot act when GitHub supplies no event. Repository protection plus a required
check is what turns these states into an actual merge block.
flowchart LR
subgraph before["Before"]
A1["One 60 s deadline"] --> A2["Earlier targets spend it"]
A2 --> A3["Later targets: history-unavailable"]
A3 --> A4["Retry replays the same prefix"]
A4 --> A2
end
subgraph after["After"]
B1["120 s sweep bound"] --> B2["Share = remaining / targets left, max 30 s"]
B2 --> B3["Early target returns its unused share"]
B3 --> B4["Settled targets written to the plan"]
B4 --> B5["Retry re-checks the premise, then evaluates only what is left"]
end
When the trusted base advances mid-sweep, the checker returns a retryable result
promptly instead of re-evaluating against its own stale checkout. It exits 2,
and trusted tools/run_knowledge_model_policy.sh refetches main and runs the
checker again from the current code and policy. The runner is the single retry
owner with a bounded budget; after exhaustion the workflow run fails. See the
limitations table above rather than reading this as unconditional fail-closed
enforcement.
Each result carries kind alongside the status state:
kind |
Meaning |
|---|---|
compliant |
Declaration and approvals satisfy the trusted policy |
noncompliant |
Genuine rejection: declaration, approval or head eligibility (including a shared head) |
evaluator-error |
GitHub API or incomplete evidence; never a rejection |
unverified |
Compliance could not be published without unverified lifecycle or ownership metadata |
deferred |
Work could not be completed in this run; rerun required |
closed |
Target left the sweep; no status was written |
advanced |
The evaluated head was superseded; nothing is written to the new head |
publication-failed |
Status write failed; the verdict was not recorded |
invalidation-failed |
The earlier result could not be replaced by pending; no verdict was published |
history-unavailable |
The head's status history could not be read within its share of the sweep, so the capacity margin is unproven; an explicit failure replaces the old result immediately, before any other target is processed |
budget-exhausted |
The head has used its reserved status capacity; one failure is recorded there, no success is published and a new head is required |
budget-saturated |
Complete history reaches the status cap, or GitHub explicitly rejects status capacity; nothing further can be written, so an earlier success may remain the newest entry |
status-rejected |
GitHub rejected a status POST with HTTP 422 without an explicit capacity reason; the current invocation stops repeating it, remains failed, and does not claim the old status was replaced |
The final JSON summary counts each kind, lists the noncompliant_pull_requests, and
states the sweep execution as ok, failed or unfinished. Exit codes: 0
the sweep completed (any number of individual rejections are their own red
statuses), 1 an execution error, 2 unfinished work to rerun. A rerun never
overrides a recorded rejection. A rejection belongs to its own PR: one PR's
rejection neither greens nor fails another PR's status.
The workflow handles PR open/reopen/close/commits/body-or-base edits on any target
branch (a PR against another base can share a main PR's head), approval-comment
creation/edit/deletion, every main push and manual dispatch. All eligible triggers
share one serialized status writer. Ordinary main pushes and closed-PR events use
the bounded impact routing below; PR and approval-comment events select the
trigger and affected head peers. Policy changes, manual dispatch and unverified
routing evidence retain the full open main-target PR refresh. It checks out main,
executes only trusted code, installs no PR
dependencies and treats PR text/files as API data. Permissions are limited to
contents/PR/issues read and commit-status write. Statuses explicitly target the
PR head because pull_request_target runs against base code. Metadata and
approvals are reread before publication; changes fail closed and need a fresh run.
PR edits and status publication are not atomic: integrators still check the
current head and latest run.
The shared concurrency group belongs to the declaration job. It uses
queue: max
with cancel-in-progress: false: one writer runs while up to 100 jobs wait,
instead of each new merge event replacing the single pending job. This is bounded
queueing, not event deduplication: overflow can still cancel pending work, and
queued runs still refresh current trusted state rather than replaying old verdicts. Ordinary non-PR Issue
comments skip that job. A newly created, nonempty PR comment without the
knowledge-policy-approval marker also skips the job; every PR comment edit or
deletion remains eligible, including removal of an approval marker. Missing event
fields or an empty body are handled conservatively. The same eligibility expression
controls both the job condition and its concurrency group: skipped events use a
unique no-op group, never the status writer group. They therefore cannot replace
a pending legitimate refresh, regardless of GitHub's condition/queue evaluation order.
File pagination must exactly match changed_files; more than the GitHub files
API's 3,000-file limit fails visibly. Exception/policy approvals require complete
commit attribution within the PR commits API's 250-commit limit. API failures
never become compliant/non-applicable results. Current rules have no merge queue;
merge_group is explicitly unsupported. Validate a trusted integration before
enabling a queue with this required check.
HTTP diagnostics retain only the response status, a conservative category,
numeric rate-limit timing/remaining headers, a bounded GitHub request ID, and
fixed allowlisted validation resource/field/code values (never rejected values).
For HTTP 422, response_shape distinguishes read failure, invalid JSON, parsed
non-object bodies, missing/wrong-type error arrays, and entries without allowlisted
details. Only fixed type names, booleans and counts over at most 20 entries are
retained. read_limit_reached means the 8 KiB read bound was reached, not proof of
truncation; errors_sample_truncated describes only the parsed error-array sample.
These shape diagnostics do not identify the cause of an otherwise unknown 422.
Raw response messages, error arrays, request URLs and credentials are not logged.
A 403 alone is not proof of rate limiting; a 422 is a validation error, not proof
that the status-history cap was reached. Read requests may retry once when an
explicit rate-limit delay fits inside their remaining retry budget (at most 60
seconds). The wait is deducted before passing the remaining timeout to the retry.
The urllib timeout applies to socket operations, not a strict total wall-clock
deadline for reading the response.
Status POSTs are never retried by the transport. An HTTP 422 status rejection is
terminal only for the current runner invocation: status-rejected remains an
execution error, not declaration noncompliance or a successful invalidation.
An explicit recognized status-capacity response is budget-saturated: the fixed
full capacity message is recognized either directly in the errors string or in
a structured Status/custom array entry. The exact string variant with the
Validation failed: prefix is also recognized, based on the verified #203 sample;
this classification does not restore status capacity or replace an old remote status.
An arbitrary string, substring match,
count of 900, or unknown 422 is not capacity proof. Other transient write failures retain
the bounded outer retries. A fresh invocation may retry after remediation.
The previous remote status may remain green or pending when a write is rejected;
no failure status is claimed to have been published. Diagnostics and retry control
do not repair permissions, restore capacity, identify every unknown validation
cause, or guarantee that historical red checks clear.
An ordinary main push compares the event's before SHA to the current fetched
main SHA, not merely the event's after. A changed trusted input forces a full
refresh: the policy JSON, checker, shell runner, workflow, policy documentation,
or any additional declared policy_paths. New routing dependencies must join the
fixed input manifest in the checker; policy JSON cannot remove the fixed inputs.
When those inputs are unchanged, the router fetches Git objects (never checks out
or executes PR code), with a 60-second aggregate graph-operation budget. It requires
complete non-shallow ancestry and before to be an ancestor of current main. For
each fixed open PR head, exactly one identical merge base against both revisions
proves that its base-relative diff and contributor commit set are unchanged. That
PR is not reevaluated and receives no new status. A changed or ambiguous merge
base selects the PR; unchanged policy JSON alone is not enough, because another
merge can change the protected paths bound by an exception approval.
All open PRs, including other base branches, remain in the ownership scan. A shared head is never skipped by push-impact analysis. A closed-PR event selects the closed trigger (reported N/A, no new status) plus remaining open main-target peers on that same head. Other PR events select the trigger and open main-target peers sharing its event head, current head, or (for synchronize) previous head. Missing previous head evidence falls back to a full refresh. Approval-comment create/edit/delete events select the trigger and current-head peers; edits and revocations remain eligible. Retargeting outside main still refreshes main-target peers. The evaluator continues checking ownership against all open PRs, including other base branches. Unrelated PRs receive no history scan or status POST. This reduces duplicate writes; it is not a persistent cache or deduplication of repeated events on the same PR. Every selected PR still invalidates pending before assessment, and known saturated heads remain blocked until a supported recovery changes the underlying condition.
Missing objects, shallow/incomplete history, force pushes, malformed collections,
repository/branch mismatches, fetch timeouts and unverified impact fall back to a
full refresh. Main and open-PR snapshots are reread before and after routed work;
a race requests replanning rather than declaring an empty selection healthy.
No affected admissions means no new work for this event—not that every existing
status is valid, that a known 422 was repaired, or that historical red checks clear.
An affected blocked PR still fails honestly. Queue overflow/event loss remains a
platform limitation; manual dispatch retains the full reconciliation path.
All selected targets still use the single serialized writer. The runner makes up to three attempts when selected work is unfinished; a green triggering PR never stops those retries early. Full sweeps evaluate the triggering PR first without dropping peers. Impact-routed results describe only the selected scope, not global admission health.
Only after the final attempt does a PR or PR-comment job use that triggering PR's
own verified result for its job conclusion. A pull_request_target result must
match the immutable event head; rerunning an old event cannot turn its historical
head green using a newer head's compliance. Missing/malformed event heads and
advanced heads fail closed with an explicit stale-event reason. PR comments lack
a head snapshot and retain current-head semantics. A peer PR's history/publication error
therefore does not turn a verified compliant trigger PR red. A noncompliant
trigger PR, unavailable evidence, status-publication failure, capacity failure,
advanced head, or missing target still fails its own job. A freshly checked
closed/non-policy-branch target is explicitly not applicable, not an admission
or acceptance PASS. It is added to the sweep even when absent from the open list.
Push-to-main jobs report reconciliation execution for the explicitly displayed
scope, separately from unresolved PR admissions. A complete, verified push report
may finish complete-with-findings when its only problems are confirmed status
capacity (budget-exhausted or budget-saturated) or noncompliant declarations.
This is not admission success or a claim that a PR is blocked from merging: an
older remote success can remain visible when capacity prevents replacement.
Per-PR statuses and raw sweep errors remain unchanged, with unresolved PR numbers,
kind counts and a warning in the final summary. Impact scopes are not global health
assessments; policy-input changes still reconcile the full open-PR set.
flowchart LR
A[Serialized final attempt] --> B[Per-head admission statuses unchanged]
A --> C[Bound report and complete target enumeration]
C --> D[Push reconciliation execution]
D --> E[Capacity findings visible, not admission success]
C --> F[PR own result or manual global result unchanged]
Capacity is established only by a complete history read or a recognized GitHub
status-capacity response, with typed provenance retained through retries. A prior
history, authorization, network or evaluator error cannot be hidden by a later
capacity rejection. Unknown 422 responses and all other execution errors still
fail the push job. Projection requires complete enumeration and one result for
each selected target; newly appearing full-sweep targets, incomplete enumeration,
missing provenance and missing/stale/malformed reports cannot yield success.
Manual workflow-dispatch, explicit CLI --pull-request, dry-run and trigger-PR
semantics are unchanged, including their capacity failures.
The runner deletes the report before each attempt. The checker atomically writes
a report bound to repository, event digest/type, policy SHA and attempt identifier;
the final consumer rejects missing, malformed, duplicate, stale or mismatched
reports. It never falls back to an earlier attempt's success. Failed collection
reads additionally produce synthetic target 0 in reporting sweeps (included in
result counts), which blocks projection even if an individual result is green.
All target results and global health remain in structured logs. The final scope, target kind, policy SHA and global summary are also written to the job summary; an incomplete/failed sweep emits a warning even when the trigger PR job passes. This isolates unrelated job conclusions without clearing any peer's failing commit status or hiding repository-wide failures. No admission rules, publishing permissions, status-history limits, or acceptance requirements are weakened.
The bootstrap PR could not exercise a target-branch workflow that had not landed: it was independently reviewed and accepted before merging, then validated on real PRs. #50 closed as completed on 2026-10-26 and the required-check enforcement described under Rollout and scope is now active. Local or PR CI success still proves nothing about a specific PR's declarations, since only the main-branch workflow's own status does.
python -m pytest tests/test_knowledge_model_policy.py
python -m ruff check tools/check_knowledge_model_policy.py tests/test_knowledge_model_policy.py
python tools/check_knowledge_model_policy.py --repository <owner/repo> --pull-request <number> --policy-sha <base-sha> --dry-run--dry-run reads public metadata and reports the local candidate's result without
publishing statuses. It does not show the main workflow is operational. Production
uses runtime-injected GITHUB_TOKEN; never pass tokens as CLI arguments. The
existing ready lane runs unit tests on Python 3.10 and 3.11 in its full suite.