@roubo/shared 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config-schema.ts +432 -0
- package/deep-merge.ts +38 -0
- package/gate-overrides-contract.ts +120 -0
- package/integration-types.ts +124 -0
- package/package.json +39 -0
- package/plugin-consent-schema.ts +80 -0
- package/plugin-enable-state-schema.ts +30 -0
- package/plugin-manifest-schema.ts +179 -0
- package/plugin-manifest.ts +55 -0
- package/plugin-runtime-types.ts +50 -0
- package/provision-descriptor-schema.ts +103 -0
- package/testbench-canonicalize.ts +129 -0
- package/testbench-contracts.ts +271 -0
- package/testbench-domain-types.ts +128 -0
- package/testbench-domain.ts +232 -0
- package/testbench-targeting-schema.ts +132 -0
- package/types.ts +1975 -0
- package/work-units-contract.ts +155 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Work-units contract: the single, compile-time source of truth (in `shared/`)
|
|
2
|
+
// for the published, versioned `work-units.json` file that product-dev breakdown
|
|
3
|
+
// writes and Roubo validates. This mirrors the test-cases / test-results pair in
|
|
4
|
+
// testbench-contracts.ts: a zod source schema, the inferred type, the runtime
|
|
5
|
+
// validator, and the versioned `$id` constant. The shape follows the field
|
|
6
|
+
// reference tables in .specifications/verify-gate/work-unit-model.md VERBATIM and
|
|
7
|
+
// is not re-derived (see that document's "Field reference" section).
|
|
8
|
+
//
|
|
9
|
+
// Scope note (#697): this module authors the zod source schema, the inferred
|
|
10
|
+
// type, the runtime validator, and the versioned `$id` constant. The root carries
|
|
11
|
+
// `.meta({ $id })` so the generate script + CI drift guard emit a versioned JSON
|
|
12
|
+
// Schema from it (schema/work-units.schema.json). Gate evaluation and writing
|
|
13
|
+
// work-units.json are separate, out-of-scope work.
|
|
14
|
+
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
// ── Versioned schema identifier ──
|
|
18
|
+
//
|
|
19
|
+
// The published file carries a `$schema` URI whose path embeds a semver, and a
|
|
20
|
+
// matching `schemaVersion` string kept consistent with it. A breaking change
|
|
21
|
+
// ships a major bump; additive optional fields do not bump the version.
|
|
22
|
+
|
|
23
|
+
export const WORK_UNITS_SCHEMA_ID = "https://roubo.dev/schema/work-units/v1.0.0.json";
|
|
24
|
+
export const WORK_UNITS_SCHEMA_VERSION = "1.0.0";
|
|
25
|
+
|
|
26
|
+
// ── tracker (the tracker manifestation; absent before a unit is filed) ──
|
|
27
|
+
//
|
|
28
|
+
// Tracker-agnostic: `system` spans github | ghe | jira so a unit can be filed
|
|
29
|
+
// into any supported tracker. `blocked_by_refs` is a derived projection of
|
|
30
|
+
// `depends_on` into this tracker's ref space (R1); required, may be empty.
|
|
31
|
+
export const TrackerSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
system: z.enum(["github", "ghe", "jira"]),
|
|
34
|
+
// The tracker's external id: an issue number (GitHub) or issue key (Jira).
|
|
35
|
+
ref: z.string(),
|
|
36
|
+
url: z.string(),
|
|
37
|
+
// GitHub GraphQL node id.
|
|
38
|
+
node_id: z.string().optional(),
|
|
39
|
+
// GitHub REST id.
|
|
40
|
+
db_id: z.number().optional(),
|
|
41
|
+
// Derived projection of `depends_on` into this tracker's `ref` space (R1).
|
|
42
|
+
blocked_by_refs: z.array(z.string()),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
export type Tracker = z.infer<typeof TrackerSchema>;
|
|
46
|
+
|
|
47
|
+
// ── implements (test/requirement/story linkage, first-class on every unit, R4) ──
|
|
48
|
+
export const ImplementsSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
requirement_ids: z.array(z.string()),
|
|
51
|
+
user_story_ids: z.array(z.string()),
|
|
52
|
+
// The TC- ids a unit is verified by. For a `kind: "verify"` unit this is the
|
|
53
|
+
// gating test set and must be non-empty (R4); enforced by the root refine.
|
|
54
|
+
test_case_ids: z.array(z.string()),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
export type Implements = z.infer<typeof ImplementsSchema>;
|
|
58
|
+
|
|
59
|
+
// ── Unit ──
|
|
60
|
+
//
|
|
61
|
+
// `.strict()` so the generated JSON Schema emits `additionalProperties: false`
|
|
62
|
+
// and an unknown extra field is rejected. Fields and requiredness follow the
|
|
63
|
+
// work-unit-model.md "Unit" table exactly.
|
|
64
|
+
export const UnitSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
// Minted WU-NNN (bare) or <id_code>-WU-NNN (coded). Permanent identity.
|
|
67
|
+
id: z.string(),
|
|
68
|
+
title: z.string(),
|
|
69
|
+
// Our tracker-agnostic work category; the integration plugin maps it to the
|
|
70
|
+
// tracker's native type.
|
|
71
|
+
type: z.enum(["feature", "task", "spike", "bug"]),
|
|
72
|
+
// Optional durable semantic role. Absent means a plain delivery slice.
|
|
73
|
+
kind: z.enum(["e2e", "doc", "verify"]).optional(),
|
|
74
|
+
description: z.string(),
|
|
75
|
+
acceptance_criteria: z.array(z.string()),
|
|
76
|
+
milestone: z.string().optional(),
|
|
77
|
+
labels: z.array(z.string()).optional(),
|
|
78
|
+
estimate: z.number().optional(),
|
|
79
|
+
// `WU-` ids. The dependency authority (R1). Required, may be empty.
|
|
80
|
+
depends_on: z.array(z.string()),
|
|
81
|
+
// Required on every unit (R4).
|
|
82
|
+
implements: ImplementsSchema,
|
|
83
|
+
// `WU-` ids this unit spans (used by e2e / verify units).
|
|
84
|
+
covers: z.array(z.string()).optional(),
|
|
85
|
+
// Doc-unit only: the documentation artifact it updates.
|
|
86
|
+
target_path: z.string().optional(),
|
|
87
|
+
// Doc-unit only: which doc-standard rule fired.
|
|
88
|
+
trigger_reason: z.string().optional(),
|
|
89
|
+
// The tracker manifestation. Absent before the unit is filed.
|
|
90
|
+
tracker: TrackerSchema.optional(),
|
|
91
|
+
})
|
|
92
|
+
.strict();
|
|
93
|
+
export type Unit = z.infer<typeof UnitSchema>;
|
|
94
|
+
|
|
95
|
+
// ── work-units.json (the versioned envelope) ──
|
|
96
|
+
//
|
|
97
|
+
// `$schema` is constrained to the literal WORK_UNITS_SCHEMA_ID so a wrong
|
|
98
|
+
// `$schema` is rejected, and `schemaVersion` must match WORK_UNITS_SCHEMA_VERSION
|
|
99
|
+
// so the two stay consistent. The verify rule (R4) is enforced at the root: a
|
|
100
|
+
// `kind: "verify"` unit must carry a non-empty `implements.test_case_ids`.
|
|
101
|
+
export const WorkUnitsFileSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
$schema: z.literal(WORK_UNITS_SCHEMA_ID),
|
|
104
|
+
schemaVersion: z.literal(WORK_UNITS_SCHEMA_VERSION),
|
|
105
|
+
// The `.specifications/<slug>/` folder name this file lives in.
|
|
106
|
+
specSlug: z.string(),
|
|
107
|
+
units: z.array(UnitSchema),
|
|
108
|
+
})
|
|
109
|
+
.strict()
|
|
110
|
+
// Pass the literal `$id` key through `.meta()` so `z.toJSONSchema()` emits a
|
|
111
|
+
// top-level `$id` carrying the versioned identifier. The generate script + CI
|
|
112
|
+
// drift guard consume this.
|
|
113
|
+
.meta({ $id: WORK_UNITS_SCHEMA_ID });
|
|
114
|
+
export type WorkUnitsFile = z.infer<typeof WorkUnitsFileSchema>;
|
|
115
|
+
|
|
116
|
+
// ── Runtime validator ──
|
|
117
|
+
//
|
|
118
|
+
// Wraps `safeParse` (never throws) and returns a discriminated result, mirroring
|
|
119
|
+
// validateTestCases / validateTestResults. On failure, each zod issue becomes a
|
|
120
|
+
// clear `path: message` string keyed by the field that failed. The `kind:
|
|
121
|
+
// "verify"` rule is enforced here as a refinement so it produces a field-named
|
|
122
|
+
// error against `implements.test_case_ids` rather than a vague envelope error.
|
|
123
|
+
|
|
124
|
+
export type ValidationResult<T> = { ok: true; data: T } | { ok: false; errors: string[] };
|
|
125
|
+
|
|
126
|
+
function zodIssuesToFieldErrors(issues: z.ZodIssue[]): string[] {
|
|
127
|
+
return issues.map((issue) => {
|
|
128
|
+
const path = issue.path.join(".");
|
|
129
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function validateWorkUnits(raw: unknown): ValidationResult<WorkUnitsFile> {
|
|
134
|
+
const parsed = WorkUnitsFileSchema.safeParse(raw);
|
|
135
|
+
if (!parsed.success) {
|
|
136
|
+
return { ok: false, errors: zodIssuesToFieldErrors(parsed.error.issues) };
|
|
137
|
+
}
|
|
138
|
+
// R4: a `kind: "verify"` unit must have a non-empty implements.test_case_ids.
|
|
139
|
+
// Enforced after the structural parse so the error is precise and field-named.
|
|
140
|
+
// Modeled as a post-parse check (rather than a schema-level union) to keep the
|
|
141
|
+
// generated JSON Schema's per-field errors clean; the rule is still validated
|
|
142
|
+
// at runtime on every call.
|
|
143
|
+
const errors: string[] = [];
|
|
144
|
+
parsed.data.units.forEach((unit, index) => {
|
|
145
|
+
if (unit.kind === "verify" && unit.implements.test_case_ids.length === 0) {
|
|
146
|
+
errors.push(
|
|
147
|
+
`units.${index}.implements.test_case_ids: a kind:"verify" unit must list at least one test case id`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
if (errors.length > 0) {
|
|
152
|
+
return { ok: false, errors };
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, data: parsed.data };
|
|
155
|
+
}
|