PoC catalogue — entry specification
The catalogue's unit is a PoC entry: a shareable proof of concept that delivered code, only findings, or both. The transferability verdict is an optional badge, not a requirement for being catalogued.
Entries are YAML files under catalog/ (one per PoC). The authoritative schema
is src/estafette/entry.py; this document explains it.
Not reinventing publiccode.yml
publiccode.yml already describes public-sector
software, and OpenCatalogi federates it. But it is software-only (url and
softwareType are mandatory), has no poc status, and no field for a
PoC's conclusion. So it cannot represent a findings-only PoC or an experiment's
outcome. estafette therefore wraps publiccode.yml rather than forking it: an
entry adds only what publiccode lacks (kind, conclusion) and references a
real publiccode.yml for the software metadata.
Fields
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | PoC name. |
owner |
string | yes | Owning organisation/person. |
contact |
string | yes | A reachable contact. |
status |
enum | yes | concept | poc | beta | stable | obsolete. |
kind |
enum | yes | code | findings | both. |
conclusion |
string | yes | The takeaway of the PoC — the point of the entry. |
repo |
string | no | Link to the code repository. |
doc |
string | no | Link to the findings/writeup. |
demo |
string | no | Link to a demo. |
publiccode |
string | no | Path/URL to a real publiccode.yml (software metadata; OpenCatalogi on-ramp). |
assessment |
string | no | Path to an estafette report.json; when present, its bronze verdict is shown as a badge. |
Example (findings-only)
name: Address matching spike
owner: Example Municipality
contact: dev@example.gov
status: poc
kind: findings
conclusion: >-
Fuzzy-matching BAG addresses against the input register hit 92% precision but
needs a human review step below 0.8 confidence. Not production-ready; the
register API rate limit (5 req/s) is the real blocker.
doc: https://example.gov/spikes/address-matching.pdf
Rendering
estafette catalogue --catalog catalog --out site renders every entry into a
static site: an index (name, kind, status, verdict badge where assessed,
conclusion) and a per-entry detail page (full conclusion, links, and the
assessment criteria when present). Output is deterministic (invariant I5).
Submission & harvesting
Entries enter the catalogue two ways (see ../submitting.md):
a pull request adding a catalog/*.yaml (CI validates it via estafette
catalogue --check), or the harvester pulling a well-known poc.yaml from
each repo listed in sources.yaml. Both produce the same PoCEntry; only the
ingress differs. Local catalog/*.yaml and harvested entries merge into one
deterministic set — the local entry wins on a name collision.
The poc.yaml / catalog/*.yaml contract
A poc.yaml is nothing more than a PoCEntry serialized as a single UTF-8 YAML
document. In a hosting repo it lives at the repo root under exactly that
filename; as a local submission it is any catalog/<name>.yaml. One
authoritative definition: src/estafette/entry.py.
- Required:
name,owner,contact,status(concept|poc|beta|stable|obsolete),kind(code|findings|both), and a non-emptyconclusion(the PoC's takeaway — the star of the entry). - Optional:
repo,doc,demo,publiccode,assessment. - A file that is missing, unparseable, or schema-invalid is skipped with a
recorded reason — never fatal (I3). A repo that also carries a
publiccode.ymlneeds no duplication:poc.yamlwins, publiccode is only the fallback (wrapped askind: code, marked auto-derived).
# poc.yaml (repo root) — or catalog/my-poc.yaml
name: My PoC
owner: My Org
contact: https://github.com/my-org/my-poc/issues
status: poc
kind: findings
conclusion: >-
One paragraph: what we tried, what we learned, whether it is worth adopting.
repo: https://github.com/my-org/my-poc
Harvester limits & compute
- Where it runs.
estafette harvestis a stateless CLI step, run in GitHub Actions (the Pages workflow,harvestbeforecatalogue) — not on our cluster. It can equally run on a workstation or via cron; nothing requires a server or persistent state. - Raw fetch, not the GitHub API. Each repo costs a plain HTTPS GET on the raw
path (
.../HEAD/poc.yaml, then.../publiccode.ymlas fallback) — 1–2 requests per repo. This avoids the API's rate ceilings;raw.githubusercontent.comhas only soft abuse limits a curated list never approaches, and raw paths are host-agnostic (GitLab/Gitea work the same). Request count is strictlyO(repos) × (1–2)— no pagination, recursion, or org enumeration. - Self-imposed caps. A per-fetch size cap (~512 KB) and timeout; a source that exceeds either is skipped with a reason. No auth, public repos only. No code execution (I6): only declared fields are read into the pydantic model.
- When the API becomes necessary (future, out of scope). Moving from a
hand-listed repo list to an org crawl ("find every repo in these orgs
with a
poc.yaml") needs repo enumeration via the GitHub API — a token and its 1000–5000/hour ceiling. That is a separate future change; v1 stays on the limit-free repo list.