@crewhaus/canary-controller 0.1.8 → 0.2.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/dist/index.d.ts CHANGED
@@ -20,8 +20,30 @@ export type RegressionGate = (input: {
20
20
  readonly verdict: "pass" | "fail";
21
21
  readonly reason?: string;
22
22
  }>;
23
- /** Stub gate that always passes. Replaced by §29 regression-runner integration. */
23
+ /**
24
+ * EXPLICIT always-pass stub. NOT the wired production default anymore
25
+ * (item 29): the CLI builds a real gate via {@link makeRegressionGate} that
26
+ * evals both versions and runs `regression-runner`'s `gate()`. Kept only for
27
+ * tests, `--dry-run`, and manual promotions that deliberately skip the eval
28
+ * gate.
29
+ */
24
30
  export declare const PASSING_GATE: RegressionGate;
31
+ /**
32
+ * Build a real {@link RegressionGate} from a caller-supplied per-step
33
+ * evaluator. The evaluator is the seam that keeps canary-controller
34
+ * dependency-inverted: it evals BOTH versions and returns the comparison
35
+ * verdict (in the CLI it runs two `eval-runner` passes and feeds the two
36
+ * `EvalRunSummary` results into `regression-runner.gate()`). Any throw from
37
+ * the evaluator is surfaced as a `fail` verdict so a broken eval never
38
+ * silently promotes a candidate — the controller then auto-rolls-back.
39
+ */
40
+ export declare function makeRegressionGate(evaluate: (input: {
41
+ readonly fromVersion: string;
42
+ readonly toVersion: string;
43
+ }) => Promise<{
44
+ readonly verdict: "pass" | "fail";
45
+ readonly reason?: string;
46
+ }>): RegressionGate;
25
47
  export type CanaryConfig = {
26
48
  readonly name: string;
27
49
  /** Currently-pinned version (control). */
package/dist/index.js CHANGED
@@ -6,8 +6,15 @@
6
6
  *
7
7
  * After `evalIntervalMs` elapses, runs an eval-spec against both versions
8
8
  * in parallel and gates promotion on `regression-runner` (Section 29 —
9
- * but ships pre-§29 with a stub gate that returns "pass" until the real
10
- * regression-runner lands).
9
+ * `gate(baseline, candidate)` over two `EvalRunSummary` results).
10
+ *
11
+ * The real gate is wired by the caller (item 29 — `crewhaus deploy canary`):
12
+ * canary-controller stays dependency-inverted (it does NOT import
13
+ * `regression-runner`/`eval-runner`, keeping this a leaf package). Callers
14
+ * build a `RegressionGate` with {@link makeRegressionGate}, injecting the
15
+ * per-step "eval both versions, then compare" closure. {@link PASSING_GATE}
16
+ * is retained ONLY as an explicit always-pass stub for tests and manual
17
+ * dry-runs — it is no longer the wired production default.
11
18
  *
12
19
  * Without an eval gate (manual mode), the controller waits for an
13
20
  * explicit `crewhaus deploy promote` call.
@@ -20,8 +27,34 @@ export class CanaryError extends CrewhausError {
20
27
  super("config", message, cause);
21
28
  }
22
29
  }
23
- /** Stub gate that always passes. Replaced by §29 regression-runner integration. */
30
+ /**
31
+ * EXPLICIT always-pass stub. NOT the wired production default anymore
32
+ * (item 29): the CLI builds a real gate via {@link makeRegressionGate} that
33
+ * evals both versions and runs `regression-runner`'s `gate()`. Kept only for
34
+ * tests, `--dry-run`, and manual promotions that deliberately skip the eval
35
+ * gate.
36
+ */
24
37
  export const PASSING_GATE = async () => ({ verdict: "pass" });
38
+ /**
39
+ * Build a real {@link RegressionGate} from a caller-supplied per-step
40
+ * evaluator. The evaluator is the seam that keeps canary-controller
41
+ * dependency-inverted: it evals BOTH versions and returns the comparison
42
+ * verdict (in the CLI it runs two `eval-runner` passes and feeds the two
43
+ * `EvalRunSummary` results into `regression-runner.gate()`). Any throw from
44
+ * the evaluator is surfaced as a `fail` verdict so a broken eval never
45
+ * silently promotes a candidate — the controller then auto-rolls-back.
46
+ */
47
+ export function makeRegressionGate(evaluate) {
48
+ return async (input) => {
49
+ try {
50
+ return await evaluate(input);
51
+ }
52
+ catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ return { verdict: "fail", reason: `eval gate errored: ${message}` };
55
+ }
56
+ };
57
+ }
25
58
  export function createCanaryController(opts) {
26
59
  return {
27
60
  route(config, requestId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/canary-controller",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Percent-of-traffic rollout with eval-gated promotion. Hash-routes requests across two pinned versions and auto-rolls-back on regression.",
6
6
  "main": "dist/index.js",
@@ -15,10 +15,10 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/audit-log": "0.1.8",
19
- "@crewhaus/deployment-controller": "0.1.8",
20
- "@crewhaus/errors": "0.1.8",
21
- "@crewhaus/spec-registry": "0.1.8"
18
+ "@crewhaus/audit-log": "0.2.0",
19
+ "@crewhaus/deployment-controller": "0.2.0",
20
+ "@crewhaus/errors": "0.2.0",
21
+ "@crewhaus/spec-registry": "0.2.0"
22
22
  },
23
23
  "license": "Apache-2.0",
24
24
  "author": {