@opensoftware/contracts 0.0.0-stage → 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 (36) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +105 -2
  3. package/dist/breaking.d.ts +27 -0
  4. package/dist/breaking.js +156 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +68 -0
  7. package/dist/formats.d.ts +10 -0
  8. package/dist/formats.js +18 -0
  9. package/dist/generated/types.d.ts +543 -0
  10. package/dist/generated/types.js +1 -0
  11. package/dist/index.d.ts +28 -0
  12. package/dist/index.js +73 -0
  13. package/dist/roundtrip.d.ts +3 -0
  14. package/dist/roundtrip.js +89 -0
  15. package/events/asyncapi.yaml +144 -0
  16. package/events/gate-decided/gate-decided.schema.json +122 -0
  17. package/events/gate-requested/gate-requested.schema.json +135 -0
  18. package/events/pr-opened/pr-opened.schema.json +138 -0
  19. package/events/run-completed/run-completed.schema.json +163 -0
  20. package/events/score-recorded/score-recorded.schema.json +163 -0
  21. package/events/work-received/work-received.schema.json +182 -0
  22. package/evidence-bundle/evidence-bundle.schema.json +180 -0
  23. package/factory-definition/factory-definition.schema.json +371 -0
  24. package/openapi/openapi.json +1553 -0
  25. package/package.json +50 -3
  26. package/run-record/run-record.schema.json +179 -0
  27. package/score/score.schema.json +110 -0
  28. package/typespec/common.tsp +62 -0
  29. package/typespec/events.tsp +134 -0
  30. package/typespec/evidence-bundle.tsp +99 -0
  31. package/typespec/factory-definition.tsp +193 -0
  32. package/typespec/main.tsp +10 -0
  33. package/typespec/run-record.tsp +88 -0
  34. package/typespec/score.tsp +68 -0
  35. package/typespec/work-item.tsp +65 -0
  36. package/work-item/work-item.schema.json +129 -0
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "@opensoftware/contracts",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "The shared formats of the open software factory: JSON Schemas, TypeScript types and the osf-contracts CLI.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/sesigl/open-software-factory.git",
9
+ "directory": "contracts"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "osf-contracts": "dist/cli.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./*.schema.json": "./*.schema.json",
21
+ "./events/asyncapi.yaml": "./events/asyncapi.yaml",
22
+ "./openapi/*": "./openapi/*",
23
+ "./typespec/*": "./typespec/*",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "typespec",
29
+ "*/*.schema.json",
30
+ "events/*/*.schema.json",
31
+ "events/asyncapi.yaml",
32
+ "openapi"
33
+ ],
34
+ "dependencies": {
35
+ "ajv": "8.20.0",
36
+ "ajv-formats": "3.0.1",
37
+ "yaml": "2.9.1"
38
+ },
39
+ "devDependencies": {
40
+ "@asyncapi/parser": "3.6.6",
41
+ "@typespec/compiler": "1.17.0",
42
+ "@typespec/json-schema": "1.17.0",
43
+ "openapi-typescript": "7.13.0",
44
+ "@opensoftware/testkit": "0.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "node scripts/build.ts",
48
+ "lint": "biome check . && node scripts/build.ts --check",
49
+ "typecheck": "tsc --noEmit -p tsconfig.json",
50
+ "test:system": "vitest run",
51
+ "docs": "node ../tools/bin/osf.ts docs ."
52
+ }
6
53
  }
@@ -0,0 +1,179 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/sesigl/open-software-factory/main/contracts/run-record/run-record.schema.json",
4
+ "title": "RunRecord",
5
+ "$comment": "Generated by `pnpm build` in contracts/ from typespec/. Do not edit.",
6
+ "type": "object",
7
+ "properties": {
8
+ "resource": {
9
+ "type": "object",
10
+ "properties": {
11
+ "service.name": {
12
+ "type": "string"
13
+ },
14
+ "osf.run.id": {
15
+ "type": "string",
16
+ "minLength": 1,
17
+ "maxLength": 128,
18
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
19
+ },
20
+ "osf.work_item.id": {
21
+ "type": "string",
22
+ "minLength": 1,
23
+ "maxLength": 128,
24
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
25
+ },
26
+ "osf.factory.version": {
27
+ "type": "string",
28
+ "pattern": "^[0-9a-f]{7,40}$",
29
+ "description": "Commit of the factory definition the run used."
30
+ },
31
+ "osf.stage": {
32
+ "type": "string",
33
+ "pattern": "^[a-z][a-z0-9-]{0,62}$",
34
+ "description": "A lowercase name: letters, digits and hyphens, starting with a letter. Agents, stages, adapters, sources."
35
+ },
36
+ "osf.requester.kind": {
37
+ "type": "string",
38
+ "enum": [
39
+ "person",
40
+ "trigger"
41
+ ],
42
+ "description": "Who caused something: a person, or an automated trigger such as a cron rule or an alert."
43
+ },
44
+ "osf.requester.id": {
45
+ "type": "string"
46
+ },
47
+ "osf.source": {
48
+ "type": "string",
49
+ "pattern": "^[a-z][a-z0-9-]{0,62}$",
50
+ "description": "Where the run was started from, for example \"slack\", \"github\", \"cli\"."
51
+ },
52
+ "osf.origin.url": {
53
+ "type": "string",
54
+ "format": "uri"
55
+ },
56
+ "osf.harness.name": {
57
+ "type": "string",
58
+ "pattern": "^[a-z][a-z0-9-]{0,62}$",
59
+ "description": "Harness adapter, for example \"claude-code\"."
60
+ },
61
+ "osf.harness.version": {
62
+ "type": "string"
63
+ }
64
+ },
65
+ "required": [
66
+ "service.name",
67
+ "osf.run.id",
68
+ "osf.work_item.id",
69
+ "osf.factory.version",
70
+ "osf.requester.kind",
71
+ "osf.requester.id",
72
+ "osf.source",
73
+ "osf.harness.name"
74
+ ],
75
+ "description": "Set at launch through OTEL_RESOURCE_ATTRIBUTES, so it is on every span of the run."
76
+ },
77
+ "span": {
78
+ "type": "object",
79
+ "properties": {
80
+ "gen_ai.operation.name": {
81
+ "type": "string",
82
+ "const": "invoke_agent"
83
+ },
84
+ "gen_ai.provider.name": {
85
+ "type": "string"
86
+ },
87
+ "gen_ai.agent.name": {
88
+ "type": "string"
89
+ },
90
+ "gen_ai.conversation.id": {
91
+ "type": "string",
92
+ "description": "The harness session ID."
93
+ },
94
+ "gen_ai.request.model": {
95
+ "type": "string"
96
+ },
97
+ "gen_ai.response.model": {
98
+ "type": "string"
99
+ },
100
+ "gen_ai.usage.input_tokens": {
101
+ "type": "integer",
102
+ "minimum": 0
103
+ },
104
+ "gen_ai.usage.output_tokens": {
105
+ "type": "integer",
106
+ "minimum": 0
107
+ },
108
+ "gen_ai.usage.cache_read.input_tokens": {
109
+ "type": "integer",
110
+ "minimum": 0
111
+ },
112
+ "gen_ai.usage.cache_write.input_tokens": {
113
+ "type": "integer",
114
+ "minimum": 0
115
+ },
116
+ "osf.outcome": {
117
+ "type": "string",
118
+ "enum": [
119
+ "succeeded",
120
+ "failed",
121
+ "cancelled"
122
+ ],
123
+ "description": "How a run ended."
124
+ },
125
+ "osf.cost.usd": {
126
+ "type": "number",
127
+ "minimum": 0,
128
+ "description": "Cost of all model calls of the run, from the inference gateway."
129
+ },
130
+ "osf.transcript.uri": {
131
+ "type": "string",
132
+ "format": "uri",
133
+ "description": "Full transcript, stored when the run ends."
134
+ },
135
+ "osf.artifact.uris": {
136
+ "type": "array",
137
+ "items": {
138
+ "type": "string",
139
+ "format": "uri"
140
+ },
141
+ "description": "Artifacts of the run: reports, screenshots, videos."
142
+ },
143
+ "osf.pull_request.urls": {
144
+ "type": "array",
145
+ "items": {
146
+ "type": "string",
147
+ "format": "uri"
148
+ }
149
+ },
150
+ "osf.issue.urls": {
151
+ "type": "array",
152
+ "items": {
153
+ "type": "string",
154
+ "format": "uri"
155
+ }
156
+ },
157
+ "osf.evidence_bundle.uri": {
158
+ "type": "string",
159
+ "format": "uri"
160
+ },
161
+ "osf.agent_trace.uri": {
162
+ "type": "string",
163
+ "format": "uri",
164
+ "description": "Agent Trace record with line attribution of the run's changes."
165
+ }
166
+ },
167
+ "required": [
168
+ "gen_ai.operation.name",
169
+ "osf.outcome"
170
+ ],
171
+ "description": "Attributes of the root span of the run. GenAI names where a convention exists."
172
+ }
173
+ },
174
+ "required": [
175
+ "resource",
176
+ "span"
177
+ ],
178
+ "description": "The attributes of one run: the OTel resource of every span of the run, and the attributes of its root\n`invoke_agent` span. The run record travels as OTLP; this schema fixes the attribute names and types.\nRealizes SCN-FACTORY-DATA-001 (record shape) and SCN-FACTORY-RUNTIME-008 (attribution)."
179
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/sesigl/open-software-factory/main/contracts/score/score.schema.json",
4
+ "title": "Score",
5
+ "$comment": "Generated by `pnpm build` in contracts/ from typespec/. Do not edit.",
6
+ "type": "object",
7
+ "properties": {
8
+ "id": {
9
+ "type": "string",
10
+ "minLength": 1,
11
+ "maxLength": 128,
12
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
13
+ },
14
+ "name": {
15
+ "type": "string",
16
+ "minLength": 1,
17
+ "description": "Scorer name, for example \"redundant-tests\"."
18
+ },
19
+ "value": {
20
+ "anyOf": [
21
+ {
22
+ "type": "number"
23
+ },
24
+ {
25
+ "type": "string"
26
+ }
27
+ ],
28
+ "description": "A number for NUMERIC, 0 or 1 for BOOLEAN, a string for CATEGORICAL."
29
+ },
30
+ "dataType": {
31
+ "type": "string",
32
+ "enum": [
33
+ "NUMERIC",
34
+ "BOOLEAN",
35
+ "CATEGORICAL"
36
+ ]
37
+ },
38
+ "traceId": {
39
+ "type": "string",
40
+ "pattern": "^[0-9a-f]{32}$",
41
+ "description": "Trace of the run (the OTel trace ID)."
42
+ },
43
+ "observationId": {
44
+ "type": "string",
45
+ "description": "Span the score is about, when it is not the whole run."
46
+ },
47
+ "sessionId": {
48
+ "type": "string"
49
+ },
50
+ "comment": {
51
+ "type": "string",
52
+ "description": "The reason."
53
+ },
54
+ "timestamp": {
55
+ "type": "string",
56
+ "format": "date-time"
57
+ },
58
+ "source": {
59
+ "type": "string",
60
+ "enum": [
61
+ "API",
62
+ "ANNOTATION"
63
+ ],
64
+ "description": "API for scorers, ANNOTATION for scores a person reviews."
65
+ },
66
+ "configId": {
67
+ "type": "string"
68
+ },
69
+ "environment": {
70
+ "type": "string"
71
+ },
72
+ "metadata": {
73
+ "type": "object",
74
+ "properties": {
75
+ "osf.run.id": {
76
+ "type": "string",
77
+ "minLength": 1,
78
+ "maxLength": 128,
79
+ "description": "An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque."
80
+ },
81
+ "osf.scorer.version": {
82
+ "type": "string"
83
+ },
84
+ "osf.judge.model": {
85
+ "type": "string",
86
+ "description": "Judge model of an LLM scorer."
87
+ },
88
+ "osf.judge.cost.usd": {
89
+ "type": "number",
90
+ "minimum": 0,
91
+ "description": "Cost of the judge call."
92
+ }
93
+ },
94
+ "required": [
95
+ "osf.run.id"
96
+ ],
97
+ "unevaluatedProperties": {}
98
+ }
99
+ },
100
+ "required": [
101
+ "id",
102
+ "name",
103
+ "value",
104
+ "dataType",
105
+ "traceId",
106
+ "timestamp",
107
+ "metadata"
108
+ ],
109
+ "description": "The result of one scorer on one run. The fields are a subset of Langfuse's create-score request\n(POST /api/public/scores), so a score can be sent to Langfuse as is. Factory fields live in `metadata`."
110
+ }
@@ -0,0 +1,62 @@
1
+ import "@typespec/json-schema";
2
+
3
+ using TypeSpec.JsonSchema;
4
+
5
+ @jsonSchema
6
+ namespace Osf.Contracts;
7
+
8
+ /** A lowercase name: letters, digits and hyphens, starting with a letter. Agents, stages, adapters, sources. */
9
+ @pattern("^[a-z][a-z0-9-]{0,62}$")
10
+ scalar Name extends string;
11
+
12
+ /** An identifier chosen by the producer. ULIDs are recommended; consumers treat it as opaque. */
13
+ @minLength(1)
14
+ @maxLength(128)
15
+ scalar Identifier extends string;
16
+
17
+ /** A Git commit hash, abbreviated (7+) or full. The factory version is the commit of the factory definition. */
18
+ @pattern("^[0-9a-f]{7,40}$")
19
+ scalar CommitHash extends string;
20
+
21
+ /** A scenario ID as checked by SPEC, for example SCN-PROOF-004. */
22
+ @pattern("^SCN-[A-Z][A-Z0-9]*(-[A-Z][A-Z0-9]*)*-[0-9]{3}$")
23
+ scalar ScenarioId extends string;
24
+
25
+ /** The version of a format inside its documents. Only 1 exists; a new major version of a format adds the next. */
26
+ @extension("const", Json<1>)
27
+ @minValue(1)
28
+ scalar FormatVersion extends int32;
29
+
30
+ /** An OpenTelemetry trace ID: 32 lowercase hex characters. */
31
+ @pattern("^[0-9a-f]{32}$")
32
+ scalar TraceId extends string;
33
+
34
+ /** Who caused something: a person, or an automated trigger such as a cron rule or an alert. */
35
+ enum ActorKind {
36
+ person,
37
+ trigger,
38
+ }
39
+
40
+ /** A person or trigger. RUNTIME-008: every run is attributed to one. */
41
+ model Actor {
42
+ kind: ActorKind;
43
+
44
+ /** Stable ID in the source system, prefixed with the source, for example "slack:U024BE7LH". */
45
+ @minLength(1)
46
+ id: string;
47
+
48
+ /** Shown to people, for example "Varun". */
49
+ displayName?: string;
50
+ }
51
+
52
+ model Link {
53
+ url: url;
54
+ title?: string;
55
+ }
56
+
57
+ /** How a run ended. */
58
+ enum RunOutcome {
59
+ succeeded,
60
+ failed,
61
+ cancelled,
62
+ }
@@ -0,0 +1,134 @@
1
+ import "@typespec/json-schema";
2
+ import "./common.tsp";
3
+ import "./work-item.tsp";
4
+ import "./score.tsp";
5
+
6
+ using TypeSpec.JsonSchema;
7
+
8
+ namespace Osf.Contracts;
9
+
10
+ /**
11
+ * A CloudEvents 1.0 event in structured JSON mode. The same JSON is the NATS message body and the webhook body.
12
+ * `type` is the versioned event name; a breaking change gets a new name (`@v2`) next to the old one.
13
+ */
14
+ model CloudEvent<Type extends string, Data> {
15
+ specversion: "1.0";
16
+
17
+ /** Unique per source. A consumer that sees the same source and id twice handles the event once. */
18
+ id: Identifier;
19
+
20
+ /** The producing module, for example "/osf/intake". */
21
+ @minLength(1)
22
+ source: string;
23
+
24
+ type: Type;
25
+ time: utcDateTime;
26
+
27
+ /** The ID of the thing the event is about: work item, gate, run, score or pull request. */
28
+ subject?: string;
29
+
30
+ datacontenttype?: "application/json";
31
+ dataschema?: url;
32
+
33
+ /** W3C trace context (CloudEvents distributed tracing extension). */
34
+ traceparent?: string;
35
+
36
+ data: Data;
37
+ }
38
+
39
+ /** intake: a work item was accepted. */
40
+ model WorkReceived is CloudEvent<"work.received@v1", WorkItem>;
41
+
42
+ /** foreman: a run ended. */
43
+ model RunCompleted is CloudEvent<"run.completed@v1", RunCompletedData>;
44
+
45
+ /** gatekeeper: a decision is needed before a stage starts. */
46
+ model GateRequested is CloudEvent<"gate.requested@v1", GateRequestedData>;
47
+
48
+ /** gatekeeper: a gate was decided. */
49
+ model GateDecided is CloudEvent<"gate.decided@v1", GateDecidedData>;
50
+
51
+ /** scorekit: a scorer scored a run. */
52
+ model ScoreRecorded is CloudEvent<"score.recorded@v1", Score>;
53
+
54
+ /** forge adapters: the factory opened a pull request. */
55
+ model PrOpened is CloudEvent<"pr.opened@v1", PrOpenedData>;
56
+
57
+ model RunCompletedData {
58
+ runId: Identifier;
59
+ workItemId: Identifier;
60
+ stage?: Name;
61
+ factoryVersion: CommitHash;
62
+ outcome: RunOutcome;
63
+ startedAt: utcDateTime;
64
+ endedAt: utcDateTime;
65
+ requester: Actor;
66
+ source: Name;
67
+
68
+ @minValue(0)
69
+ costUsd?: float64;
70
+
71
+ traceId?: TraceId;
72
+ pullRequests?: url[];
73
+ evidenceBundle?: url;
74
+ }
75
+
76
+ model GateRequestedData {
77
+ gateId: Identifier;
78
+ workItemId: Identifier;
79
+ runId?: Identifier;
80
+
81
+ /** The stage that waits. */
82
+ stage: Name;
83
+
84
+ /** Path to the policy in the factory definition. */
85
+ @minLength(1)
86
+ policy: string;
87
+
88
+ /** Approver groups picked by the policy. */
89
+ @minItems(1)
90
+ approvers: Name[];
91
+
92
+ /** What the approver is asked to decide, shown in the channel. */
93
+ @minLength(1)
94
+ summary: string;
95
+
96
+ links?: Link[];
97
+ expiresAt?: utcDateTime;
98
+ }
99
+
100
+ enum GateDecision {
101
+ approved,
102
+ rejected,
103
+ expired,
104
+ }
105
+
106
+ model GateDecidedData {
107
+ gateId: Identifier;
108
+ workItemId: Identifier;
109
+ decision: GateDecision;
110
+
111
+ /** The person who decided. Absent only when the gate expired. */
112
+ decidedBy?: Actor;
113
+
114
+ decidedAt: utcDateTime;
115
+ reason?: string;
116
+ }
117
+
118
+ model PrOpenedData {
119
+ url: url;
120
+ repository: url;
121
+
122
+ @minValue(1)
123
+ number: safeint;
124
+
125
+ @minLength(1)
126
+ title: string;
127
+
128
+ headBranch: string;
129
+ baseBranch: string;
130
+ headCommit: CommitHash;
131
+ author: Actor;
132
+ workItemId?: Identifier;
133
+ runId?: Identifier;
134
+ }
@@ -0,0 +1,99 @@
1
+ import "@typespec/json-schema";
2
+ import "./common.tsp";
3
+
4
+ using TypeSpec.JsonSchema;
5
+
6
+ namespace Osf.Contracts;
7
+
8
+ /** A path inside the bundle: relative, forward slashes, no "..". */
9
+ @pattern("^(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+$")
10
+ scalar BundlePath extends string;
11
+
12
+ @pattern("^[0-9a-f]{64}$")
13
+ scalar Sha256 extends string;
14
+
15
+ /**
16
+ * `evidence.json` at the root of an evidence bundle: what was verified, against which scenario IDs, with which
17
+ * artifacts and result (P10).
18
+ */
19
+ model EvidenceManifest {
20
+ /** Version of this format. */
21
+ manifestVersion: FormatVersion;
22
+
23
+ id: Identifier;
24
+ createdAt: utcDateTime;
25
+
26
+ /** The change that was verified. */
27
+ subject: EvidenceSubject;
28
+
29
+ runId?: Identifier;
30
+ workItemId?: Identifier;
31
+
32
+ /** The tool that wrote the bundle, for example proof 1.2.0. */
33
+ producer: Producer;
34
+
35
+ /** Passed only when every scenario passed. */
36
+ result: VerificationResult;
37
+
38
+ scenarios: ScenarioEvidence[];
39
+ artifacts: Artifact[];
40
+ }
41
+
42
+ model EvidenceSubject {
43
+ repository: url;
44
+ commit: CommitHash;
45
+ pullRequest?: url;
46
+ }
47
+
48
+ model Producer {
49
+ @minLength(1)
50
+ name: string;
51
+
52
+ @minLength(1)
53
+ version: string;
54
+ }
55
+
56
+ enum VerificationResult {
57
+ passed,
58
+ failed,
59
+ }
60
+
61
+ enum ScenarioResult {
62
+ passed,
63
+ failed,
64
+ skipped,
65
+ }
66
+
67
+ model ScenarioEvidence {
68
+ id: ScenarioId;
69
+ result: ScenarioResult;
70
+
71
+ /** Paths of the artifacts in this bundle that prove the scenario. */
72
+ artifacts: BundlePath[];
73
+
74
+ summary?: string;
75
+ }
76
+
77
+ enum ArtifactKind {
78
+ screenshot,
79
+ video,
80
+ `playwright-trace`,
81
+ junit,
82
+ log,
83
+ report,
84
+ other,
85
+ }
86
+
87
+ model Artifact {
88
+ path: BundlePath;
89
+ kind: ArtifactKind;
90
+
91
+ /** IANA media type, for example "image/png". */
92
+ @minLength(1)
93
+ mediaType: string;
94
+
95
+ sha256: Sha256;
96
+
97
+ @minValue(0)
98
+ bytes?: safeint;
99
+ }