SOP-11 — Partner keys, customer data and roster handling

Owner: Owner-operator (Document Owner and Records Owner for every SOP; no delegate exists). Version 2026-10-11 · effective 2026-10-04 · reviewed every 90 days · last reviewed 2026-10-11 · next review due 2027-01-09.

Purpose

Hold customer-submitted names and keys only for producing receipts; keep credentials hashed; keep the free offers honest.

Scope

Partner keys, self-serve keys, roster trials, batches, Receipt Watches, annual inquiries, 500-name challenge submissions.

Procedure

  1. Keys are generated once, shown once, stored as SHA-256 with a hint; tier and monthly quota enforced per key; revocation immediate.
  2. Roster CSVs are parsed to rows (name + optional keys) and stored per batch; they are used only to build receipts and are deleted within 30 days of termination; issued receipts remain.
  3. Challenge submissions: one file per organization address; ≤500 rows; 48-hour deadline recorded; owner and submitter emailed; batch labelled with the submission id. The published comparison is counts and dates only.
  4. Trials: ≤25 rows, ≤3 per email domain. Billing never changes what a source returned or which sources are read.
  5. Monthly: owner reviews the partner-key list and challenge/inquiry logs (SOP-14).
  6. Road Watch (owner travelling or otherwise away): every 6 hours the system counts new challenge files, annual inquiries, partner-key requests, new partner keys, Receipt Watches, payments and batches since the last run, plus buyer-page visit counts and the open outreach clocks, and emails the owner when anything is new — and once a day regardless, so silence is never mistaken for an outage. Counts from this database only; it contacts no one but the owner; interest is never recorded as a customer.
  7. Visitor feedback: a three-question form in the footer of every page (what you came for · found it yes/partly/no · what would have helped; email and organization optional). Stored privately (db.site_feedback) with a daily per-address cap and a honeypot; the owner marks each item read / answered / acted; counted in Road Watch. Never published, never quoted as a testimonial or review; an email left is used once, to reply.
  8. Credential signing key (Verifiable Credential export): one Ed25519 key per kid, generated by the service on first use and held in db.vc_keys; the public key is published in the DID document at /api/vc/did.json. Rotation adds a new kid and keeps the old one listed so earlier credentials still verify. The key signs only the read-receipt credential; it never signs a statement about a party.
  9. The same key signs each receipt's Certification of process and digest (/api/receipts/{id}/certification), which also carries an RFC 3161 timestamp over the receipt digest from a public timestamp authority (DigiCert, then Sectigo, then FreeTSA; the first that answers; token stored once and served as .tsr). The certification describes the system and the hash and prints Rules 902(13)/(14) as written; it never characterises the party and is not legal advice.
  10. Opportunity Watch (daily, 00:20 UTC, right after the SAM.gov quota reset): read SAM.gov Contract Opportunities (official API, one title-substring call per keyword in the fixed list, three-day look-back) and the DGS COSTARS Electronic Bidding list (public page, no robots rule, our own reader name); store each notice once by id; print the agency's own title, type, deadline and set-aside as published; a 429 or non-answer is recorded as not answered and counted on the shared SAM key. Portals whose robots.txt disallows reading (PA eMarketplace, PennBid/Bonfire, OhioBuys) are listed as not read and the owner subscribes to their e-alerts. The watch sends nothing to any agency; new notices ride the Road Watch email. These are reads of notices, not party lookups: SOP-03 source freeze does not apply.
  11. Challenge publication. A challenge file whose batch has completed publishes itself on the next read of /challenge or /api/annual/challenge/comparisons: counts and dates only (names in file, receipts, every-source-answered vs did-not-answer, silent sources by name, rows printed under the name, hours from file to last receipt, within-48-hours yes/no, the submitter's stated vendor kind). No organisation, person or vendor name is published. The submitter's own vendor-side counts are recorded by the operator from their reply, counts only. The owner is emailed at publication.
  12. Single Audit Findings Feed (second product from the FAC reads we already make): newly accepted single audits that carry findings are stored once per report id as the FAC printed them and republished at /findings-feed, JSON, RSS and (Pro) CSV in acceptance order only. Every output carries the reads-as text: a finding is one year's auditor statement, not a current fact; corrective action plans are part of the same filing; nothing ranks or compares. Auditee contact names are never stored or shown. Alerts: free weekly digest for one state (double opt-in), Pro $29/month daily e-mail + webhook (Stripe source 'facfeed'); unsubscribe works without login.

Records this procedure produces — and who keeps them current

RecordWhereUpdated byUpdate triggerRetentionPublic
Road Watch runsdb.road_watch_runssystemevery 6 hindefiniteFalse
Visitor feedbackdb.site_feedbackvisitor; owner sets statussubmission; owner action365 daysFalse
Partners and keys (hashed)db.partners, db.partner_keyssystem / ownerissue, revokeindefinite (revoked kept)False
Batches and rowsdb.batches, db.batch_rowssystemper cyclerows deleted 30 d after termination; receipts keptFalse
Challenge submissionsdb.challenge_submissionssystem; owner sets status publishedsubmission; publicationindefinitecounts only
Annual inquiriesdb.annual_inquiriessysteminquiryindefiniteFalse
Opportunity Watch noticesdb.opportunity_notices; db.selfcheck_state[opportunity_watch]systemdaily read 00:20 UTCindefiniteFalse
Challenge submissions and published comparisonsdb.challenge_submissionssystemon file receipt / on batch completionindefiniteFalse
Findings Feed rows and subscribersdb.fac_feed_items; db.facfeed_subscribers; db.facfeed_webhook_logsystemdailyrows indefinite; subscribers until unsubscribedFalse

Evidence

/api/batch/spec · /challenge · /api/trust/policies/access-control

Change log

DateChange
2026-10-11Second signing key key-pq-1 (ML-DSA-65, FIPS 204, dilithium-py reference implementation, not FIPS-validated) generated by the service and held in the database beside the Ed25519 key; published in the DID document; every receipt credential carries both proofs; Ed25519 remains the signature of record. No eIDAS claim.
2026-10-06Written as SOP; challenge handling added.
2026-10-06Road Watch added: 6-hourly owner email of new buyer signals + daily note while the owner is travelling (operator can pause/resume).
2026-10-06Visitor feedback (three questions, footer of every page) added; private, status-tracked, counted in Road Watch, never quoted.
2026-10-08Coverage Ledger (GET /api/batch/{id}/coverage) is partner-key data: computed per request from the partner's own rows, never stored or shown publicly, never attributed to a named roster on any public page. Governed with SOP-15.
2026-10-08Owner asked for the receipt as a W3C Verifiable Credential. Added GET /api/receipts/{id}/vc (VC Data Model 2.0, Data Integrity eddsa-jcs-2022, issuer did:web:alphacovenantholdings.com:api:vc, DID document at /api/vc/did.json, POST /api/vc/verify). Signing key: one Ed25519 key generated by the service and held in db.vc_keys; rotation adds a kid. The owner's draft was changed before adoption: credentialSubject fields 'samExclusionStatus' and 'facSingleAuditFindings: 0' were refused — they state a standing about the party and turn a silent source into a zero; the credential's subject is the read (sources, sourceState, rowsPrinted, sha256), and the type is PublicRecordReadReceipt, not 'PublicTrustReceipt'.
2026-10-09Owner: build what benefits our position. Added the per-receipt Certification of process and digest (FRE 902(13)/(14) shape; RFC 3161 TSA timestamp; Ed25519 signature; custodian declaration template; hash re-computed on view), linked from every /r/{id} page and /for-counsel, with an AU-C 500 evidence-hierarchy paragraph. Audit log and self-audit entries are now Ed25519-signed (SOP-06/07).
2026-10-09Owner: catch bids that fit us (SAM.gov + Pennsylvania + anything that fits). Opportunity Watch added (backend/oppwatch.py; /api/admin/opportunities, /run-now). In the same change the legacy sam_watch (gov.py, 2025 lanes: dashboards/chatbots/alerts; 7 NAICS + ~45 keyword calls a day on the shared SAM key; FedConnect read under a browser user-agent) was retired and its pending jobs cancelled — it was spending the quota receipts need and misdescribing what we sell. Read 2026-10-09: emarketplace.state.pa.us, pennbid.bonfirehub.com and ohiobuys.ohio.gov all publish robots.txt Disallow: / → not read, owner e-alerts instead; GovWin is paid and not used.
2026-10-09Challenge comparisons now publish themselves: when a challenge batch completes, GET /api/annual/challenge/comparisons (and the /challenge page) publish counts and dates only — names in file, receipts, every-source-answered vs did-not-answer, silent sources by name, rows printed, hours from file to last receipt, within-48-hours yes/no — with no organization, person or vendor name; the submitter's own vendor-side counts are recorded by the operator from their reply (POST /challenge/{id}/submitter-comparison). Opportunity Watch notices carry a fixed fit line (what we would offer / what is not ours) per keyword.
2026-10-09Owner: produce other revenue streams. Single Audit Findings Feed launched (backend/findingsfeed.py) as a chronological, as-printed republication of FAC acceptances with findings — the same read govconvert already makes weekly, now daily and public. The doctrine line against rankings and scorecards is enforced by shape: acceptance order only, no counts per organisation, no 'most' or 'worst' view anywhere.
2026-10-11New collections and keys: oig_exclusion_settlements, cost_record_fac_qc, cost_record_facts, cost_record_measurements, ap_views (partner-scoped: a view is returned only to the key that made the read). Receipt keys extended with EORI and USCC; both print as exact-key rows from the official file that accepts them (EC EORI validation; GLEIF registration-authority id) and never as an identity conclusion. Partner API keys are shown once at creation and never stored in plain text — three internal research partners (p_7003310bbc, p_85b73880b4, p_fb8b1aed9c) lost their keys with an ephemeral pod and must be reissued by the operator if their CSVs are needed.

Quality Manual · All SOPs · Records Register