Instantiates docs/standards/RESPONSIBLE-TECH-FRAMEWORK.md. Last regenerated: 2026-07-05 (initial
version — this file did not exist before this date; P2-1 in that day's remediation pass). Regenerate
on each release per the framework's audit-as-artifact discipline.
- A Ethics: Applies. See §A.
- B Bias: Applies. Reporting bias (route-choice, reporter-pool, app-access, language,
demographic/geographic, survivorship, salience, temporal) is a first-class, tested output
(
stats/bias.py) — not case-by-case here, because the whole project exists to counter one specific bias (raw counts confounding danger with traffic). See §B. - C Privacy: Applies (DPIA:
docs/DPIA.md). See §C. - D Transparency: Applies. See §D.
- E Accessibility: Applies (ACR:
docs/accessibility/ACR.md). Six shipped HTML documents over seven routes:index.html(/),404.html,web/index.html(legacy redirect stub),web/us-coverage.html(/fars/national/and/web/us-coverage.html),web/studio.html(/studio/), andweb/dossier.html(/dossier/) — seetools/build_site.pyfor the deployed allowlist.web/davis-demo.html,web/submit.html, andweb/embed.htmlare source/CI fixtures and are not deployed. The ACR evaluates only the source-onlydavis-demo.htmlsurface, so no shipped document has per-criterion coverage. See §E. - F Security: Applies (threat model:
docs/THREAT-MODEL.md, residual-risk register:docs/THREAT-MODEL.md#residual-risk-register). See §F. - AI-EVAL: N/A — no LLM/AI SDK usage anywhere in the codebase (verified by grep). See
docs/adr/0004-standards-applicability.mdfor the dated ADR making this call and its trigger condition for re-evaluation. - I18N: Applies (EN/ES) — civic-facing surface; see
docs/I18N.md.
What could go wrong? A dataset framed as "where it's dangerous" could (a) be misread as an official/complete record rather than a biased voluntary sample, (b) be used to target or embarrass specific streets/residents rather than to advocate for infrastructure change, or (c) have its public submission form abused to manufacture or suppress a hotspot (astroturfing).
How do we test for it?
- The "who could be hurt if this works exactly as intended" question: a city could point at a
low-rate segment and de-prioritize it, even though "low rate" can mean "too few reports to say
anything" (
confidence_label = uncertain) rather than "safe." This is why the dataset never lets a sparse segment rank as if it were certain (hard rule 2) and whydocs/DATA-CARD.mdhas an explicit "Out-of-scope and discouraged uses" section naming this exact misuse. docs/LIMITATIONS.mdanddocs/DATA-CARD.mdname the non-goals ("not a 311 queue," "not for enforcement, surveillance, insurance, or punitive action against individuals") directly.
What do we commit to?
- An explicit non-goals statement (README "What it does,"
docs/DATA-CARD.md"Out-of-scope and discouraged uses,"docs/INTAKE-AND-ABUSE.md"Non-goals"). - A misuse-resistance design for the public submission form (
docs/INTAKE-AND-ABUSE.mdPart B; the implemented subset indocs/SUBMISSIONS.md). - A named accountable owner: Chelsea Kelly-Reif, sole maintainer (no diffusion of responsibility to hide behind).
- A kill-switch/rollback plan:
SECURITY.md's response process for privacy/integrity issues is "un-publish or roll back the affected GeoJSON... while a corrected... version is rebuilt" — this is the misuse/harm rollback plan, not just a security process.
Enforcement:
- AUTO-GATE:
tests/test_publish_privacy.py(k-anonymity floor is mechanically enforced, not policy-only);tests/test_moderation.py(a public submission cannot bypass human review to reach the dataset). - REVIEW-GATE: this document is the consequence-scan sign-off artifact (dated 2026-07-05). Gap: no separate, narrower "ethics sign-off" doc existed before this file — RTF-01 in the 2026-07-05 audit noted this as PARTIAL; this section is intended to close it, but has not yet been re-audited against RTF-01's exact bar.
What could go wrong? The published rate could read as an unbiased measure of danger when it is actually a rate over a self-selected, biased sample, and the bias could systematically under-represent exactly the people most at risk (people without smartphones, people who avoid dangerous streets entirely, non-English speakers).
How do we test for it? stats/bias.py compares the reporter pool and the geographic spread of
reports against ridership/demographic baselines and reports over/under-representation per segment;
this is exercised by the test suite and surfaced in every brief (brief.py "Reporting bias" section,
never omitted).
What do we commit to? Eight named biases in docs/DATA-CARD.md § Known reporting biases
(route-choice, reporter-pool, app-access/digital-divide, language, demographic/geographic,
survivorship/avoidance, salience/severity, temporal/campaign) — not a generic disclaimer, but named
mechanisms with their direction of effect. EN/ES is treated as a first-class segment (a report's
language tag feeds the bias characterization), per the framework's explicit civic-repo guidance.
Enforcement:
- AUTO-GATE: the bias section is a required, non-optional part of every rendered brief
(
render_briefalways calls_render_bias_section; there is no code path that omits it); i18n key-parity gates (tools/check_catalog_parity.py) ensure the EN/ES bias text stays in parity. - REVIEW-GATE: a dedicated representational-harm review (who is over/under-represented in
self-reported near-miss data, beyond the statistical-bias framing already in
stats/bias.py) is not yet a separately committed, dated artifact — this is the same gap RTF-03 flagged in the 2026-07-05 audit as PARTIAL. Recommended next step: a short dated addendum todocs/DATA-CARD.mdor a newdocs/audits/representational-harm-review.md, not yet written.
What could go wrong? The public submission form (2026-06-29) accepts precise location data from third parties for the first time; a bug or design gap in aggregation/withholding could re-identify a reporter's home, workplace, or routine.
Deployment update (2026-07-16): that browser form is now source-only and absent from the production artifact. The risk remains applicable to CLI/operator intake and would become a public surface again only after a new reviewed deployment decision.
How do we test for it? See the full DPIA: docs/DPIA.md, and the threat model's T1/T2
and residual-risk register RR-1/RR-2. Data inventory,
retention, lawful-basis screening, and named gaps (including the currently-unaddressed lack of a
retention/deletion policy, Gap G1 in the DPIA) all live there rather than duplicated here.
What do we commit to? No identity field ever (structural, not policy); precise location discarded
after segment-snapping and never published (assert_published_clean, tested); k-anonymity floor;
env-only secrets, never committed (gitleaks pre-commit + CI).
Enforcement:
- AUTO-GATE:
tests/test_publish_privacy.py(the "guaranteed absent" denylist),server.py's_redact_path+tests/test_observability.py(no raw-store paths reachable or logged),gitleaks(pre-commit and CI, no mute). - REVIEW-GATE:
docs/DPIA.md, dated 2026-07-05, sign-off recorded there. Named gap: no automated retention/deletion job exists yet (DPIA Gap G1) — tracked, not hidden.
What could go wrong? A rate, ranking, or hotspot claim could be taken at face value without its
interval, its n, its exposure source/date, or its bias caveat — exactly the "heat-map lie" the whole
project exists to refuse.
How do we test for it? Every published rate carries rate_ci_low/rate_ci_high/n/
exposure_source/exposure_date/confidence_label (schema-enforced, schema/dataset.schema.md);
every brief states the bias caveat and a plain-language glossary before the numbers
(brief._render_intro/_render_glossary).
What do we commit to? Visible sourcing on every number (never a bare rate); explicit
uncertainty/confidence labeling (certain/uncertain/exposure_unknown, never silently omitted);
docs/LIMITATIONS.md stating plainly what the dataset cannot tell you; docs/METHODOLOGY.md recording
every statistical choice and its honest caveats (including several sections marked PLANNED rather than
overclaimed as implemented, per that document's own claim-tagging convention).
Enforcement:
- AUTO-GATE:
schema/dataset.schema.md's JSON Schema validation in CI rejects a published feature missing its interval/n/source fields;tests/test_brief.pychecks the bias section is always present. - REVIEW-GATE:
docs/DATA-CARD.mdanddocs/METHODOLOGY.mdare the committed transparency artifacts, both reviewed as part of this 2026-07-05 pass. No model card / datasheet-for-AI applies (AI-EVAL is N/A);docs/DATA-CARD.mdalready follows the Datasheets-for-Datasets shape this audit asks for.
What could go wrong? A disabled road user — among the people most endangered by a bad street and most likely to need this exact map — could be unable to read the findings.
How do we test for it? Structural gate (tools/a11y_check.py, merge-blocking) + automated
axe-core (jsdom, merge-blocking via npm ci && npm run axe in CI, fixed 2026-07-05 to install from the
committed lockfile — see P0-3 in the remediation log). Manual NVDA/VoiceOver review is not yet in
place and remains marked Not performed. The national studio has additional rendered-browser
evidence and a provisional solo-maintainer public-preview disposition under
ADR 0012; neither is represented as a
screen-reader result or conformance.
What do we commit to? WCAG 2.2 AA target; a non-visual table equivalent to every map finding; risk conveyed in text/pattern, never color alone; a committed ACR (VPAT 2.5 Rev 508).
Enforcement:
- AUTO-GATE: structural + axe-core, both merge-blocking (
ci.ymlaccessibilityjob). - REVIEW-GATE:
docs/accessibility/ACR.md— stale relative to the 2026-06-29 submission form and embed widget (report date 2026-06-17, predates both surfaces). Not re-issued as part of this pass — flagged explicitly as a carried-forward gap (P2-3 in the remediation plan is the full re-audit; that is M-effort and out of scope for this pass, so the honest move here is naming the staleness, not silently leaving the ACR looking current). A provisional public-preview record may permit deployment but cannot update an ACR row or claim this REVIEW-GATE was performed.
Frame: ASVS 5.0 target level and the narrative threat model, on top of the mechanical scanners already running in CI.
ASVS 5.0 level declaration: Level 2. Rationale: nearmiss is not processing authentication credentials or handling payment data (ASVS L2's classic trigger), but it now ingests public submissions of precise-location data from third parties (2026-06-29) — data that is PII-adjacent (location + timing can re-identify a person's routine) even though no name/email/identity field exists. L1 (opportunistic) is judged insufficient for a system accepting third-party sensitive-adjacent input from the open internet; L3 (the highest bar, aimed at systems handling high-value transactions or requiring the highest degree of trust) is judged more than this project's risk profile warrants. This is a first declaration, not yet checked line-by-line against every ASVS 5.0 L2 control — that control-by-control mapping is future work, tracked as a P2/P3-scale item, not fabricated here as done.
What could go wrong? See docs/THREAT-MODEL.md T1–T6 in full; summarized: deanonymization,
report/dataset poisoning, exposure-source tampering, a naive consumer misreading volume as danger,
supply-chain compromise, secret leakage.
How do we test for it? STRIDE-shaped threat model (docs/THREAT-MODEL.md); abuse-case tests
(tests/test_publish_privacy.py, tests/test_moderation.py); dependency/secret/workflow scanning in
CI.
What do we commit to? A documented threat model with a dated review
(docs/THREAT-MODEL.md, reviewed 2026-07-05) and a residual-risk register with a
named owner (added 2026-07-05); no fixed HIGH/CRITICAL findings merged (pip-audit --strict,
merge-blocking, no mute).
Enforcement:
- AUTO-GATE:
pip-audit --strict(blocking),gitleaks(pre-commit + CI, blocking), CodeQL (python, and as of 2026-07-05 alsojavascriptandactions— closing the "web JS unscanned" finding open since 2026-06-21),zizmor(added 2026-07-05, blocking onhighseverity, seeci.yml'szizmorjob). - REVIEW-GATE: threat-model + residual-risk-register sign-off — done, dated 2026-07-05
(
docs/THREAT-MODEL.md). Scheduled OpenSSF Scorecard (.github/workflows/scorecard.yml) and scheduled TruffleHog full-history verified-secret scan (.github/workflows/secret-scan-scheduled.yml) were also added 2026-07-05; neither has run yet as of this writing (they take effect once this branch is committed and pushed), so their results are not yet a committed artifact — that will be true after the first scheduled run.
| Item | Declaration |
|---|---|
| ASVS 5.0 level | L2 (declared above; control-by-control checklist not yet completed). |
| Container / image scanning | N/A — no Dockerfile, no container image is built or published (verified by find . -iname Dockerfile*). If a container image is ever added, this row must flip to a Trivy/Grype scanning commitment before that image ships. |
| SBOM generation | Exercised. The tag-triggered release workflow builds a CycloneDX 1.7 SBOM and attaches it to the release. sbom.cdx.json is published on the v0.2.0, v0.3.0, and v0.3.1 releases (2026-08-08), each with a Sigstore bundle, so there is a published SBOM to verify. |
| Artifact signing | Exercised. Keyless cosign signing and SLSA provenance ran on all three tags; the releases carry attestation.json plus Sigstore bundles for the sdist, wheel, SBOM, and every published city GeoJSON. The 0.3.1 wheel's attestation resolves through the GitHub attestations API. |
| Secret management | Environment variables / CI secret store only, never committed. Enforced by gitleaks pre-commit (bumped to v8.30.1 in this pass) and CI (blocking, full-history diff), plus the scheduled TruffleHog verified-secrets scan added 2026-07-05. No secrets manager / vault is in use or currently needed (no deployed always-on service holds credentials today). |
| VEX (vulnerability exploitability exchange) | N/A — no known unfixed vulnerabilities exist to justify a waiver for. pip-audit --strict is blocking with no mute; if a future unfixable finding needs a documented waiver, it is recorded here rather than silently suppressed. |
| Branch protection / ruleset artifact | ⛔ Not committed — this is a live GitHub repository setting, not a file this pass can create or verify from a local checkout. See the remediation log's Execution Log entry for the exact command the maintainer must run (gh api repos/ChelseaKR/nearmiss/rulesets to configure, then export and commit as docs/audits/branch-ruleset.json). |
docs/THREAT-MODEL.md— full adversary model + residual-risk register.docs/DPIA.md— the privacy audit's dated artifact.docs/accessibility/ACR.md— the accessibility audit's dated artifact (currently stale relative to the 2026-06-29 surfaces — see §E above).docs/DATA-CARD.md,docs/LIMITATIONS.md— the transparency audit's dated artifacts.docs/adr/0004-standards-applicability.md— the AI-EVAL N/A declaration.