Tenders: procurement as code
🟡 Partial — The full authority↔bidder round-trip is built and demonstrated against real GitHub.com repositories. The tender file has a state (open / awarded) and a formal awardee, and awarding merges the delivery PR. Today's limits: GitHub.com and GitLab (gitlab.com or self-hosted; deliveries are merge requests and awarding merges them) — neither Forgejo nor Bitbucket list deliveries. There is still no submission deadline or formal notification to non-selected bidders. Details in «Honest scope today».
This guide covers both halves of the flow: the buying authority (publish or register a tender, read the deliveries, award) and the bidder (respond to a tender, produce the evidence locally, self-check and deliver). If you come from Level 6 of the course, this is the product version of the “tender as code” it names: the threshold is imposed by the buyer’s tender, not by the supplier.
1. What a tender-as-code is
Section titled “1. What a tender-as-code is”A tender is a git repository containing two files:
pliego.oscal.yaml ← the required clauses (OSCAL format)pliego.oscal.yaml.sig ← DSSE/in-toto envelope signed by the authority (ECDSA-P256)Each tender clause references a clause from the engine’s catalog (for example mdr.gspr-3-risk-management from eu/mdr@2017, or risk estimation from eu/pren-18228@2026) with an enforcement:
- Blocking (
block) — the delivery must prove itCovered; otherwise the verdict is Rejected. - Warning (
warn) — informative; it does not shut the gate.
The signature is what makes the tender binding: the bidder (and any third party) can verify it with the authority’s public key, and the bidder’s response froga.yaml is anchored to the tender’s exact digest (responds_to_pliego = sha256 of the file’s bytes). Responding to a different tender —or a weakened version— produces a digest mismatch, and the delivery comes out Rejected. The tender does not declare a risk class: under the AI Act the bidder (provider) classifies, not the contracting authority.
sequenceDiagram participant A as Authority (deployer org) participant G as GitHub.com participant L as Bidder (provider org) A->>A: signs pliego.oscal.yaml outside the cloud (froga sign-pliego + KMS) A->>G: publishes pliego.oscal.yaml + .sig L->>G: forks the tender repo (via the platform) L->>L: local froga run → signed evidence .froga/* L->>G: delivery PR (fork → tender repo) A->>G: the cloud re-reads the PR and RE-DERIVES the verdict A->>G: «Award» → merges the PR (squash) and closes the tender file A->>A: the delivery is promoted to a governed system
2. What the cloud checks (and why it doesn’t trust)
Section titled “2. What the cloud checks (and why it doesn’t trust)”The tender file page (/[org]/pliegos/[slug]) does not show what the bidder claims: it re-derives every delivery’s verdict on the server, re-reading the artifacts at the PR head and verifying each link, fail-closed and in order:
- The tender’s signature —
pliego.oscal.yaml+.sigagainst the public key registered with the tender. Invalid signature → the tender is not authoritative. - The digest cross-check — the
responds_to_pliegoin the bidder’sfroga.yamlmust be thesha256of the tender’s exact bytes (anti-forgery). - The bidder’s identity — its public key is read from
.froga/PUBKEY.txtat the PR head; without it nothing verifies and the delivery comes out Rejected (“bidder not enrolled”). - The signed evidence — the bidder’s
conformance/*.jsonreports must verify against that key and satisfy the full schema. - The entitlement — if the tender requires blocking clauses from a paid standard (prEN 18228, MDR, DORA…), the bidder’s
.froga/entitlement.jsonmust verify against the Venturalítica issuer. - The clause intersection — every
blockclause of the tender must beCoveredby the evidence.
Any failure along the chain yields Rejected (with the gaps named clause by clause) or Unevaluable (SCM read error) — never an Accepted by omission. The gate does not fail open. Nor does unavailability get dressed up as a verdict: if GitHub did not respond (rate limit or timeout), the delivery shows “GitHub unavailable” — retry in a few minutes; it is not a judgement on the delivery or on the signature.
3. Four words that are not synonyms
Section titled “3. Four words that are not synonyms”Level 6 of the course distils this into three judgements; the tender flow adds a fourth:
| Judgement | Question it answers | Who / what issues it |
|---|---|---|
| Authenticity | Are the bytes exactly what the signer signed? | Cryptographic verification (froga verify) |
| Acceptance | Does the delivery meet the tender’s blocking clauses? | The tender gate (re-derived server-side) |
| Conformity | Does the product meet the law, clause by clause? | Only an accredited notified body — nobody in this chain |
| Award | Do I incorporate this delivery as a governed system? | A person at the authority, with the “Award” button |
A delivery can be perfectly authentic and Rejected. It can be Accepted and not conformant (the gap to full conformity is documented, not hidden). And it can be Accepted without being awarded: the verdict belongs to the gate; the award is a human decision. See notified body vs self-declaration for the boundary of the third judgement.
4. For the authority (buyer)
Section titled “4. For the authority (buyer)”4.1 Prerequisites
Section titled “4.1 Prerequisites”- AI Act deployer role. The Tenders surface only exists in deployer organisations: in Settings → Profile, declare the organisation’s AI Act role as “Deployer” (or “Both”). Without it, the
/pliegospages show an explanatory notice with a direct link to Settings → Profile to declare it (if you are anowner; everyone else is told to ask the owner). The 404 remains only for non-members of the organisation. - Member role
ownerormaintainer— publishing, registering and awarding are mutations. - A GitHub connection for the organisation (the GitHub App installed on the authority’s GitHub organisation, with permission to create repositories).
4.2 Publish a tender (signed outside the cloud)
Section titled “4.2 Publish a tender (signed outside the cloud)”At /[org]/pliegos → “Publish tender”, after signing the tender outside the cloud:
- Compose
pliego.oscal.yaml— usually with the “Initialize technical pliego” mission on your system (Annex III risk class + technical standards + required clauses, row by row), or hand-written in OSCAL format. - Sign it outside the cloud, on your machine or in your CI, with the same mechanism the engine uses to sign the development demos’ evidence:
froga sign-pliegopliego.oscal.yaml(Scaleway KMS backend in production —FROGA_SIGNING_BACKEND=scaleway-kms+SCW_KMS_BASE_URL) writes the DSSE envelopepliego.oscal.yaml.sig;froga pubkeyprints your SEC1 public key. The private key never reaches the cloud. - In the form, fill in the tender name and the repository name the cloud will create (GitHub or GitLab), and paste — or load from file — the three artifacts: the YAML, the
.sigand the public key (SEC1 hex,04…, 130 characters). The form shows the key’s fingerprint for your traceability.
The server verifies that the three artifacts are self-consistent (yaml↔sig↔pubkey) and validates every clause against the catalog before touching anything (fail-closed: a signature that does not verify leaves no trace); only then does it create the repository and commit exactly the received bytes (pliego.oscal.yaml + .sig). The tender is registered with your public key as the anchor: it is the one bidders will use to verify the tender.
4.3 Register an existing tender (“Explore tender”)
Section titled “4.3 Register an existing tender (“Explore tender”)”If the tender already lives in a repository (for example, signed with froga sign-pliego outside the platform), register it at /[org]/pliegos → “Explore tender”: Name, SCM connection, Repository (aapp-org/pliego-retina), Reference (branch or SHA, optional) and the contracting authority’s signing public key (uncompressed SEC1 hex: 130 characters, starts with 04).
Registration is fail-closed: the cloud reads pliego.oscal.yaml + .sig from the repo and verifies the signature against that key before creating anything. A repo with no tender, or with an invalid signature, is not registered. The form validates the key format inline (it warns if it is not 130 hex characters with the 04 prefix) and, as soon as you paste a well-formed key, shows its fingerprint (the first 16 hex characters of the key’s SHA-256): check it against the fingerprint the authority saw when signing the tender before you register it.
4.4 Reading the tender file: deliveries and gaps
Section titled “4.4 Reading the tender file: deliveries and gaps”Each tender has its file page at /[org]/pliegos/[slug]: the header shows the number of clauses, the signature state and the tender-file state — “Tender open”, or “Awarded to bidder · date” after awarding (the state lives in the database, it does not depend on GitHub; the /pliegos list carries the same State column). The signature is re-verified on the server on every load and has three honest states:
- “Tender signed ✓” — the signature verifies against the registered key.
- “Tender unverified ✗” — the signature is invalid or missing (a real veracity failure).
- “GitHub unavailable — retry ⟳” — GitHub did not respond (rate limit or timeout). It is not a signature failure: it is unavailability; reload in a few minutes.
Below it, the received deliveries — the pull requests against the tender repo, with their bidder, the PR link and the re-derived verdict:
- Accepted — the entire chain of section 2 verifies and all blocking clauses are covered.
- Rejected — with the gaps named (which clause, of which standard, and why), or the structural reason (digest mismatch, bidder without
PUBKEY.txt…). - Unevaluable — the PR or its artifacts could not be read (SCM error); it never turns into an Accepted.
- GitHub unavailable — the provider did not respond while evaluating that delivery (rate limit/timeout): retry. If GitHub was unavailable for the whole tender, the table shows the honest notice instead of a misleading “no deliveries yet” — the deliveries exist, they just could not be read.
PRs closed without merging (withdrawn attempts) are ignored; merged ones are kept (the awarded delivery ends up merged — it is historical acceptance, see 4.5). The verdict is recomputed on page load: if the bidder pushes new evidence to the PR, the tender file reflects it. Once the tender is awarded, the other bidders’ deliveries carry the “Not selected” label — a visual mark on the file; their re-derived verdict does not change.
4.5 Awarding
Section titled “4.5 Awarding”While the tender file is open, each Accepted delivery offers the “Award” form: you choose the system name and confirm. The award is transactional: the cloud re-verifies server-side that the delivery is still accepted (it never accepts the client’s verdict) and, in a single act:
- Promotes the delivery to a governed system in your organisation, anchored to the delivery’s exact commit (the PR head): the awarded evidence is frozen; nothing after it alters it. It records the origin (
received from= the bidder’s organisation) — the supply-chain attribution is derived from the re-verified PR, not from the form. The system appears in Systems and under the tender file’s “Awarded systems” section. - Closes the tender file: the state becomes “Awarded to
bidder· date”, the award form disappears (the decision is taken) and the other bidders’ deliveries are labelled “Not selected”. - Merges the delivery PR (squash merge) into the tender repository: the awarded delivery becomes one commit in the tender file’s history — git auditability of the decision. If the merge fails (conflict, permissions), the award is not rolled back: the UI warns you to merge it by hand on GitHub.
Awarding is also idempotent: a double-click or a second visit does not create a duplicate system — the already-promoted system is returned. And trying to award another bidder’s delivery on an already-awarded tender is refused (“the tender file is already awarded”): the recorded human decision prevails over any later retry.
Accepted ≠ awarded: accepted is the gate’s verdict; awarding is your decision to incorporate it — the tender file itself says so under the deliveries heading.
5. For the bidder (provider)
Section titled “5. For the bidder (provider)”5.1 Prerequisites
Section titled “5.1 Prerequisites”Responding to a tender takes four pieces today:
- An account and organisation on the Venturalítica platform, with the
ownerormaintainerrole. - Your own GitHub organisation (a personal account will not do) with the GitHub App installed — the tender fork lands there.
- The
frogaCLI on your machine: the evidence is produced and signed locally withfroga run; the cloud never runs the engine or signs on your behalf. - A standards entitlement if the tender requires clauses from paid standards (prEN 18228, MDR, DORA…):
froga entitlement syncwrites.froga/entitlement.json, signed by the Venturalítica issuer. ISO 23894 and ISO/IEC 42001 are free and do not require it. Which standards each plan licenses (and what happens if the tender requires one outside yours): Plans & standards.
5.2 Respond to a tender (the wizard)
Section titled “5.2 Respond to a tender (the wizard)”In Systems → “Respond to a tender” (/[org]/systems/respond), a two-step wizard:
- Step 1 — Connect to the tender. You paste the tender repository’s GitHub URL. The platform forks the tender repo into your GitHub organisation — a native fork (metadata on GitHub’s side): the cloud pushes no content across organisations, and the tender files’ authorship remains the authority’s.
- Step 2 — Declare purpose and classification. System name, intended purpose, classification (Annex III) and the declared risk class — under the AI Act you classify, not the authority. The cloud generates an initial
froga.yamlin your fork (a scaffold, unsigned) withresponds_to_pliego= the tender’s exact digest and the standards the tender requires, and registers the fork as a governed system in your organisation.
5.3 The evidence is signed on YOUR machine
Section titled “5.3 The evidence is signed on YOUR machine”The cloud never writes .froga/* or signs anything on your behalf. In your fork, locally:
git clone https://github.com/your-org/pliego-retina.git && cd pliego-retina# complete froga.yaml (risks, controls, appetite) and your evidence pipelinefroga run # produces and SIGNS the .froga/* evidencefroga pubkey > .froga/PUBKEY.txt # publish your public key (the cloud reads it from the PR)# if the tender requires paid standards:froga entitlement sync --system <id> --url <cloud> --token <token>git add froga.yaml .froga && git commit -m "signed evidence" && git push.froga/PUBKEY.txt at the delivery head is how the authority identifies you: without it, your delivery comes out “Rejected · bidder not enrolled”. The signing scheme in detail: sign & verify evidence.
5.4 Self-check BEFORE delivering (the local gate)
Section titled “5.4 Self-check BEFORE delivering (the local gate)”Don’t deliver blind: the same gate the authority re-derives in the cloud runs on your machine.
froga conformance --against-pliego pliego.oscal.yaml \ --aapp-pubkey 04… # the public key of the authority that signed the tenderIt verifies the tender’s signature, intersects its blocking clauses with your signed conformance evidence, and exits with 0 if your delivery would be Accepted and non-zero if it would be Rejected, naming the failing clause. Your public key resolves from --pubkey, or from .froga/PUBKEY.txt. Full reference: froga conformance; put it in your CI so you never discover the gap in the authority’s tender file.
5.5 Deliver
Section titled “5.5 Deliver”Your system’s detail page shows the “Delivery” panel (only on systems responding to a tender): the source tender, the local development guidance, and the “Deliver” button, which opens the delivery pull request from your fork to the authority’s tender repo — using your own credential, never the authority’s. The PR carries the handoff traceability metadata (receivedFrom, deliveryOnly); the content already lives in your fork. From then on, your delivery appears in the authority’s tender file with the re-derived verdict of section 2.
6. Honest scope today
Section titled “6. Honest scope today”Everything above is built, demonstrated against real repositories and verified — and it has limits worth knowing before planning a procurement on top of it:
- GitHub.com and GitLab. The tender file works on GitHub.com (deliveries = pull requests) and on GitLab — gitlab.com or self-hosted with a custom base URL (deliveries = merge requests; the bidder’s fork, reading the evidence at the MR head and the merge on award are implemented; there is a local test GitLab under
demo/gitlab/). Publishing a new tender (creating the repository and committing the YAML + signature) works the same on GitHub and on GitLab — signing always happens outside the cloud withfroga sign-pliego; the cloud only transports and verifies. Forgejo and Bitbucket still do not list deliveries: the app no longer offers those connections when registering a tender, precisely so no tender file appears empty without warning. - The tender file has a state, but it is not (yet) a complete administrative file. The tender now has a formal state (open / awarded), an awardee and an award date; awarding is transactional (double-clicks do not duplicate), closes the file and merges the delivery PR. What is still missing: a submission deadline, formal notification to non-selected bidders (the “Not selected” label is a mark on the file, not a notification), and closing a tender without awarding (the “Closed” state exists in the model, but there is no action yet to close a deserted tender).
- 1 tender = 1 repository. Each tender file is a GitHub repo of the authority and the deliveries are PRs against it. That gives you git auditability out of the box — and it also means a portfolio of dozens of tenders per year is a portfolio of repos your GitHub organisation must govern.
- Bidder onboarding is not packaged. Every bidder needs the four pieces of section 5.1 (platform account, GitHub organisation with the App, the CLI locally, and the entitlement if there are paid standards). There is no bidder invitation from the tender yet, nor a “respond without an account” flow.
- The gate’s verdict is not a conformity assessment. “Accepted” means the delivery meets the tender’s criterion — not that the product is conformant clause by clause with the law. That judgement, for the high-risk classes that require it, is issued only by a notified body, and there is none in this chain. It is the central lesson of Level 6.