Autonomy matrix (generated)
A1. Tier decision (no deployment block)
ci = host CI green; verdict = TFactory verdict; val = achieved/floor VAL; parity = CI parity (T = true, F = false).
| tier | result | when |
|---|---|---|
(blank) | hold-blocking | always |
async | hold-async | always |
auto | auto-merge | ci=T verdict=pass val=2/1 parity=T |
auto | hold-async | otherwise |
blocking | hold-blocking | always |
bogus | hold-blocking | always |
hard | hold-blocking | always |
low | auto-merge | ci=T verdict=pass val=2/1 parity=T |
low | hold-async | otherwise |
medium | hold-async | always |
A2. Deployment overlay (applied before the tier; all-green low change)
Required gates: human-approval, security-scan.
| block reasons | result | inputs |
|---|---|---|
| (none) | auto-merge | 9 combos, e.g. risk=medium prod=staging satisfied=human-approval,security-scan |
| deployment risk_class=high is never auto-merged | hold-blocking | 3 combos, e.g. risk=high prod=staging satisfied=human-approval,security-scan |
| deployment risk_class=high is never auto-merged production change is VAL-4 / never autonomous (human-approval required) | hold-blocking | 1 combos, e.g. risk=high prod=production satisfied=human-approval,security-scan |
| deployment risk_class=high is never auto-merged production change is VAL-4 / never autonomous (human-approval required) required system gate 'human-approval' is not satisfied (only a human can clear it) | hold-blocking | 1 combos, e.g. risk=high prod=production satisfied=security-scan |
| deployment risk_class=high is never auto-merged production change is VAL-4 / never autonomous (human-approval required) required system gate 'human-approval' is not satisfied (only a human can clear it) required system gate 'security-scan' is not satisfied | hold-blocking | 1 combos, e.g. risk=high prod=production satisfied=- |
| deployment risk_class=high is never auto-merged required system gate 'human-approval' is not satisfied (only a human can clear it) | hold-blocking | 3 combos, e.g. risk=high prod=staging satisfied=security-scan |
| deployment risk_class=high is never auto-merged required system gate 'human-approval' is not satisfied (only a human can clear it) required system gate 'security-scan' is not satisfied | hold-blocking | 3 combos, e.g. risk=high prod=staging satisfied=- |
| production change is VAL-4 / never autonomous (human-approval required) | hold-blocking | 3 combos, e.g. risk=medium prod=production satisfied=human-approval,security-scan |
| production change is VAL-4 / never autonomous (human-approval required) required system gate 'human-approval' is not satisfied (only a human can clear it) | hold-blocking | 3 combos, e.g. risk=medium prod=production satisfied=security-scan |
| production change is VAL-4 / never autonomous (human-approval required) required system gate 'human-approval' is not satisfied (only a human can clear it) required system gate 'security-scan' is not satisfied | hold-blocking | 3 combos, e.g. risk=medium prod=production satisfied=- |
| required system gate 'human-approval' is not satisfied (only a human can clear it) | hold-blocking | 9 combos, e.g. risk=medium prod=staging satisfied=security-scan |
| required system gate 'human-approval' is not satisfied (only a human can clear it) required system gate 'security-scan' is not satisfied | hold-blocking | 9 combos, e.g. risk=medium prod=staging satisfied=- |
A3. VAL floor semantics
| achieved | floor | meets floor |
|---|---|---|
2 | None | True |
2 | '' | True |
2 | 'VAL-three' | False |
1 | 2 | False |
2 | 2 | True |
3 | 2 | True |
None | 2 | False |
'VAL-2 (3 lanes)' | 2 | True |
A4. Path floor
High-risk patterns: auth, login, session, password, secret, credential, token, oauth, iam, rbac, permission, migration, alembic, schema.prisma, terraform, .tf, dockerfile, docker-compose, k8s, kubernetes, helm, .github/workflows, ci.yml, deploy, payment, billing
| changed path | floor |
|---|---|
auth | blocking |
login | blocking |
session | blocking |
password | blocking |
secret | blocking |
credential | blocking |
token | blocking |
oauth | blocking |
iam | blocking |
rbac | blocking |
permission | blocking |
migration | blocking |
alembic | blocking |
schema.prisma | blocking |
terraform | blocking |
.tf | blocking |
dockerfile | blocking |
docker-compose | blocking |
k8s | blocking |
kubernetes | blocking |
helm | blocking |
.github/workflows | blocking |
ci.yml | blocking |
deploy | blocking |
payment | blocking |
billing | blocking |
README.md | auto |
| (no changed files) | auto |
B. Live wiring (pr_endgame)
B1. Blank tier vs low (all-green signals)
| reviewTier | disposition |
|---|---|
None | auto-merge |
'' | auto-merge |
'low' | auto-merge |
| check | result |
|---|---|
| blank == low | True |
B2. Signals when nothing was measured
| signal | default |
|---|---|
achieved_val | 0 |
ci_parity | True |
host_ci_green | False |
tfactory_verdict | None |
val_floor | None |
B3. Flags
| flag | env var | unset | set to "1" |
|---|---|---|---|
| auto-merge | AIFACTORY_AUTO_MERGE | False | True |
| path-floor enforcement | AIFACTORY_PATH_RISK_FLOOR_ENFORCE | False | True |
B4. Path floor on a low task touching auth: (effective tier, floor)
| enforcement | effective tier | floor |
|---|---|---|
| advisory (unset) | 'low' | 'blocking' |
enforced (1) | 'blocking' | 'blocking' |
B5. Deployment overlay on the live path (low task, non-risky diff)
| deployment | enforce | effective tier | floor | live disposition |
|---|---|---|---|---|
| production_classification=production | unset | 'low' | 'blocking' | hold-blocking |
| production_classification=production | 1 | 'blocking' | 'blocking' | hold-blocking |
| risk_class=high | unset | 'low' | 'blocking' | hold-blocking |
| risk_class=high | 1 | 'blocking' | 'blocking' | hold-blocking |
| system_gates=['human-approval'], satisfied_gates=['human-approval'] | unset | 'low' | 'blocking' | hold-blocking |
| system_gates=['human-approval'], satisfied_gates=['human-approval'] | 1 | 'blocking' | 'blocking' | hold-blocking |
Defaults shown; per-project settings and per-deployment env override them.
B2. Child-process git config pins
| key | pinned value |
|---|---|
core.hooksPath | /dev/null |
core.fsmonitor | false |
protocol.ext.allow | never |
core.sshCommand | ssh |
core.pager | cat |
core.editor | true |
core.askPass | (empty) |
credential.helper | (empty) |
credential.https://github.com.helper | !gh auth git-credential |
sequence.editor | true |
GIT_PAGER (env default) | cat |
C. Gate determinism (static import closure)
label = the gate module's own transitive imports; package __init__ side effects are reported separately in the next column.
| gate | label | model clients imported | package init reaches a model client | local modules | via spawn |
|---|---|---|---|---|---|
merge.merge_policy | deterministic | - | yes (1): apps/backend/merge/__init__.py; reaches anthropic, claude_agent_sdk, core.client, core.simple_client, openai, providers | 2 | - |
server.services.pr_review_service | model-assisted (dynamic import: core.auth:364 (+2 more)) | anthropic, claude_agent_sdk, core.client, core.simple_client, openai, providers | yes (15): see JSON gates[].init_reach; reaches anthropic, claude_agent_sdk, core.client, core.simple_client, openai, providers | 277 | runners/github/runner.py |
server.services.pr_endgame | model-assisted (dynamic import: core.auth:364 (+3 more)) | anthropic, claude_agent_sdk, core.client, core.simple_client, openai, providers | yes (21): see JSON gates[].init_reach; reaches anthropic, claude_agent_sdk, core.client, core.simple_client, openai, providers | 374 | - |
Declared, unverified (cross-repo, not probed):
| repo | gate |
|---|---|
| PFactory | plan/review/gates.py::run_gates |
| TFactory | apps/backend/agents/evaluator.py::_run_evaluator_session |
pr_endgame's review verdict is injected, not computed there; the review component is the separate pr_review_service row.
D. Control objectives
| control | objective | frameworks | evidence | claim |
|---|---|---|---|---|
gate.merge.merge_policy | The merge decision itself is deterministic: its own import closure reaches no model client. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/backend/merge/merge_policy.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
gate.server.services.pr_endgame | The endgame that applies the merge decision imports model clients; the review verdict it consumes is injected, not computed there. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 SA; NIST 800-53 RA | apps/web-server/server/services/pr_endgame.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
gate.server.services.pr_review_service | The review that feeds the merge verdict is model-assisted, and the page says so; it runs as a spawned runner whose model clients are found by following the declared spawn edge. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 SA; NIST 800-53 RA | apps/web-server/server/services/pr_review_service.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
policy.overlay | A high-risk or production deployment block, or an unsatisfied required system gate, holds the change for blocking approval before the tier is consulted; it can only tighten the decision. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; SOX ITGC change management; SOX ITGC segregation of duties; NIST 800-53 CM | apps/backend/merge/merge_policy.py | - |
policy.path_floor | A change touching auth, secrets, migrations, infrastructure or CI paths is floored at blocking review, so a change cannot declare itself routine. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; SOX ITGC change management; NIST 800-53 CM | apps/backend/review_tier.py | - |
policy.tier.hard | A hard-tier change is never auto-merged; it is held for blocking human approval, which separates the author from the approver. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; SOX ITGC change management; SOX ITGC segregation of duties; NYDFS 500.3; NIST 800-53 CM | apps/backend/merge/merge_policy.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
policy.tier.low | A low-tier change auto-merges only when host CI is green, TFactory passed, the achieved VAL meets the floor and CI parity holds; otherwise it is held for async review. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; SOX ITGC change management; NIST 800-53 CM | apps/backend/merge/merge_policy.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
policy.tier.medium | A medium-tier change is never auto-merged; it is always held for asynchronous human review. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; SOX ITGC change management; NIST 800-53 CM | apps/backend/merge/merge_policy.py | Enforced by branch protection for non-admin merges; the fleet's merge paths do not use admin bypass, but GitHub does not prevent it while enforce_admins is false (measured 2026-10-07, Factory#943). |
policy.val_floor | A change whose achieved Verification Assurance Level is missing or below its declared floor is not auto-merged, and an unreadable floor fails closed. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/backend/merge/merge_policy.py | - |
sandbox.git_config_pins | Every child process the server spawns, and the web terminal, runs git with command-scope pins that override the repo config keys an agent could use to run a program (hooks, fsmonitor, ext transport, ssh, pager, editor, askpass, credential helper, sequence editor). | ISO 27001:2022 A.8.28; SOC 2 CC6.8; NIST 800-53 SI | apps/backend/core/child_env.py | - |
wiring.auto_merge_flag | Auto-merge is off by default and is enabled only by an explicit per-project or per-deployment setting. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/web-server/server/services/pr_endgame.py | - |
wiring.blank_tier | A task with no recorded review tier is decided as low, so the live merge path treats a blank tier exactly as the policy treats low. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/web-server/server/services/pr_endgame.py | - |
wiring.live_overlay | On the live merge path a high risk or production deployment holds the merge for a human regardless of the path-floor flag; a required human-approval gate clears only on a GitHub approval of the head commit by someone other than the PR author or a bot; an unreadable contract holds it too. The PR author is the GitHub App bot and no PAT is in the server pod. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/web-server/server/services/pr_endgame.py | Accepted residual risks (#1671): non-Claude runners and the GitHub MCP server still receive the App token; PAT copies in project .env files and the UI settings token are not removed by this change; a stored per-project clone PAT is accepted (#1703): it is read only at project add for the clone, through GIT_ASKPASS, never pushes and stays encrypted at rest; projects added without one clone with the App token, and App-token clones for stored credentials are #1700. |
wiring.path_floor_flag | The path-derived risk floor is advisory by default for tasks with a signed contract and changes their merge tier only when enforcement is explicitly switched on; for tasks without a signed contract (legacy and from-issue) it always decides the merge tier. | ISO 27001:2022 A.8.32; SOC 2 CC8.1; NIST 800-53 CM | apps/web-server/server/services/pr_endgame.py | - |