@the-rfp-hub/standard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARTIFACTS.md +85 -0
- package/CHANGELOG.md +264 -0
- package/LICENSE +121 -0
- package/NORMATIVE.md +77 -0
- package/PROCESS.md +239 -0
- package/README.md +83 -0
- package/conformance/README.md +52 -0
- package/conformance/v1.0.0/fail/deadline-fixed-date-null.json +22 -0
- package/conformance/v1.0.0/fail/deadline-fixed-without-date.json +21 -0
- package/conformance/v1.0.0/fail/eligibility-value-not-string.json +21 -0
- package/conformance/v1.0.0/fail/empty-sponsoring-organizations.json +11 -0
- package/conformance/v1.0.0/fail/missing-matching-type-block.json +14 -0
- package/conformance/v1.0.0/fail/missing-required-properties.json +5 -0
- package/conformance/v1.0.0/fail/missing-sponsoring-organizations.json +10 -0
- package/conformance/v1.0.0/fail/one-block-per-fundingtype.json +18 -0
- package/conformance/v1.0.0/fail/source-url-removed.json +17 -0
- package/conformance/v1.0.0/fail/status-not-in-enum.json +15 -0
- package/conformance/v1.0.0/fail/unknown-top-level-property.json +16 -0
- package/conformance/v1.0.0/fail/wrong-fundingtype-value.json +14 -0
- package/conformance/v1.0.0/fail/wrong-specversion.json +15 -0
- package/conformance/v1.0.0/pass/deadline-rolling-without-date.json +23 -0
- package/conformance/v1.0.0/pass/full-featured.json +137 -0
- package/conformance/v1.0.0/pass/grant-funding-mechanisms-array.json +27 -0
- package/conformance/v1.0.0/pass/grant.json +45 -0
- package/conformance/v1.0.0/pass/hackathon.json +67 -0
- package/conformance/v1.0.0/pass/minimal-required-only.json +15 -0
- package/conformance/v1.0.0/pass/self-identification.json +18 -0
- package/conformance/v1.0.0/pass/source-empty-object.json +30 -0
- package/dist/index.cjs +902 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +661 -0
- package/dist/index.d.ts +661 -0
- package/dist/index.js +896 -0
- package/dist/index.js.map +1 -0
- package/meta/rfphub-schema.meta.json +131 -0
- package/package.json +56 -0
- package/registries/deadline-labels.json +38 -0
- package/registries/eligibility-keys.json +46 -0
- package/registries/entry.schema.json +48 -0
- package/registries/index.json +44 -0
- package/registries/program-models.json +26 -0
- package/schemas/index.json +12 -0
- package/schemas/v1.0.0/BENCHMARK.md +160 -0
- package/schemas/v1.0.0/CROSSWALK.md +200 -0
- package/schemas/v1.0.0/FIELDS.md +530 -0
- package/schemas/v1.0.0/STATUS.md +81 -0
- package/schemas/v1.0.0/context.jsonld +86 -0
- package/schemas/v1.0.0/examples/01-hackathon-fundingmap_1200.json +181 -0
- package/schemas/v1.0.0/examples/02-hackathon-fundingmap_1197.json +122 -0
- package/schemas/v1.0.0/examples/03-hackathon-fundingmap_1095.json +114 -0
- package/schemas/v1.0.0/examples/04-grant-fundingmap_1459.json +67 -0
- package/schemas/v1.0.0/examples/05-hackathon-fundingmap_1210.json +97 -0
- package/schemas/v1.0.0/examples/06-hackathon-fundingmap_1340.json +91 -0
- package/schemas/v1.0.0/examples/07-hackathon-fundingmap_1093.json +89 -0
- package/schemas/v1.0.0/examples/08-hackathon-fundingmap_1091.json +89 -0
- package/schemas/v1.0.0/examples/09-hackathon-fundingmap_1094.json +89 -0
- package/schemas/v1.0.0/examples/10-grant-fundingmap_600.json +65 -0
- package/schemas/v1.0.0/examples/11-hackathon-fundingmap_1389.json +83 -0
- package/schemas/v1.0.0/examples/12-hackathon-fundingmap_1356.json +83 -0
- package/schemas/v1.0.0/examples/13-grant-fundingmap_1458.json +59 -0
- package/schemas/v1.0.0/examples/14-grant-fundingmap_1059.json +57 -0
- package/schemas/v1.0.0/examples/15-hackathon-fundingmap_1430.json +82 -0
- package/schemas/v1.0.0/examples/16-hackathon-fundingmap_1092.json +79 -0
- package/schemas/v1.0.0/examples/17-hackathon-fundingmap_1277.json +75 -0
- package/schemas/v1.0.0/examples/18-hackathon-fundingmap_1302.json +72 -0
- package/schemas/v1.0.0/examples/19-hackathon-fundingmap_1231.json +76 -0
- package/schemas/v1.0.0/examples/20-hackathon-fundingmap_1235.json +74 -0
- package/schemas/v1.0.0/examples/21-hackathon-fundingmap_1219.json +73 -0
- package/schemas/v1.0.0/examples/22-hackathon-fundingmap_1254.json +72 -0
- package/schemas/v1.0.0/examples/23-grant-fundingmap_491.json +57 -0
- package/schemas/v1.0.0/examples/24-hackathon-fundingmap_1358.json +73 -0
- package/schemas/v1.0.0/examples/25-grant-fundingmap_1021.json +57 -0
- package/schemas/v1.0.0/examples/26-bounty-fundingmap_1453.json +57 -0
- package/schemas/v1.0.0/examples/27-accelerator-fundingmap_1291.json +56 -0
- package/schemas/v1.0.0/examples/28-rfp-fundingmap_1242.json +43 -0
- package/schemas/v1.0.0/opportunity.schema.json +758 -0
- package/spec.config.json +11 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# RFP Hub Standard v1.0.0 — Field Reference
|
|
2
|
+
|
|
3
|
+
> **Maturity: `draft`.** This version was **re-cut in place on 2026-07-27** — documents published
|
|
4
|
+
> under the string `1.0.0` before that date do not validate against it. See
|
|
5
|
+
> [`STATUS.md`](./STATUS.md), the field-mapping table in
|
|
6
|
+
> [`CHANGELOG.md`](../../CHANGELOG.md) and
|
|
7
|
+
> [`adr/0001`](../../../../adr/0001-recut-v1.0.0-in-place.md).
|
|
8
|
+
|
|
9
|
+
The **RFP Hub Standard** is a canonical, ecosystem-neutral representation of a funding
|
|
10
|
+
opportunity in the Ethereum ecosystem. The normative artifact is
|
|
11
|
+
[`opportunity.schema.json`](./opportunity.schema.json) (JSON Schema, draft 2020-12).
|
|
12
|
+
|
|
13
|
+
**This document is informative.** It explains the fields, the conventions the schema cannot
|
|
14
|
+
enforce, and the lifecycle semantics — but where its prose disagrees with the schema, **the
|
|
15
|
+
schema wins**, and this file gets corrected. See [`NORMATIVE.md`](../../NORMATIVE.md) for the
|
|
16
|
+
full normative/informative split and what that means for the release cycle.
|
|
17
|
+
|
|
18
|
+
- **License:** CC0 1.0 (the standard and its docs are public domain).
|
|
19
|
+
- **Spec version:** `1.0.0` — every entry carries `specVersion: "1.0.0"` exactly.
|
|
20
|
+
- **`$id`:** `https://raw.githubusercontent.com/The-RFP-Hub/the-rfp-hub/main/packages/standard/schemas/v1.0.0/opportunity.schema.json`
|
|
21
|
+
— ⚠️ **provisional.** The project has no canonical domain yet, so the identifier points at the
|
|
22
|
+
file in the repository, where it dereferences to exactly these bytes (served as `text/plain`).
|
|
23
|
+
It is stamped from [`spec.config.json`](../../spec.config.json); adopting a domain is one edit
|
|
24
|
+
there. See [`STATUS.md`](./STATUS.md#known-issues-in-this-version).
|
|
25
|
+
|
|
26
|
+
## Conventions
|
|
27
|
+
|
|
28
|
+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT",
|
|
29
|
+
"RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted
|
|
30
|
+
as described in BCP 14 [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and
|
|
31
|
+
[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) when, and only when, they appear in all
|
|
32
|
+
capitals, as shown here.
|
|
33
|
+
|
|
34
|
+
Several requirements in this standard are **stated but not schema-enforceable** — they cross
|
|
35
|
+
object boundaries, or they describe an ingestion policy rather than a document shape. Those are
|
|
36
|
+
written as MUST here and are checked by the advisory tier of `rfphub-validate`, not by JSON
|
|
37
|
+
Schema. Where that is the case, this document says so at the point of the rule.
|
|
38
|
+
|
|
39
|
+
## Scope
|
|
40
|
+
|
|
41
|
+
The Hub is **ETH-scoped**, not a multi-ecosystem catch-all. It does not attempt to absorb
|
|
42
|
+
Solana/Cosmos/other non-ETH ecosystems. However, `ecosystems` is an **open, extensible
|
|
43
|
+
list — not a closed enum** — so the Ethereum L1 plus L2s and ETH-adjacent ecosystems
|
|
44
|
+
(Optimism, Base, Arbitrum, Polygon, Scroll, zkSync, Linea, OP Stack, Celo, …) are all
|
|
45
|
+
first-class. `ecosystems` and `networks` are deliberately **not** registry-governed: a registry
|
|
46
|
+
over a list of chain names reads as an allowed-values list however carefully the normative
|
|
47
|
+
document words it, and it would put a review step in front of a newly launched chain for no
|
|
48
|
+
interoperability gain. Write the ecosystem's usual name.
|
|
49
|
+
|
|
50
|
+
## Design principles
|
|
51
|
+
|
|
52
|
+
1. **Source-agnostic.** The standard carries no source-system internal fields (on-chain ids,
|
|
53
|
+
internal primary keys, vendor-specific flags). Such data, if needed, goes under `extensions`
|
|
54
|
+
with namespaced keys (e.g. `mysource.internalId`). This keeps the standard neutral and
|
|
55
|
+
forkable — it isn't coupled to any one aggregator's schema.
|
|
56
|
+
2. **Provenance is recorded, not validated.** `source` is a required object, but **every field
|
|
57
|
+
inside it is optional** — `"source": {}` validates. There is no required source URL and no
|
|
58
|
+
required provenance field of any kind. "Every entry is traceable to an original posting" is
|
|
59
|
+
therefore asserted by **ingestion policy**, not by schema validation: the Hub's ingestion
|
|
60
|
+
layer always sets `source.ingestedVia` server-side and records `submittedBy`/`submittedAt`,
|
|
61
|
+
and a publisher's own pipeline is expected to do the equivalent. A validator will not catch a
|
|
62
|
+
record with empty provenance, because a validator is the wrong place to catch it. *(This
|
|
63
|
+
replaces the earlier principle "every entry MUST carry a `source.url`" — that field no longer
|
|
64
|
+
exists; see the field-mapping table in [`CHANGELOG.md`](../../CHANGELOG.md).)*
|
|
65
|
+
3. **Closed core, open edges.** The top-level object and all type-specific blocks are
|
|
66
|
+
`additionalProperties: false`, with three exceptions carved out for self-identification
|
|
67
|
+
(`$schema`, `@context`, `@type`). Arbitrary publisher/integrator data goes in the free-form
|
|
68
|
+
`extensions` object. Where a field is deliberately open **and its values need to be
|
|
69
|
+
comparable across publishers** — `eligibility` keys, `deadlines[].label`,
|
|
70
|
+
`grant.programModel` — the **schema stays permissive and a [registry](../../registries/)
|
|
71
|
+
fixes what each value means**. Registered values are normative; unregistered values remain
|
|
72
|
+
valid and raise a warning, never an error. `ecosystems` and `networks` are open with **no**
|
|
73
|
+
registry, deliberately: see [Scope](#scope).
|
|
74
|
+
4. **Alignment.** Concepts align with DAOIP-5 (Grants Metadata) and schema.org/Grant where
|
|
75
|
+
practical, without inheriting their full surface area. See [CROSSWALK.md](./CROSSWALK.md).
|
|
76
|
+
|
|
77
|
+
All date-time fields are **RFC 3339 / ISO 8601** strings.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Field reference
|
|
82
|
+
|
|
83
|
+
The tables below are **generated from the schema** by `pnpm codegen`; `pnpm codegen:check`
|
|
84
|
+
fails CI if they drift. Do not edit them by hand — edit the schema's `description` and
|
|
85
|
+
regenerate. The **Registry** column links the open vocabulary that governs a field's *values*
|
|
86
|
+
where one exists.
|
|
87
|
+
|
|
88
|
+
<!-- BEGIN generated:fields -->
|
|
89
|
+
|
|
90
|
+
### Top-level fields
|
|
91
|
+
|
|
92
|
+
| Field | Type | Req. | Description | Registry |
|
|
93
|
+
|---|---|:--:|---|---|
|
|
94
|
+
| `$schema` | string(uri) | | Optional self-identification: the URL of the RFP Hub schema this document claims to conform to. Permitted so a generic validator can discover the contract from the instance alone. Ignored by validation — naming a different schema here does not change which schema the document is validated against. | — |
|
|
95
|
+
| `@context` | string \| object \| any[] | | Optional JSON-LD context: a URL, an inline context object, or an array of either. Permitted so an instance can be consumed as linked data. Ignored by validation — the standard makes no claim about its contents. | — |
|
|
96
|
+
| `@type` | string \| any[] | | Optional JSON-LD type, or an array of types. Permitted so an instance can be consumed as linked data; ignored by validation. | — |
|
|
97
|
+
| `specVersion` | `1.0.0` | ✅ | The RFP Hub Standard version this entry conforms to. Fixed at 1.0.0 for this schema. Consumers use it to select the correct validator. | — |
|
|
98
|
+
| `id` | string, ≤128, `^[A-Za-z0-9._:-]+$` | ✅ | Stable, unique identifier for the opportunity within the Hub. Immutable once assigned. A namespaced form is recommended but not required. | — |
|
|
99
|
+
| `fundingType` | `grant` \| `hackathon` \| `bounty` \| `accelerator` \| `vc_fund` \| `rfp` | ✅ | The kind of funding opportunity, and the structural discriminator of the standard. Every entry carries a type-specific object under a key equal to this value ('hackathon' → a 'hackathon' object, 'vc_fund' → a 'vc_fund' object), so consumers can always read `opportunity[opportunity.fundingType]`. The matching block is required and no other type block may be present; for grants the block may be empty. | — |
|
|
100
|
+
| `title` | string, ≤300 | ✅ | Human-readable name of the opportunity. | — |
|
|
101
|
+
| `description` | string | ✅ | Full description of the opportunity. Markdown is permitted; consumers are advised to treat it as untrusted and sanitise before rendering. | — |
|
|
102
|
+
| `summary` | string\|null, ≤500 | | Optional short teaser (roughly one or two sentences) for list and card views. | — |
|
|
103
|
+
| `status` | `upcoming` \| `open` \| `closed` \| `archived` | ✅ | Lifecycle status of the opportunity. 'upcoming' = announced but not yet accepting applications, and also the value for a pre-open posting — there is no 'draft' status; 'open' = currently accepting; 'closed' = no longer accepting; 'archived' = withdrawn or retired. Editorial and review state (pending, rejected) is not represented here — it is server-side metadata. | — |
|
|
104
|
+
| `sponsoringOrganizations` | [`organization`](#organization)[], min 1 | ✅ | The organisations issuing or backing the opportunity. Array order is semantic: entry 0 is the primary organisation and the one to display. This is the issuer or backer, not necessarily the source of funds — for donor-funded models the money's origin is deliberately not modelled, and the party running the process belongs in operatingOrganizations instead. | — |
|
|
105
|
+
| `operatingOrganizations` | [`organization`](#organization)[] | | The organisations that actually run intake and process — for example an operator running the application funnel on a funder's behalf. May be absent or empty when the sponsor also operates. | — |
|
|
106
|
+
| `source` | [`provenance`](#provenance) | ✅ | Provenance of this entry. Required as an object, but every field inside it is optional, so `"source": {}` validates. Provenance completeness is a data-quality and ingestion-policy concern rather than a schema constraint. | — |
|
|
107
|
+
| `ecosystems` | string[], unique | | Ethereum-family ecosystems this opportunity targets. The RFP Hub is ETH-scoped, but this is an open, extensible list — not a closed enum, and deliberately not registry-governed either — so L2s and ETH-adjacent ecosystems are first-class and a newly launched one needs no process. | — |
|
|
108
|
+
| `networks` | string[], unique | | Specific networks or chains the funding is denominated on or deployed to. A plain open list, deliberately not registry-governed, so a newly launched chain is expressible immediately. | — |
|
|
109
|
+
| `categories` | string[], unique | | Topical categories. Free text. | — |
|
|
110
|
+
| `tags` | string[], unique | | Free-form tags for search and faceting. | — |
|
|
111
|
+
| `eligibility` | object<string, string> | | Open key-value map of eligibility criteria. Publishers choose their own keys and write plain-string values; there are no fixed or required keys. Conventional keys (stage, geography, jurisdiction, sector, entityType, compliance) are published in registries/eligibility-keys.json — using them keeps the field comparable across publishers, and unregistered keys stay valid. | [`eligibility-keys`](../../registries/eligibility-keys.json) |
|
|
112
|
+
| `prerequisites` | string\|null | | Free text describing what a proposal must contain to be considered — track record, approach, milestone plan, disclosures. Distinct from rfp.requirements, which describes what the work must deliver. | — |
|
|
113
|
+
| `resourceLinks` | string\|null | | A single free-form string of supporting links and references — guidelines, past rounds, forum threads, original postings. Deliberately one string rather than an array of URIs, because publishers paste what they have. | — |
|
|
114
|
+
| `serviceAgreement` | string\|null | | Free text describing how a service-agreement arrangement works. Valid on any fundingType — an rfp or grant carrying it reads as a long-term service engagement. Presence of the field is the signal; duration and renewal live in the text if they matter. Not filterable or facetable, by design. **(provisional)** | — |
|
|
115
|
+
| `applicationUrl` | string(uri)\|null | | URL where applicants submit or apply — the only URL that points at the opportunity itself, and therefore the only link-back target. It may carry whatever the submission channel is, including a forum thread when no portal exists; the URL's kind is not typed. Clarifications go in description. | — |
|
|
116
|
+
| `website` | string(uri)\|null | | Primary website for the opportunity or program. | — |
|
|
117
|
+
| `logoUrl` | string(uri)\|null | | URL of the program or organisation logo image. | — |
|
|
118
|
+
| `bannerUrl` | string(uri)\|null | | URL of a banner or hero image. | — |
|
|
119
|
+
| `socialLinks` | [`socialLinks`](#sociallinks) | | Social and community links for the opportunity or program. | — |
|
|
120
|
+
| `funding` | [`funding`](#funding) | | Program-level funding envelope: single currency, total budget, amount committed to date, and the per-award range. | — |
|
|
121
|
+
| `milestones` | [`milestone`](#milestone)[] | | Optional milestone sequence, valid on any fundingType. Array order is the milestone sequence — there is no order or index field. Milestone-based payment is expressed by this array together with grant.milestoneBased; there is no separate payment-schedule concept. **(provisional)** | — |
|
|
122
|
+
| `opensAt` | string(date-time)\|null | | RFC 3339 timestamp when applications open. | — |
|
|
123
|
+
| `deadlines` | [`deadline`](#deadline)[], unique | | All deadlines and event boundaries for the opportunity, each either a fixed date or rolling, distinguished by label. Consumers should select by label rather than by array position: the first entry may be a hackathon's start date rather than its application deadline. Conventional labels are published in registries/deadline-labels.json. (Selection-by-label is a consumer convention, not schema-enforceable; see FIELDS.md.) | — |
|
|
124
|
+
| `postedAt` | string(date-time)\|null | | RFC 3339 timestamp when the opportunity was first publicly announced at the source. | — |
|
|
125
|
+
| `createdAt` | string(date-time)\|null | | RFC 3339 timestamp when this entry was created in the Hub. | — |
|
|
126
|
+
| `updatedAt` | string(date-time)\|null | | RFC 3339 timestamp when this entry was last modified in the Hub. | — |
|
|
127
|
+
| `grant` | [`grant`](#grant) | cond. | Grant-specific fields. Required, possibly as an empty object, when fundingType is 'grant'; forbidden otherwise. | — |
|
|
128
|
+
| `hackathon` | [`hackathon`](#hackathon) | cond. | Hackathon-specific fields. Required when fundingType is 'hackathon'; forbidden otherwise. | — |
|
|
129
|
+
| `bounty` | [`bounty`](#bounty) | cond. | Bounty-specific fields. Required when fundingType is 'bounty'; forbidden otherwise. | — |
|
|
130
|
+
| `accelerator` | [`accelerator`](#accelerator) | cond. | Accelerator-specific fields. Required when fundingType is 'accelerator'; forbidden otherwise. | — |
|
|
131
|
+
| `vc_fund` | [`vcFund`](#vcfund) | cond. | VC-fund-specific fields. Required when fundingType is 'vc_fund'; forbidden otherwise. | — |
|
|
132
|
+
| `rfp` | [`rfp`](#rfp) | cond. | RFP-specific fields. Required when fundingType is 'rfp'; forbidden otherwise. | — |
|
|
133
|
+
| `extensions` | object | | Namespace for publisher- or integrator-specific data not covered by the standard. Keys are conventionally namespaced, for example 'mysource.internalId'. Contents are not validated by this schema. | — |
|
|
134
|
+
|
|
135
|
+
### `organization`
|
|
136
|
+
|
|
137
|
+
An organisation sponsoring or operating the opportunity. Embedded on an opportunity as a descriptive summary; the same shape is the standalone Organization directory record.
|
|
138
|
+
|
|
139
|
+
| Field | Type | Req. | Description | Registry |
|
|
140
|
+
|---|---|:--:|---|---|
|
|
141
|
+
| `name` | string, ≤256 | ✅ | Display name of the organisation. | — |
|
|
142
|
+
| `slug` | string\|null, `^[a-z0-9-]+$` | | Lowercase URL-safe identifier, and also the organisation's namespace. | — |
|
|
143
|
+
| `type` | `foundation` \| `dao` \| `company` \| `protocol` \| `program` \| `individual` \| `other`\|null | | Kind of entity. | — |
|
|
144
|
+
| `description` | string\|null | | Short description of the organisation. | — |
|
|
145
|
+
| `website` | string(uri)\|null | | The organisation's primary website. | — |
|
|
146
|
+
| `logoUrl` | string(uri)\|null | | URL of the organisation's logo image. | — |
|
|
147
|
+
| `bannerUrl` | string(uri)\|null | | URL of the organisation's banner or hero image. | — |
|
|
148
|
+
| `socialLinks` | [`socialLinks`](#sociallinks) | | Social and community links for the organisation. | — |
|
|
149
|
+
| `ecosystems` | string[], unique | | Ethereum-family ecosystems the organisation operates in. Same open list as the top-level field. | — |
|
|
150
|
+
| `contacts` | [`contact`](#contact)[] | | Named contact routes into the organisation. Optional, and every field of every entry is optional too. | — |
|
|
151
|
+
|
|
152
|
+
### `contact`
|
|
153
|
+
|
|
154
|
+
A named contact route into the organisation. Every property is optional and there is no minimum-one-identifier constraint, so `{}` validates — deliberately, because not every publisher can or will name a person.
|
|
155
|
+
|
|
156
|
+
| Field | Type | Req. | Description | Registry |
|
|
157
|
+
|---|---|:--:|---|---|
|
|
158
|
+
| `name` | string\|null | | The person's name. | — |
|
|
159
|
+
| `role` | string\|null | | Role in the program. | — |
|
|
160
|
+
| `telegram` | string\|null | | Telegram handle. A handle rather than a URL — unlike socialLinks.telegram, which is a link. | — |
|
|
161
|
+
| `email` | string(email)\|null | | Email address. | — |
|
|
162
|
+
|
|
163
|
+
### `provenance`
|
|
164
|
+
|
|
165
|
+
How this entry reached the Hub and when it was last checked. Every field is optional, so `{}` validates. There is no source URL: link-back to the opportunity runs through the top-level applicationUrl alone.
|
|
166
|
+
|
|
167
|
+
| Field | Type | Req. | Description | Registry |
|
|
168
|
+
|---|---|:--:|---|---|
|
|
169
|
+
| `publisher` | string\|null | | Namespace — an organisation slug — this entry was published under. Auto-approval requires the publishing account to be a member of this verified org. May differ from the sponsoring organisation. | — |
|
|
170
|
+
| `submittedBy` | string\|null | | Who submitted or published this entry: a public handle, an organisation slug, or 'community' for anonymous community submissions. The internal account identity is never exposed. This is the attribution carrier for data-partner credit. | — |
|
|
171
|
+
| `submittedAt` | string(date-time)\|null | | RFC 3339 timestamp of when the entry was submitted or published to the Hub. Pairs with submittedBy. | — |
|
|
172
|
+
| `ingestedVia` | `publisher_api` \| `submission` \| `scrape` \| `import` \| `outbox`\|null | | How this entry entered the Hub. 'outbox' is a one-way push from an upstream source system's outbox; 'import' is a backfill or seed import. Always set server-side by the ingestion layer. | — |
|
|
173
|
+
| `originalId` | string\|null | | Identifier of this opportunity in the source system. | — |
|
|
174
|
+
| `verifiedAgainstSource` | boolean\|null | | Whether the entry's fields were verified against the live opportunity by the verification-assist job. null means not yet checked. | — |
|
|
175
|
+
| `verifiedAt` | string(date-time)\|null | | RFC 3339 timestamp of the last verification. Record-level only — there is no per-field freshness. | — |
|
|
176
|
+
| `snapshotUrl` | string(uri)\|null | | IPFS or archived snapshot of the opportunity taken at verification time. | — |
|
|
177
|
+
|
|
178
|
+
### `socialLinks`
|
|
179
|
+
|
|
180
|
+
Social and community links. Every value is a full URL, not a handle.
|
|
181
|
+
|
|
182
|
+
| Field | Type | Req. | Description | Registry |
|
|
183
|
+
|---|---|:--:|---|---|
|
|
184
|
+
| `twitter` | string(uri)\|null | | Link to the X/Twitter profile. | — |
|
|
185
|
+
| `discord` | string(uri)\|null | | Discord server invite or channel link. | — |
|
|
186
|
+
| `github` | string(uri)\|null | | Link to the GitHub organisation or repository. | — |
|
|
187
|
+
| `telegram` | string(uri)\|null | | Link to the Telegram group or channel. | — |
|
|
188
|
+
| `farcaster` | string(uri)\|null | | Link to the Farcaster profile or channel. | — |
|
|
189
|
+
| `forum` | string(uri)\|null | | Link to the governance or community forum. | — |
|
|
190
|
+
| `blog` | string(uri)\|null | | Link to the blog or announcements feed. | — |
|
|
191
|
+
|
|
192
|
+
### `funding`
|
|
193
|
+
|
|
194
|
+
The program-level funding envelope. Single-currency by design, and that rule is scoped to this envelope only: bounty.reward, hackathon.prizes[].currency and accelerator.funding each keep their own currency, because a prize pool may legitimately be denominated differently from the program budget. 'Remaining' is derived at the consumer layer as budget minus allocated, and never stored.
|
|
195
|
+
|
|
196
|
+
| Field | Type | Req. | Description | Registry |
|
|
197
|
+
|---|---|:--:|---|---|
|
|
198
|
+
| `currency` | string\|null, ≤16 | | ISO 4217 code or token symbol for the amounts below, and for milestones[].amount. | — |
|
|
199
|
+
| `budget` | number\|null, ≥0 | | Total program budget in major units. | — |
|
|
200
|
+
| `allocated` | number\|null, ≥0 | | Amount committed to date in major units — committed, not necessarily disbursed. Disbursement and delivery are not modelled. | — |
|
|
201
|
+
| `minAward` | number\|null, ≥0 | | Minimum individual award in major units. | — |
|
|
202
|
+
| `maxAward` | number\|null, ≥0 | | Maximum individual award in major units. | — |
|
|
203
|
+
|
|
204
|
+
### `monetaryAmount`
|
|
205
|
+
|
|
206
|
+
A single amount with its own currency, used where a sub-block is denominated independently of the program envelope.
|
|
207
|
+
|
|
208
|
+
| Field | Type | Req. | Description | Registry |
|
|
209
|
+
|---|---|:--:|---|---|
|
|
210
|
+
| `amount` | number, ≥0 | ✅ | Amount in major units of the currency, so 2000000 means 2,000,000 USD rather than cents. | — |
|
|
211
|
+
| `currency` | string, ≤16 | ✅ | ISO 4217 fiat code such as USD or EUR, or a token symbol such as ETH, OP or USDC. | — |
|
|
212
|
+
|
|
213
|
+
### `amountRange`
|
|
214
|
+
|
|
215
|
+
A lower and upper bound with a shared currency. Either bound may be absent, expressing an open-ended range.
|
|
216
|
+
|
|
217
|
+
| Field | Type | Req. | Description | Registry |
|
|
218
|
+
|---|---|:--:|---|---|
|
|
219
|
+
| `min` | number\|null, ≥0 | | Lower bound in major units. | — |
|
|
220
|
+
| `max` | number\|null, ≥0 | | Upper bound in major units. | — |
|
|
221
|
+
| `currency` | string\|null, ≤16 | | ISO 4217 code or token symbol for both bounds. | — |
|
|
222
|
+
|
|
223
|
+
### `deadline`
|
|
224
|
+
|
|
225
|
+
A single deadline or event boundary. A 'fixed' entry carries a date; 'rolling' means applications are accepted continuously.
|
|
226
|
+
|
|
227
|
+
| Field | Type | Req. | Description | Registry |
|
|
228
|
+
|---|---|:--:|---|---|
|
|
229
|
+
| `type` | `fixed` \| `rolling` | ✅ | Whether this deadline is a fixed point in time or an open-ended rolling window. | — |
|
|
230
|
+
| `date` | string(date-time)\|null | | RFC 3339 timestamp. Required and non-null when type is 'fixed', enforced by the if/then below; meaningless, and normally omitted, when type is 'rolling'. | — |
|
|
231
|
+
| `label` | string\|null, ≤120 | | What this deadline is for. Free text; conventional values are published in registries/deadline-labels.json. This is how a consumer tells an application deadline from an event boundary. | [`deadline-labels`](../../registries/deadline-labels.json) |
|
|
232
|
+
|
|
233
|
+
### `milestone`
|
|
234
|
+
|
|
235
|
+
One milestone in an opportunity's milestone sequence. Every property is optional — a publisher may list titles with no amounts, or amounts with no criteria. There is no date field: where a publisher has a due date, it goes into `criteria` as free text.
|
|
236
|
+
|
|
237
|
+
| Field | Type | Req. | Description | Registry |
|
|
238
|
+
|---|---|:--:|---|---|
|
|
239
|
+
| `title` | string\|null | | Short name of the milestone. | — |
|
|
240
|
+
| `amount` | number\|null, ≥0 | | Payment for this milestone in major units, denominated in the top-level funding.currency. That denomination rule is a requirement on publishers but crosses two objects, so it is not schema-enforceable; see FIELDS.md. The validator's advisory tier warns when this is present and funding.currency is absent. | — |
|
|
241
|
+
| `criteria` | string\|null | | Free-text acceptance criteria, including any due date. | — |
|
|
242
|
+
|
|
243
|
+
### `grant`
|
|
244
|
+
|
|
245
|
+
Grant-specific attributes not covered by the core fields. May be an empty object, because core funding and date fields live at the top level.
|
|
246
|
+
|
|
247
|
+
| Field | Type | Req. | Description | Registry |
|
|
248
|
+
|---|---|:--:|---|---|
|
|
249
|
+
| `fundingMechanisms` | (`retroactive` \| `proactive` \| `streaming` \| `quadratic` \| `matching` \| `other`)[], unique | | How funds are allocated. An array because mechanisms co-occur: a funder can offer a fixed grant and a matching grant in the same program. | — |
|
|
250
|
+
| `programModel` | string\|null | | The operating model of the program, as distinct from the funding instrument. An open list rather than a closed enum — conventional values are published in registries/program-models.json, and a publisher's own vocabulary is valid without a schema change. **(provisional)** | [`program-models`](../../registries/program-models.json) |
|
|
251
|
+
| `milestoneBased` | boolean\|null | | Whether disbursement is tied to milestones. Pairs with the top-level milestones array. | — |
|
|
252
|
+
| `recurring` | boolean\|null | | Whether the program runs in recurring rounds or seasons. | — |
|
|
253
|
+
|
|
254
|
+
### `hackathon`
|
|
255
|
+
|
|
256
|
+
Hackathon-specific attributes. All dates — registration, submission, event start and event end — live in the shared top-level deadlines array, distinguished by label.
|
|
257
|
+
|
|
258
|
+
| Field | Type | Req. | Description | Registry |
|
|
259
|
+
|---|---|:--:|---|---|
|
|
260
|
+
| `location` | string\|null | | Physical location, or null for a fully online event. | — |
|
|
261
|
+
| `online` | boolean\|null | | Whether the event is also, or only, held online. | — |
|
|
262
|
+
| `tracks` | string[], unique | | Named tracks or themes participants can build against. | — |
|
|
263
|
+
| `prizes` | [`prize`](#prize)[] | | The prize pool, one entry per prize. Each prize carries its own currency. | — |
|
|
264
|
+
| `teamSize` | [`teamSize`](#teamsize) | | Permitted team size range. | — |
|
|
265
|
+
|
|
266
|
+
### `prize`
|
|
267
|
+
|
|
268
|
+
A single hackathon prize, optionally attributed to a track.
|
|
269
|
+
|
|
270
|
+
| Field | Type | Req. | Description | Registry |
|
|
271
|
+
|---|---|:--:|---|---|
|
|
272
|
+
| `track` | string\|null | | Track this prize belongs to, where prizes are tracked separately. | — |
|
|
273
|
+
| `amount` | number, ≥0 | ✅ | Prize amount in major units. | — |
|
|
274
|
+
| `currency` | string, ≤16 | ✅ | ISO 4217 code or token symbol for this prize. | — |
|
|
275
|
+
|
|
276
|
+
### `teamSize`
|
|
277
|
+
|
|
278
|
+
Permitted team size, as an inclusive range. Either bound may be absent.
|
|
279
|
+
|
|
280
|
+
| Field | Type | Req. | Description | Registry |
|
|
281
|
+
|---|---|:--:|---|---|
|
|
282
|
+
| `min` | integer\|null, ≥1 | | Minimum number of team members. | — |
|
|
283
|
+
| `max` | integer\|null, ≥1 | | Maximum number of team members. | — |
|
|
284
|
+
|
|
285
|
+
### `bounty`
|
|
286
|
+
|
|
287
|
+
Bounty-specific attributes. A bounty is a single scoped task with a stated reward, so the reward is the one required field.
|
|
288
|
+
|
|
289
|
+
| Field | Type | Req. | Description | Registry |
|
|
290
|
+
|---|---|:--:|---|---|
|
|
291
|
+
| `reward` | [`monetaryAmount`](#monetaryamount) | ✅ | The reward paid on completion. Carries its own currency. | — |
|
|
292
|
+
| `difficulty` | `beginner` \| `intermediate` \| `advanced`\|null | | Self-assessed difficulty, as a hint to applicants. | — |
|
|
293
|
+
| `skills` | string[], unique | | Skills the task calls for. Free text. | — |
|
|
294
|
+
| `platform` | string\|null | | Platform hosting the bounty. | — |
|
|
295
|
+
|
|
296
|
+
### `accelerator`
|
|
297
|
+
|
|
298
|
+
Accelerator-specific attributes. The application deadline lives in the shared top-level deadlines array with label 'application'.
|
|
299
|
+
|
|
300
|
+
| Field | Type | Req. | Description | Registry |
|
|
301
|
+
|---|---|:--:|---|---|
|
|
302
|
+
| `programDurationWeeks` | integer\|null, ≥0 | | Length of the program in weeks. | — |
|
|
303
|
+
| `batchSize` | integer\|null, ≥0 | | Number of teams accepted per cohort. | — |
|
|
304
|
+
| `equity` | string\|null | | Equity taken, expressed as a string because programs state it in incomparable ways. | — |
|
|
305
|
+
| `funding` | [`monetaryAmount`](#monetaryamount) | | Investment or stipend offered per team. Carries its own currency. | — |
|
|
306
|
+
| `stage` | `pre-seed` \| `seed` \| `series-a`\|null | | Company stage the program targets. | — |
|
|
307
|
+
| `location` | string\|null | | Physical location, or null for a fully remote program. | — |
|
|
308
|
+
| `online` | boolean\|null | | Whether the program is also, or only, run remotely. | — |
|
|
309
|
+
|
|
310
|
+
### `vcFund`
|
|
311
|
+
|
|
312
|
+
Venture-fund-specific attributes. A fund is an ongoing source of capital rather than a round, so it carries no deadline of its own.
|
|
313
|
+
|
|
314
|
+
| Field | Type | Req. | Description | Registry |
|
|
315
|
+
|---|---|:--:|---|---|
|
|
316
|
+
| `checkSize` | [`amountRange`](#amountrange) | | Typical investment size, as a range. | — |
|
|
317
|
+
| `stages` | (`pre-seed` \| `seed` \| `series-a` \| `series-b+` \| `growth`)[], unique | | Investment stages the fund participates in. | — |
|
|
318
|
+
| `thesis` | string\|null | | Investment thesis, in the fund's own words. | — |
|
|
319
|
+
| `portfolio` | string[], unique | | Named portfolio companies, where the fund publishes them. | — |
|
|
320
|
+
| `contactMethod` | `email` \| `form` \| `intro-only`\|null | | How the fund prefers to be approached. 'intro-only' means a warm introduction is required. | — |
|
|
321
|
+
| `activelyInvesting` | boolean\|null | | Whether the fund is currently deploying capital. | — |
|
|
322
|
+
|
|
323
|
+
### `rfp`
|
|
324
|
+
|
|
325
|
+
RFP-specific attributes. The issuing organisation is sponsoringOrganizations[0], the budget is the top-level funding envelope, and the proposal deadline is a deadlines entry labelled 'application'.
|
|
326
|
+
|
|
327
|
+
| Field | Type | Req. | Description | Registry |
|
|
328
|
+
|---|---|:--:|---|---|
|
|
329
|
+
| `scope` | string\|null | | Scope of work, as one free-text field. In-scope and out-of-scope prose both live here. | — |
|
|
330
|
+
| `requirements` | string[], unique | | Free-text statements of what the work must deliver. RFP-only, and deliberately not split into hard and soft. What a proposal must contain goes in the top-level prerequisites instead. | — |
|
|
331
|
+
|
|
332
|
+
<!-- END generated:fields -->
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Documentation conventions
|
|
337
|
+
|
|
338
|
+
The re-cut leaves several fields whose **boundaries are conventional rather than
|
|
339
|
+
schema-enforced**. Each is stated here because otherwise publishers guess and the data drifts.
|
|
340
|
+
|
|
341
|
+
| Convention | Ruling |
|
|
342
|
+
|---|---|
|
|
343
|
+
| **`sponsoringOrganizations` ≠ source of funds** | It is the **issuer/backer**, not necessarily where the money comes from. For donor-funded models the party running the process belongs in `operatingOrganizations`, while `sponsoringOrganizations` carries the display/issuing entity. **The money's actual origin is deliberately not modelled.** |
|
|
344
|
+
| **`applicationUrl` = whatever the submission channel is** | Including a **forum thread** when no portal exists. Clarifications go in `description`. **There is no submission-channel field** — the URL's *kind* is not typed. |
|
|
345
|
+
| **`prerequisites` vs. `rfp.requirements`** | **`prerequisites` = what a *proposal* must contain** (track record, approach, milestone plan, disclosures). **`rfp.requirements` = what the *work* must deliver.** Application-content vs. work-content. |
|
|
346
|
+
| **The three free-text siblings** | `prerequisites`, `resourceLinks` and `serviceAgreement` are all optional top-level strings and will be used interchangeably unless the boundary is written down — see below. |
|
|
347
|
+
| **`deadlines[]` selection** | Select by `label`, **never by array position**. |
|
|
348
|
+
| **`milestones[].amount` currency** | Optional, and it **MUST** follow the top-level `funding.currency` — a stated rule of the standard, not a soft convention. Schema-unenforceable (it crosses objects), so ingest **warns**. |
|
|
349
|
+
| **Milestone due dates** | There is no milestone date field. Where a publisher has due dates, they go in `criteria` as free text. |
|
|
350
|
+
| **Single-currency scope** | The single-currency rule governs the **program-level `funding` envelope only**; `bounty.reward`, `hackathon.prizes[].currency` and `accelerator.funding` each keep their own currency. |
|
|
351
|
+
|
|
352
|
+
### The three free-text siblings
|
|
353
|
+
|
|
354
|
+
`prerequisites`, `resourceLinks` and `serviceAgreement` are all optional top-level strings, and
|
|
355
|
+
each has one job:
|
|
356
|
+
|
|
357
|
+
- **`prerequisites`** — what an applicant must *put in the proposal* to be considered: track
|
|
358
|
+
record, proposed approach, a milestone plan, conflict-of-interest disclosures. If the sentence
|
|
359
|
+
starts "your application must include…", it belongs here.
|
|
360
|
+
- **`resourceLinks`** — supporting material a reader may want *alongside* the listing:
|
|
361
|
+
guidelines, past rounds, forum threads, the original posting. Deliberately **one free-form
|
|
362
|
+
string, not an array of URIs** — publishers paste what they have. If the sentence is a link
|
|
363
|
+
with a label, it belongs here.
|
|
364
|
+
- **`serviceAgreement`** — how a **long-term service engagement** works, where the opportunity is
|
|
365
|
+
one. Valid on any `fundingType`: an `rfp` or a `grant` carrying it reads as a service
|
|
366
|
+
engagement rather than a one-off award. **Presence of the field is the signal**; duration,
|
|
367
|
+
renewal and succession live in the prose if they matter. Deliberately **not filterable or
|
|
368
|
+
facetable** — a consumer can test for presence but cannot query "engagements ≥ 12 months".
|
|
369
|
+
|
|
370
|
+
Requirements on *the work itself* are none of these three: they are `rfp.requirements` (RFP
|
|
371
|
+
only) or, for a scope statement, `rfp.scope`.
|
|
372
|
+
|
|
373
|
+
### `deadlines[]` — select by label, never by position
|
|
374
|
+
|
|
375
|
+
Every date in the standard lives in one array. Registration windows, submission windows,
|
|
376
|
+
application deadlines, community-feedback windows **and event boundaries** (`event start`,
|
|
377
|
+
`event end`) all fold into `deadlines[]`, distinguished by `label`. There is one date model in
|
|
378
|
+
the standard, not two.
|
|
379
|
+
|
|
380
|
+
The direct consequence: a consumer answering *"when must I apply by?"* **MUST filter on `label`.**
|
|
381
|
+
Taking `deadlines[0]` is wrong and will return a hackathon's start date. This is the accepted
|
|
382
|
+
cost of one unified date model.
|
|
383
|
+
|
|
384
|
+
- Conventional labels are published in
|
|
385
|
+
[`registries/deadline-labels.json`](../../registries/deadline-labels.json) — `application`,
|
|
386
|
+
`community feedback`, `registration`, `submission`, `event start`, `event end`. Labels stay
|
|
387
|
+
free text; the registry keeps them comparable, and `rfphub-validate` warns on unregistered
|
|
388
|
+
ones.
|
|
389
|
+
- `date` is **required and non-null** when `type` is `"fixed"` (schema-enforced via `if`/`then`);
|
|
390
|
+
it is meaningless and normally omitted when `type` is `"rolling"`.
|
|
391
|
+
- There is deliberately **no event-anchored or relative form** — *"opens on X, then 30 days"* is
|
|
392
|
+
unexpressible. A publisher posts a fixed date when the window actually opens. An opportunity in
|
|
393
|
+
that state carries either no entry or a `rolling` one.
|
|
394
|
+
- There is deliberately **no `recurring` deadline type**. `grant.recurring` is the only carrier
|
|
395
|
+
of a recurring-round concept.
|
|
396
|
+
- **API consumers:** an array is not a sortable scalar. The recommended derivation is
|
|
397
|
+
`nextDeadlineAt` = the earliest future `type: "fixed"` date, computed at the API layer and
|
|
398
|
+
never stored in the standard. Records carrying only `rolling` entries have no such value; they
|
|
399
|
+
sort last and are **excluded** from deadline-window filters — an exclusion that must be
|
|
400
|
+
documented by the consumer, not left silent. A staleness job that auto-closes on a passed
|
|
401
|
+
deadline should key on *latest `fixed` deadline is in the past **and** no `rolling` entry
|
|
402
|
+
exists*; rolling programs must never auto-close.
|
|
403
|
+
|
|
404
|
+
### `milestones[]` and currency
|
|
405
|
+
|
|
406
|
+
`milestones[]` is optional and valid on **any** `fundingType`. **Array order is the milestone
|
|
407
|
+
sequence** — there is no `order`/`index` field, exactly as `sponsoringOrganizations[0]` carries
|
|
408
|
+
"primary".
|
|
409
|
+
|
|
410
|
+
- `milestones[].amount` **MUST** be denominated in the top-level `funding.currency`. A milestone
|
|
411
|
+
cannot be paid in a different asset from the envelope. JSON Schema cannot express this — the
|
|
412
|
+
two live in different objects — so **ingest warns** when `milestones[].amount` is present and
|
|
413
|
+
`funding.currency` is absent.
|
|
414
|
+
- There is **no milestone date field**. Due dates go into `criteria` as free text, consistent
|
|
415
|
+
with every other free-text decision in the standard.
|
|
416
|
+
- Milestone-based payment *is* `milestones[]` plus `grant.milestoneBased`. There is no separate
|
|
417
|
+
payment-schedule concept.
|
|
418
|
+
|
|
419
|
+
### Single currency — envelope only
|
|
420
|
+
|
|
421
|
+
The program-level `funding` envelope is **single-currency**: one `currency` scalar governs
|
|
422
|
+
`budget`, `allocated`, `minAward`, `maxAward` and `milestones[].amount`. There is no
|
|
423
|
+
`amounts[]`, no multi-asset envelope.
|
|
424
|
+
|
|
425
|
+
That rule is **scoped to the envelope**. `bounty.reward`, each `hackathon.prizes[].currency` and
|
|
426
|
+
`accelerator.funding` carry their own currency, because a prize pool can legitimately be
|
|
427
|
+
denominated differently from the program budget. The standard is single-currency *at the
|
|
428
|
+
envelope*, not end to end — this is a deliberate boundary, not an oversight.
|
|
429
|
+
|
|
430
|
+
Known cost, stated plainly: a program with **simultaneous caps in two assets** (e.g. a stablecoin
|
|
431
|
+
cap *and* a governance-token cap on the same round) cannot express both. Pick the primary
|
|
432
|
+
currency and put the second in `description`, or carry it under `extensions`. Both are lossy and
|
|
433
|
+
neither is filterable.
|
|
434
|
+
|
|
435
|
+
### `funding.allocated` is committed, not disbursed
|
|
436
|
+
|
|
437
|
+
`allocated` means money **committed to date** — not money paid out. Disbursement and delivery are
|
|
438
|
+
deliberately not modelled. `remaining` is **derived** as `budget − allocated` at the consumer
|
|
439
|
+
layer and never stored.
|
|
440
|
+
|
|
441
|
+
There is no `raised` field. A donor-crowdfunded opportunity therefore asserts its `budget`
|
|
442
|
+
regardless of how much has actually been raised, and a consumer cannot tell a fully-funded round
|
|
443
|
+
from one that has raised nothing. Publishers can note it in `description`; it is not filterable.
|
|
444
|
+
|
|
445
|
+
### Type blocks — one per opportunity
|
|
446
|
+
|
|
447
|
+
Every entry carries exactly one type-specific object under a key **equal to its `fundingType`
|
|
448
|
+
value**, so a consumer can always read `opportunity[opportunity.fundingType]` — a `hackathon`
|
|
449
|
+
entry has a `hackathon` object, a `vc_fund` entry has a `vc_fund` object.
|
|
450
|
+
|
|
451
|
+
The matching block is **required** for all six types (for grants it MAY be `{}`), and **no other
|
|
452
|
+
type block may be present**: a `grant` record carrying an `rfp` object **fails validation**. This
|
|
453
|
+
is enforced by the schema, not by convention, so `opportunity[opportunity.fundingType]` is a
|
|
454
|
+
guarantee rather than an expectation.
|
|
455
|
+
|
|
456
|
+
Service agreements are **not** a seventh type — they are the top-level `serviceAgreement` field,
|
|
457
|
+
orthogonal to `fundingType`.
|
|
458
|
+
|
|
459
|
+
### Self-identification
|
|
460
|
+
|
|
461
|
+
`$schema`, `@context` and `@type` are permitted at the top level despite
|
|
462
|
+
`additionalProperties: false`, so a document can name the contract it claims to conform to and be
|
|
463
|
+
consumed as linked data without a wrapper. They are **ignored by validation** — carrying a
|
|
464
|
+
`$schema` that points somewhere else does not change which schema a document is validated
|
|
465
|
+
against. See [`adr/0003`](../../../../adr/0003-instance-self-identification-and-version-pattern.md).
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Status semantics
|
|
470
|
+
|
|
471
|
+
`status` describes the **public lifecycle** of the opportunity, not editorial/review state:
|
|
472
|
+
|
|
473
|
+
| Value | Meaning |
|
|
474
|
+
|---|---|
|
|
475
|
+
| `upcoming` | Announced but not yet accepting applications. **Also the value for a pre-open posting** — see below. |
|
|
476
|
+
| `open` | Currently accepting applications. |
|
|
477
|
+
| `closed` | Deadline passed or no longer accepting. |
|
|
478
|
+
| `archived` | Withdrawn or retired. |
|
|
479
|
+
|
|
480
|
+
**There is no `draft` status.** A publisher wanting pre-open visibility uses `upcoming`; the
|
|
481
|
+
enum stays at four values. The reasoning is that a governing body does not publish a draft
|
|
482
|
+
opportunity to a public index — by the time an entry exists, it has been announced.
|
|
483
|
+
|
|
484
|
+
Review state (`pending`, `rejected`) and the verified/auto-approved distinction are **server-side
|
|
485
|
+
metadata**, not part of the public object.
|
|
486
|
+
|
|
487
|
+
Auto-closing is a consumer-side staleness concern, not a schema rule — see the deadline
|
|
488
|
+
derivation guidance above.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Delivery (API list vs detail)
|
|
493
|
+
|
|
494
|
+
The schema defines the **canonical, full** opportunity object — used for the detail endpoint,
|
|
495
|
+
exports, snapshots, and agent payloads. For bandwidth, **list/search responses return a lighter
|
|
496
|
+
projection**: core fields only, omitting the type-specific block
|
|
497
|
+
(`opportunity[opportunity.fundingType]`) and `extensions`. Clients fetch the full object from the
|
|
498
|
+
detail endpoint (`GET /v1/opportunities/:id`). This is an API-delivery concern — it does not
|
|
499
|
+
change the object's canonical schema.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Conformance
|
|
504
|
+
|
|
505
|
+
A document **conforms** to this version of the standard when it validates against
|
|
506
|
+
[`opportunity.schema.json`](./opportunity.schema.json).
|
|
507
|
+
|
|
508
|
+
An **implementation** conforms with respect to the published suite when every document in
|
|
509
|
+
[`conformance/v1.0.0/pass/`](../../conformance/v1.0.0/pass) validates and every document in
|
|
510
|
+
[`conformance/v1.0.0/fail/`](../../conformance/v1.0.0/fail) does not. The suite asserts nothing
|
|
511
|
+
about which error is reported, how many, or in what order. Passing it is **evidence of
|
|
512
|
+
conformance, not a definition of it** — the schema is the definition. Warnings from the advisory
|
|
513
|
+
tier (unregistered registry values, milestone amounts with no envelope currency) do **not** affect
|
|
514
|
+
conformance; a conforming document may raise warnings, which is the point of the split.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Open items
|
|
519
|
+
|
|
520
|
+
- **Canonical domain** — undecided. Identifiers are provisional but honest: `$id` dereferences
|
|
521
|
+
to the file in the repository, and `@vocab` is visibly marked `draft`. Both are stamped from
|
|
522
|
+
`spec.config.json`, so adopting a domain is a one-line change. See
|
|
523
|
+
[`STATUS.md`](./STATUS.md#known-issues-in-this-version).
|
|
524
|
+
- **Status granularity** — the four-value enum is the most-questioned part of the standard
|
|
525
|
+
(`in review`, `paused`, `awarded` have all been asked for). It remains open on its own terms;
|
|
526
|
+
the no-`draft` ruling above does not close it.
|
|
527
|
+
- **Level-of-effort / scope-complexity signal** — asked for, not modelled.
|
|
528
|
+
- **Cross-system dedup** — when the same opportunity is aggregated from more than one upstream
|
|
529
|
+
source, a merge-precedence policy is needed at the read/aggregation layer. The removal of a
|
|
530
|
+
source URL makes this a judgement call rather than a lookup.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Status of this version — RFP Hub Standard v1.0.0
|
|
2
|
+
|
|
3
|
+
*This section describes the status of this version of the standard at the time of publication.
|
|
4
|
+
Other documents may supersede it. A machine-readable index of all versions is at
|
|
5
|
+
[`schemas/index.json`](../index.json).*
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Version** | `1.0.0` |
|
|
10
|
+
| **Maturity** | **`draft`** |
|
|
11
|
+
| **Identifiers** | Stamped from [`spec.config.json`](../../spec.config.json) — **no canonical domain yet**, see Known issues |
|
|
12
|
+
| **Re-cut in place** | **2026-07-27** |
|
|
13
|
+
| **Supersedes** | none — this is the first version |
|
|
14
|
+
| **Superseded by** | none — this is the current version |
|
|
15
|
+
| **Feedback** | GitHub issues on [`The-RFP-Hub/the-rfp-hub`](https://github.com/The-RFP-Hub/the-rfp-hub/issues) |
|
|
16
|
+
| **License** | CC0 1.0 |
|
|
17
|
+
|
|
18
|
+
## Maturity: `draft`
|
|
19
|
+
|
|
20
|
+
`draft` means the shape is **usable and stable enough to publish against, but not frozen**. It
|
|
21
|
+
may still change without a new version directory while this maturity holds. Promotion to
|
|
22
|
+
`stable` freezes this directory: a `FROZEN` marker file lands in it and CI rejects any PR that
|
|
23
|
+
edits it thereafter — see [`PROCESS.md`](../../PROCESS.md).
|
|
24
|
+
|
|
25
|
+
Feature-level maturity is finer-grained than document-level maturity. Several fields in this
|
|
26
|
+
version are annotated `x-stability: provisional` in the schema (`serviceAgreement`,
|
|
27
|
+
`milestones[]`, `grant.programModel`) — they rest on narrower evidence than the rest and are the
|
|
28
|
+
most likely to change. Everything else is `x-stability: stable`.
|
|
29
|
+
|
|
30
|
+
## The honest paragraph
|
|
31
|
+
|
|
32
|
+
**Spec v1.0.0 was re-cut in place on 2026-07-27.** The contents published under this version
|
|
33
|
+
string **differ from the contents published under the same version string before that date**.
|
|
34
|
+
Documents that validated against the earlier bytes do **not** validate against these: fields were
|
|
35
|
+
renamed (`type` → `fundingType`), removed (`organization`, `source.url`, `closesAt`) and added,
|
|
36
|
+
and one-block-per-funding-type became a validation rule.
|
|
37
|
+
|
|
38
|
+
This is the thing semantic versioning exists to prevent. It was done deliberately, once, and on
|
|
39
|
+
a narrow basis: the standard had never been published to a package registry, no external
|
|
40
|
+
consumer had adopted it, and the milestone that would have declared it complete had not closed.
|
|
41
|
+
A version bump would have invented a migration story with nobody on the other end of it.
|
|
42
|
+
|
|
43
|
+
- **Full rationale and the alternatives considered:**
|
|
44
|
+
[`adr/0001-recut-v1.0.0-in-place.md`](../../../../adr/0001-recut-v1.0.0-in-place.md)
|
|
45
|
+
- **The field decisions themselves:** [`adr/0002-v-next-field-recut.md`](../../../../adr/0002-v-next-field-recut.md)
|
|
46
|
+
- **Field-by-field mapping:** the [field mapping table](../../CHANGELOG.md#field-mapping-old--new)
|
|
47
|
+
in `CHANGELOG.md`. No row is ever removed from it.
|
|
48
|
+
- **The pre-re-cut bytes are preserved** at the git tag
|
|
49
|
+
**`standard/spec-v1.0.0-precut-2026-07`**, created before the first re-cut commit. That tag is
|
|
50
|
+
the only way to recover the earlier shape.
|
|
51
|
+
|
|
52
|
+
**In-place re-cuts end here.** [`PROCESS.md`](../../PROCESS.md) states the rule that forbids a
|
|
53
|
+
second one, and the freeze workflow mechanises it so the policy does not depend on memory.
|
|
54
|
+
|
|
55
|
+
## Known issues in this version
|
|
56
|
+
|
|
57
|
+
- **There is no canonical domain yet, and the identifiers say so.** The project does not own a
|
|
58
|
+
domain, so nothing is minted on one. Instead:
|
|
59
|
+
- **`$id`** — for the schema, the metaschema and the registry entry schema — is a
|
|
60
|
+
`raw.githubusercontent.com` URL on the default branch. It **does dereference**, to exactly
|
|
61
|
+
the bytes shipped here. *Known limitation:* GitHub serves it as `text/plain`, not
|
|
62
|
+
`application/schema+json`. A URL that resolves to the real document with the wrong
|
|
63
|
+
Content-Type is strictly better than one that resolves to a parking page or to nothing.
|
|
64
|
+
- **`@vocab`** is `https://github.com/The-RFP-Hub/the-rfp-hub/ns/draft/rfp#` and **does not
|
|
65
|
+
dereference**. A vocabulary namespace must be versionless, so it cannot live under
|
|
66
|
+
`schemas/<version>/`, and no versionless document exists to point it at. The `draft` segment
|
|
67
|
+
mirrors this version's maturity so the IRI reads as provisional on sight, and the authority
|
|
68
|
+
is one the project demonstrably controls.
|
|
69
|
+
- **Swapping in the real domain is a one-line edit** to `baseUrl`/`vocabIri` in
|
|
70
|
+
[`spec.config.json`](../../spec.config.json) followed by `pnpm codegen`. Every identifier in
|
|
71
|
+
the package is stamped from that file; none is hand-written, and `pnpm check` fails if one
|
|
72
|
+
ever is.
|
|
73
|
+
- **Status granularity** — the four-value `status` enum is the most-questioned part of the
|
|
74
|
+
standard. It is unresolved, not settled.
|
|
75
|
+
- **`vc_fund` has no real-data coverage** — see [`BENCHMARK.md`](./BENCHMARK.md).
|
|
76
|
+
|
|
77
|
+
## How to comment
|
|
78
|
+
|
|
79
|
+
Open a GitHub issue. Substantive changes stay open for a minimum comment window before merge;
|
|
80
|
+
editorial corrections to informative documents do not need one. Both rules, and the errata
|
|
81
|
+
labels used to triage them, are in [`PROCESS.md`](../../PROCESS.md).
|