@opensoftware/contracts 0.0.0-stage → 1.4.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 (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +112 -2
  3. package/dist/breaking.d.ts +27 -0
  4. package/dist/breaking.js +157 -0
  5. package/dist/breaking.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +69 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/formats.d.ts +10 -0
  10. package/dist/formats.js +21 -0
  11. package/dist/formats.js.map +1 -0
  12. package/dist/generated/types.d.ts +636 -0
  13. package/dist/generated/types.js +2 -0
  14. package/dist/generated/types.js.map +1 -0
  15. package/dist/index.d.ts +31 -0
  16. package/dist/index.js +75 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/roundtrip.d.ts +3 -0
  19. package/dist/roundtrip.js +90 -0
  20. package/dist/roundtrip.js.map +1 -0
  21. package/dist/run-record.d.ts +18 -0
  22. package/dist/run-record.js +27 -0
  23. package/dist/run-record.js.map +1 -0
  24. package/events/asyncapi.yaml +186 -0
  25. package/events/gate-decided/gate-decided.schema.json +122 -0
  26. package/events/gate-requested/gate-requested.schema.json +135 -0
  27. package/events/pr-closed/pr-closed.schema.json +80 -0
  28. package/events/pr-opened/pr-opened.schema.json +138 -0
  29. package/events/pr-reviewed/pr-reviewed.schema.json +111 -0
  30. package/events/run-completed/run-completed.schema.json +163 -0
  31. package/events/score-recorded/score-recorded.schema.json +163 -0
  32. package/events/work-received/work-received.schema.json +182 -0
  33. package/evidence-bundle/evidence-bundle.schema.json +180 -0
  34. package/factory-definition/factory-definition.schema.json +371 -0
  35. package/openapi/openapi.json +1715 -0
  36. package/package.json +50 -3
  37. package/run-record/run-record.schema.json +179 -0
  38. package/score/score.schema.json +110 -0
  39. package/typespec/common.tsp +62 -0
  40. package/typespec/events.tsp +168 -0
  41. package/typespec/evidence-bundle.tsp +99 -0
  42. package/typespec/factory-definition.tsp +193 -0
  43. package/typespec/main.tsp +10 -0
  44. package/typespec/run-record.tsp +88 -0
  45. package/typespec/score.tsp +68 -0
  46. package/typespec/work-item.tsp +65 -0
  47. package/work-item/work-item.schema.json +129 -0
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ // @opensoftware/contracts: the types of the shared formats and a validator for each. Modules import only from here.
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import Ajv2020, {} from "ajv/dist/2020.js";
6
+ import addFormats from "ajv-formats";
7
+ import { formatNamed } from "./formats.js";
8
+ export { diff as breakingChanges } from "./breaking.js";
9
+ export { FORMATS, formatNamed } from "./formats.js";
10
+ export { runSpans } from "./run-record.js";
11
+ /** The package root: dist/ and src/ both sit one level below it, next to the schema folders. */
12
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
13
+ const validators = new Map();
14
+ /** The JSON Schema of a format, as published in `<path>/<name>.schema.json`. */
15
+ export function schemaOf(format) {
16
+ const found = typeof format === "string" ? formatNamed(format) : format;
17
+ if (!found)
18
+ throw new Error(`Unknown format: ${String(format)}`);
19
+ return JSON.parse(readFileSync(join(ROOT, found.path, `${found.name}.schema.json`), "utf8"));
20
+ }
21
+ /** Every problem of the document; empty when it matches the format. */
22
+ export function validate(format, document) {
23
+ const name = typeof format === "string" ? format : format.name;
24
+ let check = validators.get(name);
25
+ if (!check) {
26
+ // strictRequired is off: "exactly one of" rules use `not: { required: [...] }` on purpose.
27
+ const ajv = new Ajv2020.default({ allErrors: true, strict: true, strictRequired: false });
28
+ addFormats.default(ajv);
29
+ check = ajv.compile(schemaOf(format));
30
+ validators.set(name, check);
31
+ }
32
+ return check(document) ? [] : problems(check.errors ?? []);
33
+ }
34
+ /** True when the document matches the format, with its type narrowed. */
35
+ export function isValid(format, document) {
36
+ return validate(format, document).length === 0;
37
+ }
38
+ const ALTERNATIVES = new Set(["anyOf", "oneOf"]);
39
+ /** Ajv errors as one line per problem; the branches of a failed `anyOf` or `oneOf` collapse into one problem. */
40
+ function problems(errors) {
41
+ const alternatives = errors.filter((error) => ALTERNATIVES.has(error.keyword)).map((error) => error.instancePath);
42
+ const insideAlternative = (error) => !ALTERNATIVES.has(error.keyword) &&
43
+ /\/(anyOf|oneOf)\//.test(error.schemaPath) &&
44
+ alternatives.some((path) => error.instancePath === path || error.instancePath.startsWith(`${path}/`));
45
+ const seen = new Set();
46
+ const out = [];
47
+ for (const error of errors) {
48
+ if (insideAlternative(error))
49
+ continue;
50
+ const problem = describe(error);
51
+ const key = `${problem.path} ${problem.message}`;
52
+ if (seen.has(key))
53
+ continue;
54
+ seen.add(key);
55
+ out.push(problem);
56
+ }
57
+ return out;
58
+ }
59
+ function describe(error) {
60
+ const child = (name) => `${error.instancePath}/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`;
61
+ switch (error.keyword) {
62
+ case "additionalProperties":
63
+ return { path: child(String(error.params.additionalProperty)), message: "unknown field" };
64
+ case "unevaluatedProperties":
65
+ return { path: child(String(error.params.unevaluatedProperty)), message: "unknown field" };
66
+ case "required":
67
+ return { path: child(String(error.params.missingProperty)), message: "missing required field" };
68
+ case "anyOf":
69
+ case "oneOf":
70
+ return { path: error.instancePath || "/", message: "must match exactly one of the allowed forms" };
71
+ default:
72
+ return { path: error.instancePath || "/", message: error.message ?? error.keyword };
73
+ }
74
+ }
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const CLIENTS: readonly ["typescript"];
2
+ export type Client = (typeof CLIENTS)[number];
3
+ export declare function roundtrip(_client: Client, dir: string): number;
@@ -0,0 +1,90 @@
1
+ // `osf-contracts roundtrip --client <client> [dir]`: every valid fixture under dir is loaded by a generated client
2
+ // and written back; the result must equal the fixture. TypeScript is the only client (ADR 0012); the flag leaves
3
+ // room for another one.
4
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5
+ import { join, sep } from "node:path";
6
+ import { FORMATS } from "./formats.js";
7
+ import { validate } from "./index.js";
8
+ export const CLIENTS = ["typescript"];
9
+ export function roundtrip(_client, dir) {
10
+ const fixtures = validFixtures(dir);
11
+ if (fixtures.length === 0) {
12
+ process.stderr.write(`No valid fixtures under ${dir}/<format>/fixtures/valid/\n`);
13
+ return 1;
14
+ }
15
+ let written;
16
+ try {
17
+ written = viaTypeScript(fixtures);
18
+ }
19
+ catch (error) {
20
+ process.stderr.write(`${error.message}\n`);
21
+ return 1;
22
+ }
23
+ let changed = 0;
24
+ for (const { file } of fixtures) {
25
+ const original = JSON.parse(readFileSync(file, "utf8"));
26
+ const difference = written.has(file) ? firstDifference(original, written.get(file), "") : "not written back";
27
+ if (difference === undefined) {
28
+ process.stdout.write(`${file}: unchanged\n`);
29
+ }
30
+ else {
31
+ changed++;
32
+ process.stdout.write(`${file}: changed at ${difference}\n`);
33
+ }
34
+ }
35
+ return changed === 0 ? 0 : 1;
36
+ }
37
+ function validFixtures(dir) {
38
+ const out = [];
39
+ for (const format of FORMATS) {
40
+ const folder = join(dir, format.path, "fixtures", "valid");
41
+ if (!existsSync(folder) || !statSync(folder).isDirectory())
42
+ continue;
43
+ for (const entry of readdirSync(folder).sort()) {
44
+ if (entry.endsWith(".json"))
45
+ out.push({ file: join(folder, entry).split(sep).join("/"), format });
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+ /** The TypeScript side: validate, then serialize the typed value again. */
51
+ function viaTypeScript(fixtures) {
52
+ const out = new Map();
53
+ for (const { file, format } of fixtures) {
54
+ const document = JSON.parse(readFileSync(file, "utf8"));
55
+ const problems = validate(format, document);
56
+ if (problems.length > 0)
57
+ throw new Error(`${file} does not match ${format.name}: ${problems[0]?.path} ${problems[0]?.message}`);
58
+ out.set(file, JSON.parse(JSON.stringify(document)));
59
+ }
60
+ return out;
61
+ }
62
+ /** JSON Pointer and values of the first difference; numbers compare by value, so 4 equals 4.0. */
63
+ function firstDifference(a, b, path) {
64
+ if (typeof a === "number" && typeof b === "number")
65
+ return a === b ? undefined : `${path || "/"}: ${a} → ${b}`;
66
+ if (Array.isArray(a) && Array.isArray(b)) {
67
+ if (a.length !== b.length)
68
+ return `${path || "/"}: ${a.length} items → ${b.length} items`;
69
+ for (let index = 0; index < a.length; index++) {
70
+ const found = firstDifference(a[index], b[index], `${path}/${index}`);
71
+ if (found)
72
+ return found;
73
+ }
74
+ return undefined;
75
+ }
76
+ if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
77
+ const left = a;
78
+ const right = b;
79
+ for (const key of new Set([...Object.keys(left), ...Object.keys(right)])) {
80
+ const found = firstDifference(left[key], right[key], `${path}/${key}`);
81
+ if (found)
82
+ return found;
83
+ }
84
+ return undefined;
85
+ }
86
+ return JSON.stringify(a) === JSON.stringify(b)
87
+ ? undefined
88
+ : `${path || "/"}: ${JSON.stringify(a)} → ${JSON.stringify(b)}`;
89
+ }
90
+ //# sourceMappingURL=roundtrip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roundtrip.js","sourceRoot":"","sources":["../src/roundtrip.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** An OTLP/JSON attribute value as runlog writes it: a string, an integer, a double or an array of them. */
2
+ export type AttributeValue = string | number | AttributeValue[] | undefined;
3
+ export type SpanAttributes = Record<string, AttributeValue>;
4
+ /** One span of a run record, with its resource. */
5
+ export interface RunSpan {
6
+ traceId: string;
7
+ spanId: string;
8
+ parentSpanId: string | undefined;
9
+ name: string;
10
+ /** Unix time in nanoseconds; 0 when the span has none. */
11
+ start: bigint;
12
+ end: bigint;
13
+ /** The attributes of the span's resource, like `osf.run.id`. */
14
+ resource: SpanAttributes;
15
+ attributes: SpanAttributes;
16
+ }
17
+ /** The spans of a run record in the order they appear; undefined when the value is no OTLP/JSON trace export request. */
18
+ export declare function runSpans(record: unknown): RunSpan[] | undefined;
@@ -0,0 +1,27 @@
1
+ // Reading run records: OTLP/JSON trace export requests with the run-record attributes, as runlog writes them.
2
+ /** The spans of a run record in the order they appear; undefined when the value is no OTLP/JSON trace export request. */
3
+ export function runSpans(record) {
4
+ const { resourceSpans } = (record ?? {});
5
+ if (!Array.isArray(resourceSpans))
6
+ return undefined;
7
+ return resourceSpans.flatMap((entry) => {
8
+ const resource = decode(entry.resource?.attributes);
9
+ return (entry.scopeSpans ?? []).flatMap((scope) => (scope.spans ?? []).map((span) => ({
10
+ traceId: span.traceId,
11
+ spanId: span.spanId,
12
+ parentSpanId: span.parentSpanId || undefined,
13
+ name: span.name ?? "",
14
+ start: BigInt(span.startTimeUnixNano ?? 0),
15
+ end: BigInt(span.endTimeUnixNano ?? 0),
16
+ resource,
17
+ attributes: decode(span.attributes),
18
+ })));
19
+ });
20
+ }
21
+ const value = (otlp) => {
22
+ if (otlp.arrayValue)
23
+ return (otlp.arrayValue.values ?? []).map(value);
24
+ return otlp.intValue === undefined ? (otlp.stringValue ?? otlp.doubleValue) : Number(otlp.intValue);
25
+ };
26
+ const decode = (list) => Object.fromEntries((list ?? []).map(({ key, value: entry }) => [key, value(entry)]));
27
+ //# sourceMappingURL=run-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-record.js","sourceRoot":"","sources":["../src/run-record.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAyC9G,yHAAyH;AACzH,MAAM,UAAU,QAAQ,CAAC,MAAe;IACtC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAgC,CAAC;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IACpD,OAAQ,aAAqC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAChD,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;YAC5C,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;YACrB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,CAAC;YAC1C,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC;YACtC,QAAQ;YACR,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;SACpC,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,IAAe,EAAkB,EAAE;IAChD,IAAI,IAAI,CAAC,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACtG,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,IAAoB,EAAkB,EAAE,CACtD,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,186 @@
1
+ # Events between modules (ADR 0006). Hand-written: TypeSpec has no maintained AsyncAPI emitter, so the
2
+ # payloads are the JSON Schemas that `pnpm build` emits from typespec/events.tsp. Every message is a CloudEvents
3
+ # 1.0 event in structured JSON mode; its `type` is the versioned name, for example "work.received@v1".
4
+ asyncapi: 3.1.0
5
+ info:
6
+ title: Open Software Factory events
7
+ version: 0.0.0
8
+ description: Facts that modules publish and others react to. Past tense, versioned, at-least-once.
9
+ defaultContentType: application/cloudevents+json
10
+ servers:
11
+ nats:
12
+ host: localhost:4222
13
+ protocol: nats
14
+ description: NATS JetStream (ADR 0003). Without a bus the same JSON is posted to webhooks.
15
+ channels:
16
+ work-received:
17
+ address: osf.work.received.v1
18
+ description: A work item was accepted. `data` is a work item.
19
+ messages:
20
+ WorkReceived:
21
+ $ref: '#/components/messages/WorkReceived'
22
+ run-completed:
23
+ address: osf.run.completed.v1
24
+ description: A run ended.
25
+ messages:
26
+ RunCompleted:
27
+ $ref: '#/components/messages/RunCompleted'
28
+ gate-requested:
29
+ address: osf.gate.requested.v1
30
+ description: A stage waits for a decision.
31
+ messages:
32
+ GateRequested:
33
+ $ref: '#/components/messages/GateRequested'
34
+ gate-decided:
35
+ address: osf.gate.decided.v1
36
+ description: A gate was approved, rejected or expired.
37
+ messages:
38
+ GateDecided:
39
+ $ref: '#/components/messages/GateDecided'
40
+ score-recorded:
41
+ address: osf.score.recorded.v1
42
+ description: A scorer scored a run. `data` is a score.
43
+ messages:
44
+ ScoreRecorded:
45
+ $ref: '#/components/messages/ScoreRecorded'
46
+ pr-opened:
47
+ address: osf.pr.opened.v1
48
+ description: The factory opened a pull request.
49
+ messages:
50
+ PrOpened:
51
+ $ref: '#/components/messages/PrOpened'
52
+ pr-reviewed:
53
+ address: osf.pr.reviewed.v1
54
+ description: A person, an agent or a bot reviewed or commented on a pull request.
55
+ messages:
56
+ PrReviewed:
57
+ $ref: '#/components/messages/PrReviewed'
58
+ pr-closed:
59
+ address: osf.pr.closed.v1
60
+ description: A pull request was merged or closed without a merge.
61
+ messages:
62
+ PrClosed:
63
+ $ref: '#/components/messages/PrClosed'
64
+ operations:
65
+ publishWorkReceived:
66
+ action: send
67
+ summary: Published by intake.
68
+ channel:
69
+ $ref: '#/channels/work-received'
70
+ messages:
71
+ - $ref: '#/channels/work-received/messages/WorkReceived'
72
+ publishRunCompleted:
73
+ action: send
74
+ summary: Published by foreman.
75
+ channel:
76
+ $ref: '#/channels/run-completed'
77
+ messages:
78
+ - $ref: '#/channels/run-completed/messages/RunCompleted'
79
+ publishGateRequested:
80
+ action: send
81
+ summary: Published by gatekeeper.
82
+ channel:
83
+ $ref: '#/channels/gate-requested'
84
+ messages:
85
+ - $ref: '#/channels/gate-requested/messages/GateRequested'
86
+ publishGateDecided:
87
+ action: send
88
+ summary: Published by gatekeeper.
89
+ channel:
90
+ $ref: '#/channels/gate-decided'
91
+ messages:
92
+ - $ref: '#/channels/gate-decided/messages/GateDecided'
93
+ publishScoreRecorded:
94
+ action: send
95
+ summary: Published by scorekit.
96
+ channel:
97
+ $ref: '#/channels/score-recorded'
98
+ messages:
99
+ - $ref: '#/channels/score-recorded/messages/ScoreRecorded'
100
+ publishPrOpened:
101
+ action: send
102
+ summary: Published by forge adapters.
103
+ channel:
104
+ $ref: '#/channels/pr-opened'
105
+ messages:
106
+ - $ref: '#/channels/pr-opened/messages/PrOpened'
107
+ publishPrReviewed:
108
+ action: send
109
+ summary: Published by forge adapters.
110
+ channel:
111
+ $ref: '#/channels/pr-reviewed'
112
+ messages:
113
+ - $ref: '#/channels/pr-reviewed/messages/PrReviewed'
114
+ publishPrClosed:
115
+ action: send
116
+ summary: Published by forge adapters.
117
+ channel:
118
+ $ref: '#/channels/pr-closed'
119
+ messages:
120
+ - $ref: '#/channels/pr-closed/messages/PrClosed'
121
+ components:
122
+ messages:
123
+ WorkReceived:
124
+ name: work.received@v1
125
+ title: work.received@v1
126
+ contentType: application/cloudevents+json
127
+ payload:
128
+ schemaFormat: application/schema+json;version=draft-2020-12
129
+ schema:
130
+ $ref: './work-received/work-received.schema.json'
131
+ RunCompleted:
132
+ name: run.completed@v1
133
+ title: run.completed@v1
134
+ contentType: application/cloudevents+json
135
+ payload:
136
+ schemaFormat: application/schema+json;version=draft-2020-12
137
+ schema:
138
+ $ref: './run-completed/run-completed.schema.json'
139
+ GateRequested:
140
+ name: gate.requested@v1
141
+ title: gate.requested@v1
142
+ contentType: application/cloudevents+json
143
+ payload:
144
+ schemaFormat: application/schema+json;version=draft-2020-12
145
+ schema:
146
+ $ref: './gate-requested/gate-requested.schema.json'
147
+ GateDecided:
148
+ name: gate.decided@v1
149
+ title: gate.decided@v1
150
+ contentType: application/cloudevents+json
151
+ payload:
152
+ schemaFormat: application/schema+json;version=draft-2020-12
153
+ schema:
154
+ $ref: './gate-decided/gate-decided.schema.json'
155
+ ScoreRecorded:
156
+ name: score.recorded@v1
157
+ title: score.recorded@v1
158
+ contentType: application/cloudevents+json
159
+ payload:
160
+ schemaFormat: application/schema+json;version=draft-2020-12
161
+ schema:
162
+ $ref: './score-recorded/score-recorded.schema.json'
163
+ PrOpened:
164
+ name: pr.opened@v1
165
+ title: pr.opened@v1
166
+ contentType: application/cloudevents+json
167
+ payload:
168
+ schemaFormat: application/schema+json;version=draft-2020-12
169
+ schema:
170
+ $ref: './pr-opened/pr-opened.schema.json'
171
+ PrReviewed:
172
+ name: pr.reviewed@v1
173
+ title: pr.reviewed@v1
174
+ contentType: application/cloudevents+json
175
+ payload:
176
+ schemaFormat: application/schema+json;version=draft-2020-12
177
+ schema:
178
+ $ref: './pr-reviewed/pr-reviewed.schema.json'
179
+ PrClosed:
180
+ name: pr.closed@v1
181
+ title: pr.closed@v1
182
+ contentType: application/cloudevents+json
183
+ payload:
184
+ schemaFormat: application/schema+json;version=draft-2020-12
185
+ schema:
186
+ $ref: './pr-closed/pr-closed.schema.json'
@@ -0,0 +1,122 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/sesigl/open-software-factory/main/contracts/events/gate-decided/gate-decided.schema.json",
4
+ "title": "GateDecided",
5
+ "$comment": "Generated by `pnpm build` in contracts/ from typespec/. Do not edit.",
6
+ "type": "object",
7
+ "properties": {
8
+ "specversion": {
9
+ "type": "string",
10
+ "const": "1.0"
11
+ },
12
+ "id": {
13
+ "type": "string",
14
+ "minLength": 1,
15
+ "maxLength": 128,
16
+ "description": "Unique per source. A consumer that sees the same source and id twice handles the event once."
17
+ },
18
+ "source": {
19
+ "type": "string",
20
+ "minLength": 1,
21
+ "description": "The producing module, for example \"/osf/intake\"."
22
+ },
23
+ "type": {
24
+ "type": "string",
25
+ "const": "gate.decided@v1"
26
+ },
27
+ "time": {
28
+ "type": "string",
29
+ "format": "date-time"
30
+ },
31
+ "subject": {
32
+ "type": "string",
33
+ "description": "The ID of the thing the event is about: work item, gate, run, score or pull request."
34
+ },
35
+ "datacontenttype": {
36
+ "type": "string",
37
+ "const": "application/json"
38
+ },
39
+ "dataschema": {
40
+ "type": "string",
41
+ "format": "uri"
42
+ },
43
+ "traceparent": {
44
+ "type": "string",
45
+ "description": "W3C trace context (CloudEvents distributed tracing extension)."
46
+ },
47
+ "data": {
48
+ "type": "object",
49
+ "properties": {
50
+ "gateId": {
51
+ "type": "string",
52
+ "minLength": 1,
53
+ "maxLength": 128,
54
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
55
+ },
56
+ "workItemId": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "maxLength": 128,
60
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
61
+ },
62
+ "decision": {
63
+ "type": "string",
64
+ "enum": [
65
+ "approved",
66
+ "rejected",
67
+ "expired"
68
+ ]
69
+ },
70
+ "decidedBy": {
71
+ "type": "object",
72
+ "properties": {
73
+ "kind": {
74
+ "type": "string",
75
+ "enum": [
76
+ "person",
77
+ "trigger"
78
+ ],
79
+ "description": "Who caused something: a person, or an automated trigger such as a cron rule or an alert."
80
+ },
81
+ "id": {
82
+ "type": "string",
83
+ "minLength": 1,
84
+ "description": "Stable ID in the source system, prefixed with the source, for example \"slack:U024BE7LH\"."
85
+ },
86
+ "displayName": {
87
+ "type": "string",
88
+ "description": "Shown to people, for example \"Varun\"."
89
+ }
90
+ },
91
+ "required": [
92
+ "kind",
93
+ "id"
94
+ ],
95
+ "description": "The person who decided. Absent only when the gate expired."
96
+ },
97
+ "decidedAt": {
98
+ "type": "string",
99
+ "format": "date-time"
100
+ },
101
+ "reason": {
102
+ "type": "string"
103
+ }
104
+ },
105
+ "required": [
106
+ "gateId",
107
+ "workItemId",
108
+ "decision",
109
+ "decidedAt"
110
+ ]
111
+ }
112
+ },
113
+ "required": [
114
+ "specversion",
115
+ "id",
116
+ "source",
117
+ "type",
118
+ "time",
119
+ "data"
120
+ ],
121
+ "description": "gatekeeper: a gate was decided."
122
+ }