@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.
Files changed (77) hide show
  1. package/ARTIFACTS.md +85 -0
  2. package/CHANGELOG.md +264 -0
  3. package/LICENSE +121 -0
  4. package/NORMATIVE.md +77 -0
  5. package/PROCESS.md +239 -0
  6. package/README.md +83 -0
  7. package/conformance/README.md +52 -0
  8. package/conformance/v1.0.0/fail/deadline-fixed-date-null.json +22 -0
  9. package/conformance/v1.0.0/fail/deadline-fixed-without-date.json +21 -0
  10. package/conformance/v1.0.0/fail/eligibility-value-not-string.json +21 -0
  11. package/conformance/v1.0.0/fail/empty-sponsoring-organizations.json +11 -0
  12. package/conformance/v1.0.0/fail/missing-matching-type-block.json +14 -0
  13. package/conformance/v1.0.0/fail/missing-required-properties.json +5 -0
  14. package/conformance/v1.0.0/fail/missing-sponsoring-organizations.json +10 -0
  15. package/conformance/v1.0.0/fail/one-block-per-fundingtype.json +18 -0
  16. package/conformance/v1.0.0/fail/source-url-removed.json +17 -0
  17. package/conformance/v1.0.0/fail/status-not-in-enum.json +15 -0
  18. package/conformance/v1.0.0/fail/unknown-top-level-property.json +16 -0
  19. package/conformance/v1.0.0/fail/wrong-fundingtype-value.json +14 -0
  20. package/conformance/v1.0.0/fail/wrong-specversion.json +15 -0
  21. package/conformance/v1.0.0/pass/deadline-rolling-without-date.json +23 -0
  22. package/conformance/v1.0.0/pass/full-featured.json +137 -0
  23. package/conformance/v1.0.0/pass/grant-funding-mechanisms-array.json +27 -0
  24. package/conformance/v1.0.0/pass/grant.json +45 -0
  25. package/conformance/v1.0.0/pass/hackathon.json +67 -0
  26. package/conformance/v1.0.0/pass/minimal-required-only.json +15 -0
  27. package/conformance/v1.0.0/pass/self-identification.json +18 -0
  28. package/conformance/v1.0.0/pass/source-empty-object.json +30 -0
  29. package/dist/index.cjs +902 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +661 -0
  32. package/dist/index.d.ts +661 -0
  33. package/dist/index.js +896 -0
  34. package/dist/index.js.map +1 -0
  35. package/meta/rfphub-schema.meta.json +131 -0
  36. package/package.json +56 -0
  37. package/registries/deadline-labels.json +38 -0
  38. package/registries/eligibility-keys.json +46 -0
  39. package/registries/entry.schema.json +48 -0
  40. package/registries/index.json +44 -0
  41. package/registries/program-models.json +26 -0
  42. package/schemas/index.json +12 -0
  43. package/schemas/v1.0.0/BENCHMARK.md +160 -0
  44. package/schemas/v1.0.0/CROSSWALK.md +200 -0
  45. package/schemas/v1.0.0/FIELDS.md +530 -0
  46. package/schemas/v1.0.0/STATUS.md +81 -0
  47. package/schemas/v1.0.0/context.jsonld +86 -0
  48. package/schemas/v1.0.0/examples/01-hackathon-fundingmap_1200.json +181 -0
  49. package/schemas/v1.0.0/examples/02-hackathon-fundingmap_1197.json +122 -0
  50. package/schemas/v1.0.0/examples/03-hackathon-fundingmap_1095.json +114 -0
  51. package/schemas/v1.0.0/examples/04-grant-fundingmap_1459.json +67 -0
  52. package/schemas/v1.0.0/examples/05-hackathon-fundingmap_1210.json +97 -0
  53. package/schemas/v1.0.0/examples/06-hackathon-fundingmap_1340.json +91 -0
  54. package/schemas/v1.0.0/examples/07-hackathon-fundingmap_1093.json +89 -0
  55. package/schemas/v1.0.0/examples/08-hackathon-fundingmap_1091.json +89 -0
  56. package/schemas/v1.0.0/examples/09-hackathon-fundingmap_1094.json +89 -0
  57. package/schemas/v1.0.0/examples/10-grant-fundingmap_600.json +65 -0
  58. package/schemas/v1.0.0/examples/11-hackathon-fundingmap_1389.json +83 -0
  59. package/schemas/v1.0.0/examples/12-hackathon-fundingmap_1356.json +83 -0
  60. package/schemas/v1.0.0/examples/13-grant-fundingmap_1458.json +59 -0
  61. package/schemas/v1.0.0/examples/14-grant-fundingmap_1059.json +57 -0
  62. package/schemas/v1.0.0/examples/15-hackathon-fundingmap_1430.json +82 -0
  63. package/schemas/v1.0.0/examples/16-hackathon-fundingmap_1092.json +79 -0
  64. package/schemas/v1.0.0/examples/17-hackathon-fundingmap_1277.json +75 -0
  65. package/schemas/v1.0.0/examples/18-hackathon-fundingmap_1302.json +72 -0
  66. package/schemas/v1.0.0/examples/19-hackathon-fundingmap_1231.json +76 -0
  67. package/schemas/v1.0.0/examples/20-hackathon-fundingmap_1235.json +74 -0
  68. package/schemas/v1.0.0/examples/21-hackathon-fundingmap_1219.json +73 -0
  69. package/schemas/v1.0.0/examples/22-hackathon-fundingmap_1254.json +72 -0
  70. package/schemas/v1.0.0/examples/23-grant-fundingmap_491.json +57 -0
  71. package/schemas/v1.0.0/examples/24-hackathon-fundingmap_1358.json +73 -0
  72. package/schemas/v1.0.0/examples/25-grant-fundingmap_1021.json +57 -0
  73. package/schemas/v1.0.0/examples/26-bounty-fundingmap_1453.json +57 -0
  74. package/schemas/v1.0.0/examples/27-accelerator-fundingmap_1291.json +56 -0
  75. package/schemas/v1.0.0/examples/28-rfp-fundingmap_1242.json +43 -0
  76. package/schemas/v1.0.0/opportunity.schema.json +758 -0
  77. package/spec.config.json +11 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,902 @@
1
+ 'use strict';
2
+
3
+ // schemas/v1.0.0/opportunity.schema.json
4
+ var opportunity_schema_default = {
5
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6
+ $id: "https://raw.githubusercontent.com/The-RFP-Hub/the-rfp-hub/main/packages/standard/schemas/v1.0.0/opportunity.schema.json",
7
+ title: "RFP Hub Opportunity",
8
+ description: "RFP Hub Standard v1.0.0 \u2014 the normative definition of a funding opportunity in the Ethereum ecosystem. A document conforms to this version of the standard when it validates against this file. Covers six funding types: grant, hackathon, bounty, accelerator, vc_fund, rfp. Aligns conceptually with DAOIP-5 (Grants Metadata) and schema.org/Grant. Published under CC0 1.0 Universal. The field reference is FIELDS.md, the normative/informative split is NORMATIVE.md, and both ship alongside this file in the @rfp-hub/standard package and at https://github.com/The-RFP-Hub/the-rfp-hub.",
9
+ $comment: "AUTHORING NOTES (not for consumers). (1) `additionalProperties: false` at this level is safe alongside `allOf` only because no branch introduces a property name: each branch uses `required` and `not` exclusively, and every name they mention is declared in this object's `properties`. That is the classic 2020-12 trap avoided by construction rather than by `unevaluatedProperties`, which is deliberately not used \u2014 it would relax this object from closed to 'closed after evaluation' for no gain. Any future branch that adds `properties` MUST be paired with a switch to `unevaluatedProperties: false` here. The same reasoning applies inside $defs/deadline, whose `then` narrows `date`, a property already declared in its own `properties`. (2) Absence of `x-stability` means `stable`; only fields resting on narrow evidence are annotated `provisional`. (3) Property order is grouped identity \u2192 classification \u2192 organisations \u2192 provenance \u2192 content \u2192 money \u2192 time \u2192 type blocks \u2192 extensions, and `required` follows property order, so diffs stay reviewable.",
10
+ type: "object",
11
+ additionalProperties: false,
12
+ "x-stability": "stable",
13
+ "x-since": "1.0.0",
14
+ required: [
15
+ "specVersion",
16
+ "id",
17
+ "fundingType",
18
+ "title",
19
+ "description",
20
+ "status",
21
+ "sponsoringOrganizations",
22
+ "source"
23
+ ],
24
+ properties: {
25
+ $schema: {
26
+ type: "string",
27
+ format: "uri",
28
+ description: "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 \u2014 naming a different schema here does not change which schema the document is validated against.",
29
+ examples: ["https://raw.githubusercontent.com/The-RFP-Hub/the-rfp-hub/main/packages/standard/schemas/v1.0.0/opportunity.schema.json"]
30
+ },
31
+ "@context": {
32
+ anyOf: [{ type: "string" }, { type: "object" }, { type: "array" }],
33
+ description: "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 \u2014 the standard makes no claim about its contents.",
34
+ examples: ["https://raw.githubusercontent.com/The-RFP-Hub/the-rfp-hub/main/packages/standard/schemas/v1.0.0/context.jsonld"]
35
+ },
36
+ "@type": {
37
+ anyOf: [{ type: "string" }, { type: "array" }],
38
+ description: "Optional JSON-LD type, or an array of types. Permitted so an instance can be consumed as linked data; ignored by validation.",
39
+ examples: ["schema:Grant"]
40
+ },
41
+ specVersion: {
42
+ type: "string",
43
+ const: "1.0.0",
44
+ description: "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.",
45
+ examples: ["1.0.0"]
46
+ },
47
+ id: {
48
+ type: "string",
49
+ minLength: 1,
50
+ maxLength: 128,
51
+ pattern: "^[A-Za-z0-9._:-]+$",
52
+ description: "Stable, unique identifier for the opportunity within the Hub. Immutable once assigned. A namespaced form is recommended but not required.",
53
+ examples: ["filecoin:propgf-batch-3", "fundingmap:1242"]
54
+ },
55
+ fundingType: {
56
+ type: "string",
57
+ enum: ["grant", "hackathon", "bounty", "accelerator", "vc_fund", "rfp"],
58
+ description: "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' \u2192 a 'hackathon' object, 'vc_fund' \u2192 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.",
59
+ examples: ["grant", "rfp"]
60
+ },
61
+ title: {
62
+ type: "string",
63
+ minLength: 1,
64
+ maxLength: 300,
65
+ description: "Human-readable name of the opportunity."
66
+ },
67
+ description: {
68
+ type: "string",
69
+ minLength: 1,
70
+ description: "Full description of the opportunity. Markdown is permitted; consumers are advised to treat it as untrusted and sanitise before rendering."
71
+ },
72
+ summary: {
73
+ type: ["string", "null"],
74
+ maxLength: 500,
75
+ description: "Optional short teaser (roughly one or two sentences) for list and card views."
76
+ },
77
+ status: {
78
+ type: "string",
79
+ enum: ["upcoming", "open", "closed", "archived"],
80
+ description: "Lifecycle status of the opportunity. 'upcoming' = announced but not yet accepting applications, and also the value for a pre-open posting \u2014 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 \u2014 it is server-side metadata.",
81
+ examples: ["open"]
82
+ },
83
+ sponsoringOrganizations: {
84
+ type: "array",
85
+ minItems: 1,
86
+ items: { $ref: "#/$defs/organization" },
87
+ description: "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 \u2014 for donor-funded models the money's origin is deliberately not modelled, and the party running the process belongs in operatingOrganizations instead.",
88
+ $comment: "Deliberately not uniqueItems: co-sponsors are distinguished by their own fields, and enforcing uniqueness on a whole entity object catches nothing useful while making a partially-populated duplicate a hard error."
89
+ },
90
+ operatingOrganizations: {
91
+ type: "array",
92
+ items: { $ref: "#/$defs/organization" },
93
+ description: "The organisations that actually run intake and process \u2014 for example an operator running the application funnel on a funder's behalf. May be absent or empty when the sponsor also operates."
94
+ },
95
+ source: {
96
+ $ref: "#/$defs/provenance",
97
+ description: '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.'
98
+ },
99
+ ecosystems: {
100
+ type: "array",
101
+ items: { type: "string", minLength: 1 },
102
+ uniqueItems: true,
103
+ description: "Ethereum-family ecosystems this opportunity targets. The RFP Hub is ETH-scoped, but this is an open, extensible list \u2014 not a closed enum, and deliberately not registry-governed either \u2014 so L2s and ETH-adjacent ecosystems are first-class and a newly launched one needs no process.",
104
+ examples: [["Optimism"], ["Ethereum", "Base"]]
105
+ },
106
+ networks: {
107
+ type: "array",
108
+ items: { type: "string", minLength: 1 },
109
+ uniqueItems: true,
110
+ description: "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.",
111
+ examples: [["optimism"], ["ethereum-mainnet", "base"]]
112
+ },
113
+ categories: {
114
+ type: "array",
115
+ items: { type: "string", minLength: 1 },
116
+ uniqueItems: true,
117
+ description: "Topical categories. Free text.",
118
+ examples: [["DeFi", "Public Goods"], ["Infrastructure", "Developer Tooling"]]
119
+ },
120
+ tags: {
121
+ type: "array",
122
+ items: { type: "string", minLength: 1 },
123
+ uniqueItems: true,
124
+ description: "Free-form tags for search and faceting."
125
+ },
126
+ eligibility: {
127
+ type: "object",
128
+ additionalProperties: { type: "string" },
129
+ description: "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 \u2014 using them keeps the field comparable across publishers, and unregistered keys stay valid.",
130
+ examples: [
131
+ {
132
+ stage: "Pre-seed to Series A",
133
+ geography: "Global, excluding sanctioned jurisdictions",
134
+ entityType: "Registered legal entity required before payout"
135
+ }
136
+ ]
137
+ },
138
+ prerequisites: {
139
+ type: ["string", "null"],
140
+ description: "Free text describing what a proposal must contain to be considered \u2014 track record, approach, milestone plan, disclosures. Distinct from rfp.requirements, which describes what the work must deliver.",
141
+ examples: [
142
+ "A proposal must include prior track record, technical approach, a milestone plan with amounts, and disclosure of any conflicts."
143
+ ]
144
+ },
145
+ resourceLinks: {
146
+ type: ["string", "null"],
147
+ description: "A single free-form string of supporting links and references \u2014 guidelines, past rounds, forum threads, original postings. Deliberately one string rather than an array of URIs, because publishers paste what they have.",
148
+ examples: ["Original posting: https://forum.example.org/t/opsec-rfp/1 \xB7 Prior round retrospective: https://example.org/retro"]
149
+ },
150
+ serviceAgreement: {
151
+ type: ["string", "null"],
152
+ "x-stability": "provisional",
153
+ "x-since": "1.0.0",
154
+ description: "Free text describing how a service-agreement arrangement works. Valid on any fundingType \u2014 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.",
155
+ examples: [
156
+ "Runs as a rolling twelve-month engagement rather than a one-off deliverable; renewal is decided at the end of each term by the sponsoring foundation.",
157
+ "Ongoing service-provider relationship, scoped annually, with a published scope-of-work and quarterly reporting."
158
+ ]
159
+ },
160
+ applicationUrl: {
161
+ type: ["string", "null"],
162
+ format: "uri",
163
+ description: "URL where applicants submit or apply \u2014 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.",
164
+ examples: ["https://example.org/apply", "https://forum.example.org/t/rfp-thread/42"]
165
+ },
166
+ website: {
167
+ type: ["string", "null"],
168
+ format: "uri",
169
+ description: "Primary website for the opportunity or program."
170
+ },
171
+ logoUrl: {
172
+ type: ["string", "null"],
173
+ format: "uri",
174
+ description: "URL of the program or organisation logo image."
175
+ },
176
+ bannerUrl: {
177
+ type: ["string", "null"],
178
+ format: "uri",
179
+ description: "URL of a banner or hero image."
180
+ },
181
+ socialLinks: {
182
+ $ref: "#/$defs/socialLinks",
183
+ description: "Social and community links for the opportunity or program."
184
+ },
185
+ funding: {
186
+ $ref: "#/$defs/funding",
187
+ description: "Program-level funding envelope: single currency, total budget, amount committed to date, and the per-award range."
188
+ },
189
+ milestones: {
190
+ type: "array",
191
+ items: { $ref: "#/$defs/milestone" },
192
+ "x-stability": "provisional",
193
+ "x-since": "1.0.0",
194
+ description: "Optional milestone sequence, valid on any fundingType. Array order is the milestone sequence \u2014 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.",
195
+ $comment: "Deliberately not uniqueItems: every property of a milestone is optional, so two sparsely-populated entries can legitimately be byte-identical while denoting different milestones distinguished only by position."
196
+ },
197
+ opensAt: {
198
+ type: ["string", "null"],
199
+ format: "date-time",
200
+ description: "RFC 3339 timestamp when applications open."
201
+ },
202
+ deadlines: {
203
+ type: "array",
204
+ items: { $ref: "#/$defs/deadline" },
205
+ uniqueItems: true,
206
+ description: "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.)",
207
+ examples: [
208
+ [
209
+ { type: "fixed", date: "2026-08-15T23:59:59.000Z", label: "community feedback" },
210
+ { type: "fixed", date: "2026-09-30T23:59:59.000Z", label: "application" }
211
+ ]
212
+ ]
213
+ },
214
+ postedAt: {
215
+ type: ["string", "null"],
216
+ format: "date-time",
217
+ description: "RFC 3339 timestamp when the opportunity was first publicly announced at the source."
218
+ },
219
+ createdAt: {
220
+ type: ["string", "null"],
221
+ format: "date-time",
222
+ description: "RFC 3339 timestamp when this entry was created in the Hub."
223
+ },
224
+ updatedAt: {
225
+ type: ["string", "null"],
226
+ format: "date-time",
227
+ description: "RFC 3339 timestamp when this entry was last modified in the Hub."
228
+ },
229
+ grant: {
230
+ $ref: "#/$defs/grant",
231
+ description: "Grant-specific fields. Required, possibly as an empty object, when fundingType is 'grant'; forbidden otherwise."
232
+ },
233
+ hackathon: {
234
+ $ref: "#/$defs/hackathon",
235
+ description: "Hackathon-specific fields. Required when fundingType is 'hackathon'; forbidden otherwise."
236
+ },
237
+ bounty: {
238
+ $ref: "#/$defs/bounty",
239
+ description: "Bounty-specific fields. Required when fundingType is 'bounty'; forbidden otherwise."
240
+ },
241
+ accelerator: {
242
+ $ref: "#/$defs/accelerator",
243
+ description: "Accelerator-specific fields. Required when fundingType is 'accelerator'; forbidden otherwise."
244
+ },
245
+ vc_fund: {
246
+ $ref: "#/$defs/vcFund",
247
+ description: "VC-fund-specific fields. Required when fundingType is 'vc_fund'; forbidden otherwise."
248
+ },
249
+ rfp: {
250
+ $ref: "#/$defs/rfp",
251
+ description: "RFP-specific fields. Required when fundingType is 'rfp'; forbidden otherwise."
252
+ },
253
+ extensions: {
254
+ type: "object",
255
+ additionalProperties: true,
256
+ description: "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.",
257
+ $comment: "Top level only \u2014 the organization $def deliberately has no equivalent. The asymmetry is a decision, not an oversight.",
258
+ examples: [{ "mysource.internalId": "OPP-4821" }]
259
+ }
260
+ },
261
+ allOf: [
262
+ {
263
+ $comment: "One block per funding type. Each branch requires the matching block and forbids all five others, so `opportunity[opportunity.fundingType]` is a guarantee rather than an expectation. Branches introduce no property names \u2014 see the root $comment.",
264
+ if: { properties: { fundingType: { const: "grant" } }, required: ["fundingType"] },
265
+ then: {
266
+ required: ["grant"],
267
+ not: {
268
+ anyOf: [
269
+ { required: ["hackathon"] },
270
+ { required: ["bounty"] },
271
+ { required: ["accelerator"] },
272
+ { required: ["vc_fund"] },
273
+ { required: ["rfp"] }
274
+ ]
275
+ }
276
+ }
277
+ },
278
+ {
279
+ if: { properties: { fundingType: { const: "hackathon" } }, required: ["fundingType"] },
280
+ then: {
281
+ required: ["hackathon"],
282
+ not: {
283
+ anyOf: [
284
+ { required: ["grant"] },
285
+ { required: ["bounty"] },
286
+ { required: ["accelerator"] },
287
+ { required: ["vc_fund"] },
288
+ { required: ["rfp"] }
289
+ ]
290
+ }
291
+ }
292
+ },
293
+ {
294
+ if: { properties: { fundingType: { const: "bounty" } }, required: ["fundingType"] },
295
+ then: {
296
+ required: ["bounty"],
297
+ not: {
298
+ anyOf: [
299
+ { required: ["grant"] },
300
+ { required: ["hackathon"] },
301
+ { required: ["accelerator"] },
302
+ { required: ["vc_fund"] },
303
+ { required: ["rfp"] }
304
+ ]
305
+ }
306
+ }
307
+ },
308
+ {
309
+ if: { properties: { fundingType: { const: "accelerator" } }, required: ["fundingType"] },
310
+ then: {
311
+ required: ["accelerator"],
312
+ not: {
313
+ anyOf: [
314
+ { required: ["grant"] },
315
+ { required: ["hackathon"] },
316
+ { required: ["bounty"] },
317
+ { required: ["vc_fund"] },
318
+ { required: ["rfp"] }
319
+ ]
320
+ }
321
+ }
322
+ },
323
+ {
324
+ if: { properties: { fundingType: { const: "vc_fund" } }, required: ["fundingType"] },
325
+ then: {
326
+ required: ["vc_fund"],
327
+ not: {
328
+ anyOf: [
329
+ { required: ["grant"] },
330
+ { required: ["hackathon"] },
331
+ { required: ["bounty"] },
332
+ { required: ["accelerator"] },
333
+ { required: ["rfp"] }
334
+ ]
335
+ }
336
+ }
337
+ },
338
+ {
339
+ if: { properties: { fundingType: { const: "rfp" } }, required: ["fundingType"] },
340
+ then: {
341
+ required: ["rfp"],
342
+ not: {
343
+ anyOf: [
344
+ { required: ["grant"] },
345
+ { required: ["hackathon"] },
346
+ { required: ["bounty"] },
347
+ { required: ["accelerator"] },
348
+ { required: ["vc_fund"] }
349
+ ]
350
+ }
351
+ }
352
+ }
353
+ ],
354
+ $defs: {
355
+ organization: {
356
+ type: "object",
357
+ title: "Organization",
358
+ additionalProperties: false,
359
+ required: ["name"],
360
+ description: "An organisation sponsoring or operating the opportunity. Embedded on an opportunity as a descriptive summary; the same shape is the standalone Organization directory record.",
361
+ $comment: "Two deliberate absences, both decisions rather than oversights. There is no `verified` flag: verification belongs to the publishing relationship \u2014 a user permissioned on a verified org \u2014 not to the organisation itself. There is no `extensions` object: extensions are top level only.",
362
+ properties: {
363
+ name: {
364
+ type: "string",
365
+ minLength: 1,
366
+ maxLength: 256,
367
+ description: "Display name of the organisation."
368
+ },
369
+ slug: {
370
+ type: ["string", "null"],
371
+ pattern: "^[a-z0-9-]+$",
372
+ description: "Lowercase URL-safe identifier, and also the organisation's namespace.",
373
+ examples: ["example-foundation"]
374
+ },
375
+ type: {
376
+ type: ["string", "null"],
377
+ enum: ["foundation", "dao", "company", "protocol", "program", "individual", "other", null],
378
+ description: "Kind of entity."
379
+ },
380
+ description: { type: ["string", "null"], description: "Short description of the organisation." },
381
+ website: { type: ["string", "null"], format: "uri", description: "The organisation's primary website." },
382
+ logoUrl: { type: ["string", "null"], format: "uri", description: "URL of the organisation's logo image." },
383
+ bannerUrl: { type: ["string", "null"], format: "uri", description: "URL of the organisation's banner or hero image." },
384
+ socialLinks: { $ref: "#/$defs/socialLinks", description: "Social and community links for the organisation." },
385
+ ecosystems: {
386
+ type: "array",
387
+ items: { type: "string", minLength: 1 },
388
+ uniqueItems: true,
389
+ description: "Ethereum-family ecosystems the organisation operates in. Same open list as the top-level field."
390
+ },
391
+ contacts: {
392
+ type: "array",
393
+ items: { $ref: "#/$defs/contact" },
394
+ description: "Named contact routes into the organisation. Optional, and every field of every entry is optional too."
395
+ }
396
+ }
397
+ },
398
+ contact: {
399
+ type: "object",
400
+ title: "Contact",
401
+ additionalProperties: false,
402
+ description: "A named contact route into the organisation. Every property is optional and there is no minimum-one-identifier constraint, so `{}` validates \u2014 deliberately, because not every publisher can or will name a person.",
403
+ properties: {
404
+ name: { type: ["string", "null"], description: "The person's name." },
405
+ role: {
406
+ type: ["string", "null"],
407
+ description: "Role in the program.",
408
+ examples: ["Steward", "Program owner", "Grants Manager"]
409
+ },
410
+ telegram: {
411
+ type: ["string", "null"],
412
+ description: "Telegram handle. A handle rather than a URL \u2014 unlike socialLinks.telegram, which is a link.",
413
+ examples: ["@example_steward"]
414
+ },
415
+ email: { type: ["string", "null"], format: "email", description: "Email address." }
416
+ }
417
+ },
418
+ provenance: {
419
+ type: "object",
420
+ title: "Provenance",
421
+ additionalProperties: false,
422
+ description: "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.",
423
+ properties: {
424
+ publisher: {
425
+ type: ["string", "null"],
426
+ description: "Namespace \u2014 an organisation slug \u2014 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.",
427
+ examples: ["filecoin"]
428
+ },
429
+ submittedBy: {
430
+ type: ["string", "null"],
431
+ description: "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."
432
+ },
433
+ submittedAt: {
434
+ type: ["string", "null"],
435
+ format: "date-time",
436
+ description: "RFC 3339 timestamp of when the entry was submitted or published to the Hub. Pairs with submittedBy."
437
+ },
438
+ ingestedVia: {
439
+ type: ["string", "null"],
440
+ enum: ["publisher_api", "submission", "scrape", "import", "outbox", null],
441
+ description: "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."
442
+ },
443
+ originalId: { type: ["string", "null"], description: "Identifier of this opportunity in the source system." },
444
+ verifiedAgainstSource: {
445
+ type: ["boolean", "null"],
446
+ description: "Whether the entry's fields were verified against the live opportunity by the verification-assist job. null means not yet checked."
447
+ },
448
+ verifiedAt: {
449
+ type: ["string", "null"],
450
+ format: "date-time",
451
+ description: "RFC 3339 timestamp of the last verification. Record-level only \u2014 there is no per-field freshness."
452
+ },
453
+ snapshotUrl: {
454
+ type: ["string", "null"],
455
+ format: "uri",
456
+ description: "IPFS or archived snapshot of the opportunity taken at verification time."
457
+ }
458
+ }
459
+ },
460
+ socialLinks: {
461
+ type: "object",
462
+ title: "Social links",
463
+ additionalProperties: false,
464
+ description: "Social and community links. Every value is a full URL, not a handle.",
465
+ properties: {
466
+ twitter: { type: ["string", "null"], format: "uri", description: "Link to the X/Twitter profile." },
467
+ discord: { type: ["string", "null"], format: "uri", description: "Discord server invite or channel link." },
468
+ github: { type: ["string", "null"], format: "uri", description: "Link to the GitHub organisation or repository." },
469
+ telegram: { type: ["string", "null"], format: "uri", description: "Link to the Telegram group or channel." },
470
+ farcaster: { type: ["string", "null"], format: "uri", description: "Link to the Farcaster profile or channel." },
471
+ forum: { type: ["string", "null"], format: "uri", description: "Link to the governance or community forum." },
472
+ blog: { type: ["string", "null"], format: "uri", description: "Link to the blog or announcements feed." }
473
+ }
474
+ },
475
+ funding: {
476
+ type: "object",
477
+ title: "Funding envelope",
478
+ additionalProperties: false,
479
+ description: "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.",
480
+ properties: {
481
+ currency: {
482
+ type: ["string", "null"],
483
+ maxLength: 16,
484
+ description: "ISO 4217 code or token symbol for the amounts below, and for milestones[].amount.",
485
+ examples: ["USD", "OP"]
486
+ },
487
+ budget: { type: ["number", "null"], minimum: 0, description: "Total program budget in major units." },
488
+ allocated: {
489
+ type: ["number", "null"],
490
+ minimum: 0,
491
+ description: "Amount committed to date in major units \u2014 committed, not necessarily disbursed. Disbursement and delivery are not modelled."
492
+ },
493
+ minAward: { type: ["number", "null"], minimum: 0, description: "Minimum individual award in major units." },
494
+ maxAward: { type: ["number", "null"], minimum: 0, description: "Maximum individual award in major units." }
495
+ }
496
+ },
497
+ monetaryAmount: {
498
+ type: "object",
499
+ title: "Monetary amount",
500
+ additionalProperties: false,
501
+ required: ["amount", "currency"],
502
+ description: "A single amount with its own currency, used where a sub-block is denominated independently of the program envelope.",
503
+ properties: {
504
+ amount: {
505
+ type: "number",
506
+ minimum: 0,
507
+ description: "Amount in major units of the currency, so 2000000 means 2,000,000 USD rather than cents."
508
+ },
509
+ currency: {
510
+ type: "string",
511
+ minLength: 1,
512
+ maxLength: 16,
513
+ description: "ISO 4217 fiat code such as USD or EUR, or a token symbol such as ETH, OP or USDC."
514
+ }
515
+ }
516
+ },
517
+ amountRange: {
518
+ type: "object",
519
+ title: "Amount range",
520
+ additionalProperties: false,
521
+ description: "A lower and upper bound with a shared currency. Either bound may be absent, expressing an open-ended range.",
522
+ properties: {
523
+ min: { type: ["number", "null"], minimum: 0, description: "Lower bound in major units." },
524
+ max: { type: ["number", "null"], minimum: 0, description: "Upper bound in major units." },
525
+ currency: { type: ["string", "null"], maxLength: 16, description: "ISO 4217 code or token symbol for both bounds." }
526
+ }
527
+ },
528
+ deadline: {
529
+ type: "object",
530
+ title: "Deadline",
531
+ additionalProperties: false,
532
+ required: ["type"],
533
+ description: "A single deadline or event boundary. A 'fixed' entry carries a date; 'rolling' means applications are accepted continuously.",
534
+ $comment: "There is deliberately no event-anchored or relative form ('opens on X, then 30 days'): an unanchored window does not denote a point in time, so the publisher posts a fixed date when the window actually opens. 'recurring' is likewise out of the enum \u2014 grant.recurring is the only carrier of that concept.",
535
+ properties: {
536
+ type: {
537
+ type: "string",
538
+ enum: ["fixed", "rolling"],
539
+ description: "Whether this deadline is a fixed point in time or an open-ended rolling window."
540
+ },
541
+ date: {
542
+ type: ["string", "null"],
543
+ format: "date-time",
544
+ description: "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'."
545
+ },
546
+ label: {
547
+ type: ["string", "null"],
548
+ maxLength: 120,
549
+ description: "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.",
550
+ examples: ["application", "community feedback", "registration", "submission", "event start", "event end"]
551
+ }
552
+ },
553
+ if: { properties: { type: { const: "fixed" } }, required: ["type"] },
554
+ then: { required: ["date"], properties: { date: { type: "string" } } },
555
+ examples: [
556
+ { type: "fixed", date: "2026-09-30T23:59:59.000Z", label: "application" },
557
+ { type: "rolling", label: "application" }
558
+ ]
559
+ },
560
+ milestone: {
561
+ type: "object",
562
+ title: "Milestone",
563
+ additionalProperties: false,
564
+ description: "One milestone in an opportunity's milestone sequence. Every property is optional \u2014 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.",
565
+ properties: {
566
+ title: { type: ["string", "null"], description: "Short name of the milestone." },
567
+ amount: {
568
+ type: ["number", "null"],
569
+ minimum: 0,
570
+ description: "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."
571
+ },
572
+ criteria: { type: ["string", "null"], description: "Free-text acceptance criteria, including any due date." }
573
+ },
574
+ examples: [
575
+ {
576
+ title: "Ratings framework published",
577
+ amount: 5e4,
578
+ criteria: "Published methodology, reviewed by the coalition. Due within 8 weeks of kickoff."
579
+ }
580
+ ]
581
+ },
582
+ grant: {
583
+ type: "object",
584
+ title: "Grant details",
585
+ additionalProperties: false,
586
+ description: "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.",
587
+ properties: {
588
+ fundingMechanisms: {
589
+ type: "array",
590
+ uniqueItems: true,
591
+ items: {
592
+ type: "string",
593
+ enum: ["retroactive", "proactive", "streaming", "quadratic", "matching", "other"]
594
+ },
595
+ description: "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.",
596
+ examples: [["proactive"], ["proactive", "matching"], ["retroactive", "quadratic"]]
597
+ },
598
+ programModel: {
599
+ type: ["string", "null"],
600
+ "x-stability": "provisional",
601
+ "x-since": "1.0.0",
602
+ description: "The operating model of the program, as distinct from the funding instrument. An open list rather than a closed enum \u2014 conventional values are published in registries/program-models.json, and a publisher's own vocabulary is valid without a schema change.",
603
+ examples: ["grant", "program", "infra", "incentives"]
604
+ },
605
+ milestoneBased: {
606
+ type: ["boolean", "null"],
607
+ description: "Whether disbursement is tied to milestones. Pairs with the top-level milestones array."
608
+ },
609
+ recurring: {
610
+ type: ["boolean", "null"],
611
+ description: "Whether the program runs in recurring rounds or seasons.",
612
+ $comment: "The only carrier of a 'recurring round' concept: deadlines[] has no 'recurring' type, so a recurring program is expressed here plus a rolling or fixed deadline."
613
+ }
614
+ }
615
+ },
616
+ hackathon: {
617
+ type: "object",
618
+ title: "Hackathon details",
619
+ additionalProperties: false,
620
+ description: "Hackathon-specific attributes. All dates \u2014 registration, submission, event start and event end \u2014 live in the shared top-level deadlines array, distinguished by label.",
621
+ properties: {
622
+ location: { type: ["string", "null"], description: "Physical location, or null for a fully online event." },
623
+ online: { type: ["boolean", "null"], description: "Whether the event is also, or only, held online." },
624
+ tracks: {
625
+ type: "array",
626
+ items: { type: "string", minLength: 1 },
627
+ uniqueItems: true,
628
+ description: "Named tracks or themes participants can build against.",
629
+ examples: [["DeFi", "Infra"]]
630
+ },
631
+ prizes: {
632
+ type: "array",
633
+ items: { $ref: "#/$defs/prize" },
634
+ description: "The prize pool, one entry per prize. Each prize carries its own currency.",
635
+ $comment: "Deliberately not uniqueItems: identical prizes are routine \u2014 several equal-value track prizes, or repeated runner-up awards \u2014 so uniqueness would reject real data."
636
+ },
637
+ teamSize: { $ref: "#/$defs/teamSize", description: "Permitted team size range." }
638
+ }
639
+ },
640
+ prize: {
641
+ type: "object",
642
+ title: "Hackathon prize",
643
+ additionalProperties: false,
644
+ required: ["amount", "currency"],
645
+ description: "A single hackathon prize, optionally attributed to a track.",
646
+ properties: {
647
+ track: { type: ["string", "null"], description: "Track this prize belongs to, where prizes are tracked separately." },
648
+ amount: { type: "number", minimum: 0, description: "Prize amount in major units." },
649
+ currency: { type: "string", minLength: 1, maxLength: 16, description: "ISO 4217 code or token symbol for this prize." }
650
+ }
651
+ },
652
+ teamSize: {
653
+ type: "object",
654
+ title: "Team size range",
655
+ additionalProperties: false,
656
+ description: "Permitted team size, as an inclusive range. Either bound may be absent.",
657
+ properties: {
658
+ min: { type: ["integer", "null"], minimum: 1, description: "Minimum number of team members." },
659
+ max: { type: ["integer", "null"], minimum: 1, description: "Maximum number of team members." }
660
+ }
661
+ },
662
+ bounty: {
663
+ type: "object",
664
+ title: "Bounty details",
665
+ additionalProperties: false,
666
+ required: ["reward"],
667
+ description: "Bounty-specific attributes. A bounty is a single scoped task with a stated reward, so the reward is the one required field.",
668
+ properties: {
669
+ reward: { $ref: "#/$defs/monetaryAmount", description: "The reward paid on completion. Carries its own currency." },
670
+ difficulty: {
671
+ type: ["string", "null"],
672
+ enum: ["beginner", "intermediate", "advanced", null],
673
+ description: "Self-assessed difficulty, as a hint to applicants."
674
+ },
675
+ skills: {
676
+ type: "array",
677
+ items: { type: "string", minLength: 1 },
678
+ uniqueItems: true,
679
+ description: "Skills the task calls for. Free text.",
680
+ examples: [["Frontend", "Design"]]
681
+ },
682
+ platform: {
683
+ type: ["string", "null"],
684
+ description: "Platform hosting the bounty.",
685
+ examples: ["Gitcoin", "Layer3", "Superteam Earn"]
686
+ }
687
+ }
688
+ },
689
+ accelerator: {
690
+ type: "object",
691
+ title: "Accelerator details",
692
+ additionalProperties: false,
693
+ description: "Accelerator-specific attributes. The application deadline lives in the shared top-level deadlines array with label 'application'.",
694
+ properties: {
695
+ programDurationWeeks: { type: ["integer", "null"], minimum: 0, description: "Length of the program in weeks." },
696
+ batchSize: { type: ["integer", "null"], minimum: 0, description: "Number of teams accepted per cohort." },
697
+ equity: {
698
+ type: ["string", "null"],
699
+ description: "Equity taken, expressed as a string because programs state it in incomparable ways.",
700
+ examples: ["5%", "none", "up to 7% SAFE"]
701
+ },
702
+ funding: { $ref: "#/$defs/monetaryAmount", description: "Investment or stipend offered per team. Carries its own currency." },
703
+ stage: {
704
+ type: ["string", "null"],
705
+ enum: ["pre-seed", "seed", "series-a", null],
706
+ description: "Company stage the program targets."
707
+ },
708
+ location: { type: ["string", "null"], description: "Physical location, or null for a fully remote program." },
709
+ online: { type: ["boolean", "null"], description: "Whether the program is also, or only, run remotely." }
710
+ }
711
+ },
712
+ vcFund: {
713
+ type: "object",
714
+ title: "VC fund details",
715
+ additionalProperties: false,
716
+ description: "Venture-fund-specific attributes. A fund is an ongoing source of capital rather than a round, so it carries no deadline of its own.",
717
+ properties: {
718
+ checkSize: { $ref: "#/$defs/amountRange", description: "Typical investment size, as a range." },
719
+ stages: {
720
+ type: "array",
721
+ items: { type: "string", enum: ["pre-seed", "seed", "series-a", "series-b+", "growth"] },
722
+ uniqueItems: true,
723
+ description: "Investment stages the fund participates in."
724
+ },
725
+ thesis: { type: ["string", "null"], description: "Investment thesis, in the fund's own words." },
726
+ portfolio: {
727
+ type: "array",
728
+ items: { type: "string", minLength: 1 },
729
+ uniqueItems: true,
730
+ description: "Named portfolio companies, where the fund publishes them."
731
+ },
732
+ contactMethod: {
733
+ type: ["string", "null"],
734
+ enum: ["email", "form", "intro-only", null],
735
+ description: "How the fund prefers to be approached. 'intro-only' means a warm introduction is required."
736
+ },
737
+ activelyInvesting: { type: ["boolean", "null"], description: "Whether the fund is currently deploying capital." }
738
+ }
739
+ },
740
+ rfp: {
741
+ type: "object",
742
+ title: "RFP details",
743
+ additionalProperties: false,
744
+ description: "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'.",
745
+ properties: {
746
+ scope: {
747
+ type: ["string", "null"],
748
+ description: "Scope of work, as one free-text field. In-scope and out-of-scope prose both live here.",
749
+ $comment: "There is deliberately no separate outOfScope field \u2014 same philosophy as requirements and milestone criteria: one free-text field rather than a structure publishers would fill inconsistently."
750
+ },
751
+ requirements: {
752
+ type: "array",
753
+ items: { type: "string", minLength: 1 },
754
+ uniqueItems: true,
755
+ description: "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.",
756
+ examples: [["Methodology must be reproducible from public evidence."]]
757
+ }
758
+ }
759
+ }
760
+ }
761
+ };
762
+
763
+ // src/schema.ts
764
+ var opportunitySchema = opportunity_schema_default;
765
+ var SPEC_VERSION = "1.0.0";
766
+
767
+ // registries/deadline-labels.json
768
+ var deadline_labels_default = {
769
+ application: {
770
+ description: 'The deadline by which an application or proposal must be submitted. The default answer to "when must I apply by?" \u2014 every per-type proposal/application deadline folds in under this label.',
771
+ status: "active",
772
+ since: "1.0.0",
773
+ examples: ["application", "proposal deadline", "applications close"]
774
+ },
775
+ "community feedback": {
776
+ description: "The deadline for public comment on a proposal, where the process runs a community-feedback window before the application window closes. A listing that carries only one deadline routinely loses this one.",
777
+ status: "active",
778
+ since: "1.0.0",
779
+ examples: ["community feedback", "forum discussion closes"]
780
+ },
781
+ registration: {
782
+ description: "The deadline to register or sign up for an event, as distinct from submitting work. Most commonly used on hackathons.",
783
+ status: "active",
784
+ since: "1.0.0",
785
+ examples: ["registration", "sign-up closes"]
786
+ },
787
+ submission: {
788
+ description: "The deadline to submit a project or deliverable after registering. On hackathons this is normally later than 'registration' and at or before 'event end'.",
789
+ status: "active",
790
+ since: "1.0.0",
791
+ examples: ["submission", "project submission"]
792
+ },
793
+ "event start": {
794
+ description: "The moment an event begins. An EVENT BOUNDARY, not a deadline \u2014 it shares the array because the standard keeps one date model, which is exactly why consumers must filter on label rather than taking the array head.",
795
+ status: "active",
796
+ since: "1.0.0",
797
+ examples: ["event start", "hacking begins"]
798
+ },
799
+ "event end": {
800
+ description: "The moment an event finishes. An event boundary, not a deadline.",
801
+ status: "active",
802
+ since: "1.0.0",
803
+ examples: ["event end", "closing ceremony"]
804
+ }
805
+ };
806
+
807
+ // registries/eligibility-keys.json
808
+ var eligibility_keys_default = {
809
+ stage: {
810
+ description: "How mature an applicant team or project must be. Where a program restricts by maturity \u2014 idea, prototype, live product, revenue \u2014 that restriction goes here rather than into the description prose.",
811
+ status: "active",
812
+ since: "1.0.0",
813
+ examples: ["Pre-seed to Series A", "Live on mainnet with users", "Idea stage welcome"]
814
+ },
815
+ geography: {
816
+ description: "Where applicants must be based or where the funded work must happen. The residency/location axis, distinct from the legal axis in 'jurisdiction'.",
817
+ status: "active",
818
+ since: "1.0.0",
819
+ examples: ["Global", "Latin America only", "EU-based teams"]
820
+ },
821
+ jurisdiction: {
822
+ description: "Legal and sanctions constraints on who may receive funds \u2014 the compliance-driven half of location. Kept separate from 'geography' because a globally open program can still exclude sanctioned jurisdictions.",
823
+ status: "active",
824
+ since: "1.0.0",
825
+ examples: ["No US persons", "Excludes OFAC-sanctioned jurisdictions"]
826
+ },
827
+ sector: {
828
+ description: "The problem domain or vertical an applicant must work in, where that is an eligibility gate rather than a preference. Topical interest that is not a gate belongs in categories/tags.",
829
+ status: "active",
830
+ since: "1.0.0",
831
+ examples: ["DeFi and payments", "Public goods infrastructure", "Developer tooling only"]
832
+ },
833
+ entityType: {
834
+ description: "What kind of legal entity may apply \u2014 individual, unincorporated team, company, non-profit, DAO. Frequently the first thing that disqualifies an applicant and frequently buried in prose.",
835
+ status: "active",
836
+ since: "1.0.0",
837
+ examples: [
838
+ "Registered legal entity required",
839
+ "Individuals and teams welcome",
840
+ "Non-profits only"
841
+ ]
842
+ },
843
+ compliance: {
844
+ description: "Process obligations an applicant must accept to be eligible: KYC/KYB, audited financials, reporting commitments, agreement terms.",
845
+ status: "active",
846
+ since: "1.0.0",
847
+ examples: [
848
+ "KYC required before payout",
849
+ "Audited financials for awards over $100k",
850
+ "Quarterly reporting required"
851
+ ]
852
+ }
853
+ };
854
+
855
+ // registries/program-models.json
856
+ var program_models_default = {
857
+ grant: {
858
+ description: "An ordinary grant round: applications in, awards out, closeout on delivery. The default operating model \u2014 record it explicitly only where distinguishing it from the models below is meaningful. Note this is the PROGRAM MODEL 'grant', not the fundingType 'grant'; the two axes are deliberately separate.",
859
+ status: "active",
860
+ since: "1.0.0",
861
+ examples: ["grant"]
862
+ },
863
+ program: {
864
+ description: "A managed program with a cohort, a schedule and ongoing operator involvement, rather than a one-shot award. Different lifecycle and different closeout criteria from a plain grant round.",
865
+ status: "active",
866
+ since: "1.0.0",
867
+ examples: ["program"]
868
+ },
869
+ infra: {
870
+ description: "Funding directed at infrastructure or partnership work, usually negotiated and sourced rather than openly applied for. Tracked differently because delivery is measured against integration rather than a proposal.",
871
+ status: "active",
872
+ since: "1.0.0",
873
+ examples: ["infra", "partnerships"]
874
+ },
875
+ incentives: {
876
+ description: "Usage or liquidity incentives paid against on-chain activity rather than against a proposal. Collapsing this into 'grant' misrepresents both the lifecycle and the tracking needs.",
877
+ status: "active",
878
+ since: "1.0.0",
879
+ examples: ["incentives", "liquidity incentives"]
880
+ }
881
+ };
882
+
883
+ // src/registries.ts
884
+ var registries = {
885
+ "deadline-labels": deadline_labels_default,
886
+ "eligibility-keys": eligibility_keys_default,
887
+ "program-models": program_models_default
888
+ };
889
+ function isRegistered(name, value) {
890
+ return Object.hasOwn(registries[name], value);
891
+ }
892
+ function activeValues(name) {
893
+ return Object.entries(registries[name]).filter(([, e]) => e.status === "active").map(([k]) => k).sort();
894
+ }
895
+
896
+ exports.SPEC_VERSION = SPEC_VERSION;
897
+ exports.activeValues = activeValues;
898
+ exports.isRegistered = isRegistered;
899
+ exports.opportunitySchema = opportunitySchema;
900
+ exports.registries = registries;
901
+ //# sourceMappingURL=index.cjs.map
902
+ //# sourceMappingURL=index.cjs.map