RFC-0007 — Access Discovery & Authenticated-Test Provisioning

Status: Implemented · Created: 2026-06-15 · Updated: 2026-06-17 · Extends: RFC-0002 (task contract), RFC-0005 (provisioning), RFC-0006 (assurance levels), RFC-0001a (evidence gates) · Affects: PFactory, AIFactory, TFactory, CFactory RFC-0005 said provision the toolchain. RFC-0006 said declare how much was proven, never overclaim. This RFC fills the gap between them: discover what access a feature needs to be tested, curate that access (human-verified when it must be), and let the assurance ladder stay honest when access cannot be obtained. The single rule carried forward: we never claim something is tested when the access to test it was never available.

1. The problem — the access ceiling

RFC-0006 names the verifiability ceiling and notes VAL-3 “needs a disposable target + credentials”. It never says how those credentials are discovered, obtained, or refused. That is this RFC.

A feature is frequently un-testable not because we lack a toolchain (RFC-0005) but because we lack access: a test must reach an authenticated API, a cloud account, a database, or a login-gated web app — and that gate is often MFA / 2FA, which exists precisely to stop non-interactive automation from logging in.

Two failure modes must both be avoided:

  1. Silent under-test — the pipeline lints the code, can’t reach the real target, and reports it as done. (RFC-0006’s cardinal sin.)
  2. Unsafe over-reach — the pipeline is handed broad production credentials and a way to bypass MFA, and tests against something it can damage.

The honest path is neither. It is to classify the access, route each class to a non-interactive mechanism where one legitimately exists, require a one-time human-verified bootstrap where it doesn’t, and declare the residue un-automatable — letting the VAL gate record exactly how far testing got.

2. The reframe — you do not “solve” MFA, you classify access

MFA is a control designed to block non-interactive login. A pipeline that “solves” it is either storing a bypass or scraping a human’s session — both lose credibility. So we do not try. Instead every resource a test needs is sorted into one of four access classes, and each class has a defined route:

Class What it is How the pipeline authenticates MFA in path? Human role
A — Machine-native Cloud workload identity / OIDC federation (GitHub Actions to AWS/GCP/Azure), service accounts, scoped API tokens Federated short-lived credentials; no password, no MFA prompt No None
B — Bootstrap-once, machine-reused OAuth device-code / refresh tokens; a TOTP seed held as a secret (codes generated in-process); a captured browser storageState session Human clears MFA once at bootstrap; secret stored and refreshed; pipeline reuses Cleared once One-time bootstrap
C — Ephemeral target (the default for VAL-3) A throwaway resource the pipeline owns: LocalStack / testcontainers, an ephemeral Keycloak realm, a disposable cloud project No production credential at all — provision, seed a test identity, tear down None None (cost-guarded)
D — Un-automatable Push-approval, hardware key (WebAuthn / FIDO2), SMS or email code sent to a person Cannot, and must not, be faked Hard block Assisted run, or refuse

Design consequences, stated plainly:

3. Where it plugs in — discover, curate, test, gate

spec
  |
  v
PFactory  access-discovery  (new planning step)
  |   per target, emit: { resource, auth_class A|B|C|D, credential_ref, bootstrap: human|none }
  v
Task Contract  $defs.access   (new block, sibling of RFC-0005 environment)
  |
  v
Curation gate  (human-verified WHEN bootstrap = human)
  |   device-code login | store TOTP seed | approve sandbox account
  |   recorded in the audit chain (RFC-0001a); on success flips the
  |   "credentials" / "sandbox_target" capability into the `available` set
  v
TFactory  maps access -> existing .tfactory.yml credential refs; runs egress-gated lanes
  |
  v
verification_gate  VAL-3 achievable ONLY if access was curated; else not_run + honest reason

The leverage point: the honest downgrade already exists. RFC-0006’s verification_profiles.py marks VAL-3 requires: ["sandbox_target","credentials"], and verification_gate.py already downgrades VAL-3 to not_run with a reason when those are absent from available. Discovery and curation therefore only have to decide whether those capabilities enter available — no component needs to learn a new way to be honest; the gate already is.

4. The contract block

A new optional top-level access block in the RFC-0002 task contract, populated by PFactory discovery and updated by the curation gate. Proposed $defs.access (additive; absence means “no external access required”):

"access": {
  "type": "object",
  "additionalProperties": false,
  "description": "RFC-0007 access requirements discovered at planning time. Absent => the task needs no external/authenticated resource. The curation gate sets curated/human_approval; TFactory consumes credential_ref; the VAL gate treats un-curated requirements as a VAL-3 not_run.",
  "required": ["requirements"],
  "properties": {
    "requirements": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["resource", "auth_class", "bootstrap"],
        "properties": {
          "resource":      { "type": "string", "description": "What is being reached, e.g. 'staging-api', 'sandbox-aws', 'keycloak-realm'." },
          "auth_class":    { "type": "string", "enum": ["A-machine-native", "B-bootstrap-once", "C-ephemeral-target", "D-un-automatable"] },
          "credential_ref":{ "type": ["string", "null"], "description": "Existing broker ref the test will use: env:NAME | vault:path#field | store:<id>. Never an inline secret." },
          "env_required":  { "type": "array", "items": { "type": "string" }, "description": "Env var names the auth needs at runtime." },
          "bootstrap":     { "type": "string", "enum": ["none", "human"], "description": "human => a one-time human-verified login/seed/approval is required before this is usable." },
          "curated":       { "type": "boolean", "description": "Set true by the curation gate once the credential/target is provisioned and validated. VAL-3 stays not_run until true." },
          "human_approval":{
            "type": ["object", "null"],
            "additionalProperties": false,
            "properties": {
              "approved_by": { "type": "string" },
              "approved_at": { "type": "string" },
              "scope":       { "type": "string", "description": "What was granted, e.g. 'sandbox account, read+deploy, auto-teardown'." }
            }
          },
          "mvp_note":      { "type": ["string", "null"], "description": "For class D: the honest reason testing cannot pass this gate autonomously." }
        }
      }
    }
  }
}

5. The honesty contract (carried from RFC-0006)

6. What already exists (reuse, do not rebuild)

This RFC is therefore mostly connective: a planning-time discovery step, a typed contract block, and a curation gate that flips capabilities — wired onto storage and a gate that already exist.

7. Phasing

8. Non-goals