Skip to content

feat(compliance): vendor + subprocessor register with usage reconciliation (CC9.2) - #210

Merged
TinDang97 merged 1 commit into
mainfrom
add/vendor-subprocessor-register
Aug 14, 2026
Merged

feat(compliance): vendor + subprocessor register with usage reconciliation (CC9.2)#210
TinDang97 merged 1 commit into
mainfrom
add/vendor-subprocessor-register

Conversation

@TinDang97

Copy link
Copy Markdown
Collaborator

R8 soc2-groundwork — task 3 of 8: vendor + subprocessor register (CC9.2)

A read + report-only governance tool that produces an auditable subprocessor register: every declared third-party processor of customer data, classified documented / incomplete / dpa_expired / unusedand reconciled against what the system actually reaches.

The anti-theater spine

A register that is only a hand-maintained doc silently drifts from reality. build_register cross-checks the declared list against a live UsageSource, so a processor the system reaches but that is absent from the declared register is a HARD finding — never a silent omission. Declaring a processor (including non-customer-data infra) is what suppresses that finding.

Design (mirrors the CC8.1 change-evidence #208 and CC6 access-review #209 tools)

  • Pure core build_register(source, declared, *, now_iso) over a UsageSource port. now_iso injected → identical declared + usage + now yields byte-identical output. Driven by a zero-network fake in the suite.
  • IO adapter ConfigUsageSource enumerates live usage from the deploy surface (provider registry, egress allow-list, storage config) — not the network. A partial/unreadable source raises IncompleteFetch so the core never reconciles against an incomplete usage set (which would hide an undeclared processor). Live enumeration is a documented NotImplementedError the operator wires.

Fail-closed classification (10/10 red-first tests)

Guard Rule
M4 · R:UNDECLARED_PROCESSOR A live-reached processor absent from the register is a hard finding, counted, never dropped
M3 · A4 · R:ASSUMED_COMPLIANT Missing required field or DPA != signedincomplete, never documented
M5 DPA expired when now_iso >= dpa_expiry — computed against injected now, never "valid" by omission
A2 · E6 A non-customer-data processor carries no DPA burden — listed for reconciliation, not risk-classified
M6 · R:FABRICATED_SIGNOFF Review record stays unreviewed — draft until a real human signs
M1 · R:TOOL_WRITES_REGISTER No write path — the port's only method is the read processors; the tool never edits subprocessors.json

scripts/soc2/ is repo-governance tooling, not part of the gateway wheel. subprocessors.json ships with placeholder DPA dates to be verified by a human as vendor agreements are signed — the tool correctly flags the current OpenAI/Anthropic/Bedrock entries as incomplete (DPA pending) until then.

ADD gate PASS (contract sha256:a593b6e1); ruff clean.


⚠ Four-eyes disclosure

This PR still owes a genuine second human reviewer. Any byte-approval applied to merge it is operator-directed, disclosed as NOT independent four-eyes, and added to the auditor sample-set tally — it does not satisfy CC1.4/CC8.1 separation of duties. Closing that gap is R8 independent-review-control, blocked until Tin recruits a real second reviewer.

…ation (CC9.2)

R8 soc2-groundwork task 3 of 8. A read+report-only tool that produces an
auditable subprocessor register: every declared third-party processor of
customer data, classified documented / incomplete / dpa_expired / unused —
AND reconciled against what the system actually reaches.

The reconciliation is the anti-theater spine: a register that is only a
hand-maintained doc silently drifts from reality, so build_register cross-checks
the declared list against a live UsageSource. A processor the system reaches but
that is absent from the declared register is a HARD finding, never a silent
omission (M4, R:UNDECLARED_PROCESSOR) — declaring a processor (incl. non-
customer-data infra) is what suppresses that finding.

Design (mirrors the CC8.1 change-evidence and CC6 access-review tools beside it):
- PURE core: build_register(source, declared, *, now_iso) over a UsageSource
  port. now_iso is INJECTED (no clock), so identical declared + usage + now
  yields byte-identical output. This is the layer the suite drives.
- IO adapter ConfigUsageSource is the only thing that enumerates live usage,
  derived deterministically from the deploy surface (provider registry, egress
  allow-list, storage config) — NOT the network. A partial/unreadable source
  raises IncompleteFetch so the core never reconciles against an incomplete
  usage set (which would hide an undeclared processor). Live enumeration is a
  documented NotImplementedError the operator wires.

Fail-closed classification (all red-first tested, 10/10):
- A vendor missing a required field or a signed, in-window DPA is never
  documented — it fails closed to incomplete (M3, A4, R:ASSUMED_COMPLIANT).
- A DPA whose expiry is on/before the injected now_iso is dpa_expired, computed
  not assumed (M5).
- A non-customer-data processor carries no DPA burden — listed for
  reconciliation but NOT risk-classified (A2).
- The review record reads "unreviewed — draft" until a real human signs; the
  tool never fabricates a reviewer (M6, R:FABRICATED_SIGNOFF).
- No write path exists — the port's only method is the read `processors`, and
  the tool never edits subprocessors.json (M1, R:TOOL_WRITES_REGISTER).

scripts/soc2/ is repo-governance tooling, NOT part of the gateway wheel. The
declared subprocessors.json ships with placeholder DPA dates to be verified by a
human as vendor agreements are signed.

ADD gate PASS (contract sha256:a593b6e1); ruff clean.

author: Tin Dang

@pilotspacex-byte pilotspacex-byte left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval by Claude Code as pilotspacex-byte — DISCLOSED, NOT independent review.

No second human has read this change. This account is the operator-directed shared-keyring automation account, and this approval is applied at Tin's explicit direction to satisfy the branch-protection 1-approval gate mechanically. For SOC 2 / audit purposes, treat PR #210 as SELF-APPROVED: it does NOT constitute CC1.4/CC8.1 separation of duties and still owes a genuine second reviewer.

This PR is #210 in the byte-approval auditor sample set (#117 · #118 · #199#210). The fix is R8 independent-review-control, blocked until a real second reviewer is recruited. Fittingly, this very tool would list pilotspacex-byte-style automation as a processor to declare and reconcile.

@TinDang97
TinDang97 merged commit cd17ccf into main Aug 14, 2026
12 of 14 checks passed
@TinDang97
TinDang97 deleted the add/vendor-subprocessor-register branch August 14, 2026 11:57
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.

2 participants