Initial scaffold (Phase A gate = CONDITIONAL; narrowed Phase B build). Not published — owner-gated.
- COMPLIANCE.md — Phase A license gate. Mobility Database catalog metadata confirmed CC0;
feed content is each agency's license with consumer-borne compliance; US open-feed universe
sized (~0.5–0.9 % standard-open). Verdict: CONDITIONAL → open-license-filtered build.
- License filter (
src/gtfs.js) — isOpenLicense() + verified-open allowlist; default
openLicensesOnly = true; per-record license/attribution propagation.
- Decoders — static GTFS (fflate unzip + RFC-4180 CSV parse → snapshot + diff);
GTFS-Realtime (protobuf via
gtfs-realtime-bindings) → vehicle-positions / service-alerts /
trip-updates normalization.
- Actor (
src/main.js) — catalog resolution, compliance gate, per-mode decode, pay-per-event
record-returned charging with eventChargeLimitReached handling, _error/_skipped markers.
- Smoke test (
src/smoke.js) — one request to the MBTA open GTFS-RT feed; proves license
filter + protobuf decode.
- Unit suite (
test/gtfs.test.js, node --test, no network) — 32 tests over the decode +
license-filter core: isOpenLicense/allowlist/feedAttribution, RFC-4180 parseCsv
(quoted commas, escaped quotes, CRLF, embedded newlines) + csvToObjects, resolveFeed
(URL vs mdb-id vs unresolved), decodeStaticGtfs+staticDiff (baseline/none/added/removed via
a synthetic zip), and decodeRealtime for all three RT modes against a saved offline protobuf
fixture (test/fixtures/gtfs-rt.pb, regenerable via npm run fixture).
- License-filter fix (
src/gtfs.js) — tightened the CC-BY pattern so CC-BY-NC / CC-BY-ND (and
their -sa/-nd combos) are no longer falsely cleared as open; CC-BY and CC-BY-SA remain open per
the Open Definition. Surfaced by the new tests; prevents a wrong "open" license claim.
- Robustness (
src/gtfs.js, src/main.js) — bounded exponential-backoff fetchWithRetry()
(retries transient network errors + HTTP 408/429/5xx, never permanent 4xx; injectable
fetch/sleep) now wraps every feed fetch and the catalog fetch. New inputs fetchRetries (default
2) + retryBaseDelayMs (500). Feed errors are stage-tagged (_error.stage = fetch/decode/emit)
so malformed zips / bad protobufs are attributable; reached-but-empty feeds emit an un-charged
_empty marker instead of vanishing. Unit suite now 41 tests (+9: retry classification,
retry/backoff/exhaust/rethrow paths, malformed-zip throw, header-only empty-feed decode).
- Positioning docs (
docs/) — SCOPE.md (curated-open sizing, honest non-promises, mirrors
COMPLIANCE §2–3), PRICING.md (pay-per-event structure + billing-honesty table; concrete price
values owner-deferred), LISTING.md (Apify Store copy draft). README gains a Docs index.
Docs-only; suite unchanged at 41/41 green.
- Allowlist build-out (slice 2,
src/gtfs.js + COMPLIANCE.md §6–7) — VERIFIED_OPEN_ALLOWLIST
grew from 1 → 4 entries, each with an out-of-band-verified open license cited (agency/portal
page read 2026-07-09): MBTA (open, RT), Valley Metro Phoenix (CC-BY, RT via public-key
mecatran endpoints), CCRTA Cape Cod (CC-BY 4.0, static), Caltrans DDS host (CC-BY 4.0,
~29 CA-agency static feeds). Two carry GTFS-RT so the realtime path is exercised. Excluded
(could not confirm open): TriMet (revocable + registration), Nevada County/Gold Country Stage
(API-key auth, Swiftly vendor license), CapMetro (revocable, agency-owned), SEPTA (custom license
agreement). Suite +5 → 46 tests (allowlist membership + each new entry clears isOpenLicense
and stamps attribution).
- Local end-to-end run (slice 3) — ran the Actor via
apify-cli run against MBTA's live
GTFS-RT VehiclePositions.pb: decoded 3 vehicle-position records, each dataset item stamped with
_license / _licenseUrl / _attribution. Charging is a platform no-op locally (SDK logs
"Ignored attempt to charge — the Actor does not use pay-per-event pricing" — expected; PPE billing
only activates once the price schema is declared + published, which is owner-gated).