@opensip-cli/contracts 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.
Files changed (92) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +31 -0
  4. package/dist/__tests__/exit-codes.test.d.ts +2 -0
  5. package/dist/__tests__/exit-codes.test.d.ts.map +1 -0
  6. package/dist/__tests__/exit-codes.test.js +185 -0
  7. package/dist/__tests__/exit-codes.test.js.map +1 -0
  8. package/dist/__tests__/manifest-reexport.test.d.ts +6 -0
  9. package/dist/__tests__/manifest-reexport.test.d.ts.map +1 -0
  10. package/dist/__tests__/manifest-reexport.test.js +38 -0
  11. package/dist/__tests__/manifest-reexport.test.js.map +1 -0
  12. package/dist/__tests__/types-only.test.d.ts +10 -0
  13. package/dist/__tests__/types-only.test.d.ts.map +1 -0
  14. package/dist/__tests__/types-only.test.js +49 -0
  15. package/dist/__tests__/types-only.test.js.map +1 -0
  16. package/dist/cli-flags.d.ts +56 -0
  17. package/dist/cli-flags.d.ts.map +1 -0
  18. package/dist/cli-flags.js +85 -0
  19. package/dist/cli-flags.js.map +1 -0
  20. package/dist/cli-flags.test.d.ts +2 -0
  21. package/dist/cli-flags.test.d.ts.map +1 -0
  22. package/dist/cli-flags.test.js +51 -0
  23. package/dist/cli-flags.test.js.map +1 -0
  24. package/dist/command-outcome.d.ts +87 -0
  25. package/dist/command-outcome.d.ts.map +1 -0
  26. package/dist/command-outcome.js +32 -0
  27. package/dist/command-outcome.js.map +1 -0
  28. package/dist/command-outcome.test.d.ts +10 -0
  29. package/dist/command-outcome.test.d.ts.map +1 -0
  30. package/dist/command-outcome.test.js +68 -0
  31. package/dist/command-outcome.test.js.map +1 -0
  32. package/dist/command-results.d.ts +501 -0
  33. package/dist/command-results.d.ts.map +1 -0
  34. package/dist/command-results.js +14 -0
  35. package/dist/command-results.js.map +1 -0
  36. package/dist/exit-codes.d.ts +44 -0
  37. package/dist/exit-codes.d.ts.map +1 -0
  38. package/dist/exit-codes.js +186 -0
  39. package/dist/exit-codes.js.map +1 -0
  40. package/dist/graph-catalog.d.ts +143 -0
  41. package/dist/graph-catalog.d.ts.map +1 -0
  42. package/dist/graph-catalog.js +13 -0
  43. package/dist/graph-catalog.js.map +1 -0
  44. package/dist/index.d.ts +62 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +56 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/recipe-default.d.ts +53 -0
  49. package/dist/recipe-default.d.ts.map +1 -0
  50. package/dist/recipe-default.js +55 -0
  51. package/dist/recipe-default.js.map +1 -0
  52. package/dist/recipe-default.test.d.ts +2 -0
  53. package/dist/recipe-default.test.d.ts.map +1 -0
  54. package/dist/recipe-default.test.js +32 -0
  55. package/dist/recipe-default.test.js.map +1 -0
  56. package/dist/score.d.ts +26 -0
  57. package/dist/score.d.ts.map +1 -0
  58. package/dist/score.js +25 -0
  59. package/dist/score.js.map +1 -0
  60. package/dist/score.test.d.ts +2 -0
  61. package/dist/score.test.d.ts.map +1 -0
  62. package/dist/score.test.js +22 -0
  63. package/dist/score.test.js.map +1 -0
  64. package/dist/session-types.d.ts +132 -0
  65. package/dist/session-types.d.ts.map +1 -0
  66. package/dist/session-types.js +11 -0
  67. package/dist/session-types.js.map +1 -0
  68. package/dist/signal-envelope.d.ts +118 -0
  69. package/dist/signal-envelope.d.ts.map +1 -0
  70. package/dist/signal-envelope.js +84 -0
  71. package/dist/signal-envelope.js.map +1 -0
  72. package/dist/signal-envelope.test.d.ts +2 -0
  73. package/dist/signal-envelope.test.d.ts.map +1 -0
  74. package/dist/signal-envelope.test.js +168 -0
  75. package/dist/signal-envelope.test.js.map +1 -0
  76. package/dist/types.d.ts +78 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +5 -0
  79. package/dist/types.js.map +1 -0
  80. package/dist/verbose-detail.d.ts +26 -0
  81. package/dist/verbose-detail.d.ts.map +1 -0
  82. package/dist/verbose-detail.js +75 -0
  83. package/dist/verbose-detail.js.map +1 -0
  84. package/dist/verbose-detail.test.d.ts +2 -0
  85. package/dist/verbose-detail.test.d.ts.map +1 -0
  86. package/dist/verbose-detail.test.js +53 -0
  87. package/dist/verbose-detail.test.js.map +1 -0
  88. package/dist/verdict-envelope.test.d.ts +8 -0
  89. package/dist/verdict-envelope.test.d.ts.map +1 -0
  90. package/dist/verdict-envelope.test.js +67 -0
  91. package/dist/verdict-envelope.test.js.map +1 -0
  92. package/package.json +56 -0
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { BUILTIN_DEFAULT_RECIPE, resolveToolRecipeName } from './recipe-default.js';
3
+ describe('resolveToolRecipeName (ADR-0022 precedence + tolerance)', () => {
4
+ it('explicit --recipe wins over every config source and is strict', () => {
5
+ expect(resolveToolRecipeName({
6
+ explicit: 'backend',
7
+ toolRecipe: 'graph-core',
8
+ })).toEqual({ name: 'backend', source: 'flag', tolerant: false });
9
+ });
10
+ it('falls to <tool>.recipe when no flag, and is tolerant', () => {
11
+ expect(resolveToolRecipeName({ toolRecipe: 'graph-core' })).toEqual({
12
+ name: 'graph-core',
13
+ source: 'tool-config',
14
+ tolerant: true,
15
+ });
16
+ });
17
+ it('returns the builtin default when nothing is configured', () => {
18
+ expect(resolveToolRecipeName({})).toEqual({
19
+ name: BUILTIN_DEFAULT_RECIPE,
20
+ source: 'builtin',
21
+ tolerant: true,
22
+ });
23
+ });
24
+ it('treats empty-string config values as unset (precedence skips them)', () => {
25
+ expect(resolveToolRecipeName({ explicit: '', toolRecipe: '' })).toEqual({
26
+ name: BUILTIN_DEFAULT_RECIPE,
27
+ source: 'builtin',
28
+ tolerant: true,
29
+ });
30
+ });
31
+ });
32
+ //# sourceMappingURL=recipe-default.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-default.test.js","sourceRoot":"","sources":["../src/recipe-default.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEpF,QAAQ,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACvE,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CACJ,qBAAqB,CAAC;YACpB,QAAQ,EAAE,SAAS;YACnB,UAAU,EAAE,YAAY;SACzB,CAAC,CACH,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,qBAAqB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YAClE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,aAAa;YACrB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACxC,IAAI,EAAE,sBAAsB;YAC5B,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,qBAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACtE,IAAI,EAAE,sBAAsB;YAC5B,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Canonical pass rate for a run.
3
+ *
4
+ * `score` is a shared field on the run verdict (`SignalEnvelope.verdict`)
5
+ * and `StoredSession` that the dashboard renders as the "PASS RATE" column.
6
+ * It has ONE meaning across every tool: the percentage of units that passed.
7
+ *
8
+ * A unit passes when it has no error-severity signals — warnings alone
9
+ * do not fail a unit. So a warnings-only run
10
+ * scores 100, consistent with the WARN-but-passing status the dashboard
11
+ * shows for it. An empty run (no checks) also scores 100, matching the
12
+ * fitness gate-baseline convention so `--gate-compare` does not report a
13
+ * phantom regression on an empty recipe.
14
+ *
15
+ * This lives in contracts — the layer below every tool — because the
16
+ * formula must be identical everywhere `score` is produced. Each tool
17
+ * previously rolled its own: fitness used passed/total, but graph used a
18
+ * findings-count penalty (`100 - findings`), which disagreed with its own
19
+ * passed/total summary and rendered 0% for warnings-only runs. Route all
20
+ * score computation through here so they cannot drift again.
21
+ */
22
+ export declare function passRate(summary: {
23
+ readonly total: number;
24
+ readonly passed: number;
25
+ }): number;
26
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE7F"}
package/dist/score.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Canonical pass rate for a run.
3
+ *
4
+ * `score` is a shared field on the run verdict (`SignalEnvelope.verdict`)
5
+ * and `StoredSession` that the dashboard renders as the "PASS RATE" column.
6
+ * It has ONE meaning across every tool: the percentage of units that passed.
7
+ *
8
+ * A unit passes when it has no error-severity signals — warnings alone
9
+ * do not fail a unit. So a warnings-only run
10
+ * scores 100, consistent with the WARN-but-passing status the dashboard
11
+ * shows for it. An empty run (no checks) also scores 100, matching the
12
+ * fitness gate-baseline convention so `--gate-compare` does not report a
13
+ * phantom regression on an empty recipe.
14
+ *
15
+ * This lives in contracts — the layer below every tool — because the
16
+ * formula must be identical everywhere `score` is produced. Each tool
17
+ * previously rolled its own: fitness used passed/total, but graph used a
18
+ * findings-count penalty (`100 - findings`), which disagreed with its own
19
+ * passed/total summary and rendered 0% for warnings-only runs. Route all
20
+ * score computation through here so they cannot drift again.
21
+ */
22
+ export function passRate(summary) {
23
+ return summary.total > 0 ? Math.round((summary.passed / summary.total) * 100) : 100;
24
+ }
25
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,QAAQ,CAAC,OAA4D;IACnF,OAAO,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACtF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=score.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.test.d.ts","sourceRoot":"","sources":["../src/score.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { passRate } from './score.js';
3
+ describe('passRate', () => {
4
+ it('is the rounded passed/total percentage', () => {
5
+ expect(passRate({ total: 4, passed: 4 })).toBe(100);
6
+ expect(passRate({ total: 4, passed: 1 })).toBe(25);
7
+ expect(passRate({ total: 2, passed: 1 })).toBe(50);
8
+ });
9
+ it('rounds to the nearest integer', () => {
10
+ expect(passRate({ total: 3, passed: 1 })).toBe(33);
11
+ expect(passRate({ total: 3, passed: 2 })).toBe(67);
12
+ });
13
+ it('is 100 for an empty run (no checks) — matches the gate-baseline convention', () => {
14
+ expect(passRate({ total: 0, passed: 0 })).toBe(100);
15
+ });
16
+ it('does not penalize warnings: all-passed scores 100 regardless of finding volume', () => {
17
+ // The graph regression: every check passed (warnings only), so the
18
+ // pass rate is 100 even though the run had many findings.
19
+ expect(passRate({ total: 1, passed: 1 })).toBe(100);
20
+ });
21
+ });
22
+ //# sourceMappingURL=score.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.test.js","sourceRoot":"","sources":["../src/score.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,mEAAmE;QACnE,0DAA0D;QAC1D,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Session persistence contract type.
3
+ *
4
+ * `StoredSession` is the cross-tool shape every tool's session row shares.
5
+ * The runtime (SessionRepo, schema, id/filename helpers) lives in
6
+ * @opensip-cli/session-store; this type stays in contracts as the shared
7
+ * surface tools and the dashboard agree on (audit 2026-05-29, contracts
8
+ * split).
9
+ */
10
+ import type { SignalEnvelope } from './signal-envelope.js';
11
+ import type { ToolShortId } from '@opensip-cli/core';
12
+ /**
13
+ * A persisted tool-run session.
14
+ *
15
+ * Holds only **generic** columns every tool shares — score, pass/fail,
16
+ * lifecycle timing, host metrics, provenance. Per-session detail is
17
+ * tool-specific and lives in the opaque {@link StoredSession.payload}:
18
+ * `contracts` holds ZERO tool vocabulary. Each tool owns the shape of its own
19
+ * payload; the dashboard, as the presentation owner, reads the payload and
20
+ * renders it — the same producer/consumer split used for `GraphCatalog`.
21
+ *
22
+ * ## Host-owned run lifecycle timing (host-owned-run-timing)
23
+ *
24
+ * - `startedAt` is the wall-clock start of the user-initiated tool run,
25
+ * captured by the host run-lifecycle plane *after* the per-run `RunScope`
26
+ * exists and *before* any tool-owned setup / handler / live-renderer work.
27
+ * - `completedAt` is the wall-clock instant the tool handler / live renderer
28
+ * returned its completion data to the host, *before* host persistence,
29
+ * render, egress, or report side effects.
30
+ * - `durationMs` is the canonical tool-invocation duration (monotonic
31
+ * elapsed between the two boundaries), **not** TTY-busy time.
32
+ *
33
+ * All three are stamped exclusively by the host run-lifecycle plane from a
34
+ * single `RunLifecycle`. Tools never capture `new Date()` / `Date.now()` /
35
+ * `performance.now()` for these fields and never supply them — they return a
36
+ * `ToolSessionContribution` (verdict/score/recipe/payload) and the host owns
37
+ * the timing. See the clock taxonomy in the spec / session docs.
38
+ *
39
+ * `hostMetrics` (when present) explains *host-side* overhead — TTY occupancy,
40
+ * render, persist, egress, total command time — separately from the canonical
41
+ * `durationMs`. It is a hydrated projection of the sibling host-metrics record
42
+ * keyed by session id, not necessarily a column on the `sessions` table.
43
+ */
44
+ export interface StoredSession {
45
+ readonly id: string;
46
+ readonly tool: ToolShortId;
47
+ /** Wall-clock start of the tool run (host run-lifecycle plane). */
48
+ readonly startedAt: string;
49
+ /** Wall-clock completion of the tool run (when the tool returned to the host). */
50
+ readonly completedAt: string;
51
+ readonly cwd: string;
52
+ readonly recipe?: string;
53
+ readonly score: number;
54
+ readonly passed: boolean;
55
+ /** Canonical tool-invocation duration (monotonic), not TTY-busy time. */
56
+ readonly durationMs: number;
57
+ /**
58
+ * Host-side overhead metrics for this run, hydrated from the sibling
59
+ * host-metrics record. Absent when no metrics were captured. These are NOT
60
+ * a replacement for `durationMs` — they answer "where did host-side cost
61
+ * accumulate", not "how long did the tool take".
62
+ */
63
+ readonly hostMetrics?: StoredSessionHostMetrics;
64
+ /**
65
+ * Tool-owned opaque per-session detail. `contracts` treats this as
66
+ * `unknown` and never inspects it; the producing tool owns and validates
67
+ * its shape. Absent for tools that persist no detail.
68
+ *
69
+ * ## Inner payload versioning convention
70
+ *
71
+ * All tools (first-party and third-party) MUST stamp new payloads they
72
+ * persist with a top-level numeric `"__version": N` (double-underscore
73
+ * prefix signals infrastructure, not user data). Start at `1` for the
74
+ * current shape.
75
+ *
76
+ * The host (contracts / session-store / datastore) stays ignorant of tool
77
+ * shapes — only the producing tool owns the semantics of its version.
78
+ * The structural decoder tolerates legacy payloads (missing `__version`
79
+ * treated as v1 / legacy with `fidelity: 'projection'`).
80
+ *
81
+ * Example of a versioned tool payload (illustrative v1):
82
+ * ```json
83
+ * {
84
+ * "__version": 1,
85
+ * "summary": { "total": 42, "passed": 40, "failed": 2, "errors": 1, "warnings": 1 },
86
+ * "checks": [ ... ]
87
+ * }
88
+ * ```
89
+ *
90
+ * - Additive / optional fields: safe, no version bump required.
91
+ * - Breaking (remove/rename/ reinterpret required field, change shapes
92
+ * that replay code depends on): bump `__version` + follow documented
93
+ * deprecation window (see extending guide and ADR-0050).
94
+ */
95
+ readonly payload?: unknown;
96
+ }
97
+ /**
98
+ * Host-side overhead metrics for a single tool run, captured on separate
99
+ * clocks from the canonical `durationMs` (host-owned-run-timing §5.3/§5.4).
100
+ *
101
+ * Stored in a sibling host-metrics record keyed by session id (so render /
102
+ * egress metrics — known only *after* the initial session write — can be
103
+ * upserted without rewriting the session row) and hydrated back onto
104
+ * {@link StoredSession.hostMetrics} for readers. Every field is optional: a
105
+ * given run only populates the metrics observable for its path (e.g.
106
+ * `ttyBusyMs` only for live/TTY runs).
107
+ */
108
+ export interface StoredSessionHostMetrics {
109
+ /** Time the interactive TTY was occupied by the live view. */
110
+ readonly ttyBusyMs?: number;
111
+ /** Time spent rendering the final static/live completion output. */
112
+ readonly renderMs?: number;
113
+ /** Time spent writing the session row + payload. */
114
+ readonly persistMs?: number;
115
+ /** Time spent in host-owned post-run signal/report delivery. */
116
+ readonly egressMs?: number;
117
+ /** Elapsed time for the full command action, including host pre/post work. */
118
+ readonly totalCommandMs?: number;
119
+ }
120
+ /** A tool-owned replay of a stored session projection. */
121
+ export interface ToolSessionReplay<R = unknown> {
122
+ /** Human-renderable result for the shared CLI render seam. */
123
+ readonly result: R;
124
+ /** Machine-readable reconstructed envelope emitted by `sessions show --json`. */
125
+ readonly envelope: SignalEnvelope;
126
+ /**
127
+ * Stored sessions currently persist dashboard/detail projections, not the full
128
+ * live run envelope. This marker makes that explicit for machine consumers.
129
+ */
130
+ readonly fidelity: 'projection';
131
+ }
132
+ //# sourceMappingURL=session-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-types.d.ts","sourceRoot":"","sources":["../src/session-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,wBAAwB,CAAC;IAChD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,OAAO;IAC5C,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnB,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CACjC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Session persistence contract type.
3
+ *
4
+ * `StoredSession` is the cross-tool shape every tool's session row shares.
5
+ * The runtime (SessionRepo, schema, id/filename helpers) lives in
6
+ * @opensip-cli/session-store; this type stays in contracts as the shared
7
+ * surface tools and the dashboard agree on (audit 2026-05-29, contracts
8
+ * split).
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=session-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-types.js","sourceRoot":"","sources":["../src/session-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,118 @@
1
+ import type { FingerprintStrategy, Signal, ToolShortId, VerdictPolicy } from '@opensip-cli/core';
2
+ /**
3
+ * Run-level verdict header. `passed` ⇔ "no `critical`/`high` signals";
4
+ * `score` is the canonical {@link passRate} over `summary`.
5
+ */
6
+ export interface RunVerdict {
7
+ readonly score: number;
8
+ readonly passed: boolean;
9
+ readonly summary: {
10
+ readonly total: number;
11
+ readonly passed: number;
12
+ readonly failed: number;
13
+ readonly errors: number;
14
+ readonly warnings: number;
15
+ };
16
+ }
17
+ /**
18
+ * Per-unit fact sidecar. A "unit" is the neutral umbrella over a fit check, a
19
+ * graph rule, and a sim scenario (ADR-0011). Carries ONLY what a flat
20
+ * `Signal[]` cannot express: that a unit ran, whether it errored, and timing.
21
+ * `passed` ⇔ "that unit emitted no `critical`/`high` signals".
22
+ */
23
+ export interface UnitResult {
24
+ readonly slug: string;
25
+ readonly passed: boolean;
26
+ readonly violationCount?: number;
27
+ readonly durationMs: number;
28
+ readonly error?: string;
29
+ /**
30
+ * Files the unit validated/scanned this run (fitness's "Validated" column).
31
+ * A per-unit fact a flat `Signal[]` cannot express — a check that scanned
32
+ * 450 files and emitted 0 signals still has `filesValidated: 450`. Optional:
33
+ * graph rules / sim scenarios do not scan files and omit it (the terminal
34
+ * table renders the column blank for those tools). `itemType` names the
35
+ * scanned noun (`files` / `packages` / …) for the column label.
36
+ */
37
+ readonly filesValidated?: number;
38
+ readonly itemType?: string;
39
+ /**
40
+ * Findings suppressed by an inline `@fitness-ignore` directive this run
41
+ * (fitness's "Ignores" column). Like {@link filesValidated}, a per-unit fact
42
+ * not recoverable from the (post-suppression) signal list; optional and
43
+ * omitted by tools without a suppression mechanism.
44
+ */
45
+ readonly ignoredCount?: number;
46
+ }
47
+ /** The one tool-run output envelope. The `CommandResult` payload every tool returns. */
48
+ export interface SignalEnvelope {
49
+ readonly schemaVersion: 2;
50
+ readonly tool: ToolShortId;
51
+ readonly recipe?: string;
52
+ readonly runId: string;
53
+ readonly createdAt: string;
54
+ readonly verdict: RunVerdict;
55
+ readonly units: readonly UnitResult[];
56
+ readonly signals: readonly Signal[];
57
+ /** Graph-only edge-fidelity marker, carried over from CliOutput.resolutionMode. */
58
+ readonly resolutionMode?: 'exact' | 'fast';
59
+ }
60
+ /**
61
+ * Input to {@link buildSignalEnvelope}. `signals` are already the wire
62
+ * currency; `units` carry the per-unit ran/errored/timing facts. `runId` and
63
+ * `createdAt` are supplied by the caller (formatter-purity contract: no
64
+ * `Date.now()`/`randomUUID` in this layer, so tests stay deterministic).
65
+ */
66
+ export interface BuildEnvelopeInput {
67
+ readonly tool: ToolShortId;
68
+ readonly recipe?: string;
69
+ readonly runId: string;
70
+ readonly createdAt: string;
71
+ readonly units: readonly UnitResult[];
72
+ readonly signals: readonly Signal[];
73
+ readonly resolutionMode?: 'exact' | 'fast';
74
+ /**
75
+ * The tool's resolved findings policy (ADR-0035). `verdict.passed` is computed
76
+ * from `(errors, warnings)` against this — replacing the old `errors === 0`.
77
+ */
78
+ readonly policy: VerdictPolicy;
79
+ /**
80
+ * `true` when the run faulted OUTSIDE its units — e.g. fit's plugin-load
81
+ * errors, which occur before any unit exists. Unit-level faults are derived
82
+ * from `UnitResult.error` and need not be passed here. A faulted run always
83
+ * FAILs, independent of the findings policy (a crash ≠ "0 errors found").
84
+ */
85
+ readonly runFaulted: boolean;
86
+ /**
87
+ * The tool's baseline-identity strategy (ADR-0036). {@link buildSignalEnvelope}
88
+ * stamps `Signal.fingerprint` with it at construction, so every envelope
89
+ * reaches the host seams (gate save/compare, cloud, SARIF) already stamped —
90
+ * the "tool forgot to stamp" failure class cannot occur for an envelope built
91
+ * here. Omitted ⇒ {@link defaultFingerprintStrategy} (`ruleId|filePath|line|col`),
92
+ * which is exactly the documented inheritance for a tool that declares no
93
+ * `Tool.fingerprintStrategy`. Stamping is idempotent: a signal that already
94
+ * carries a non-empty `fingerprint` is preserved byte-for-byte, so a tool may
95
+ * still stamp earlier (e.g. at `createSignal`) without double-hashing.
96
+ */
97
+ readonly fingerprintStrategy?: FingerprintStrategy;
98
+ }
99
+ /**
100
+ * Assemble a {@link SignalEnvelope} from a run's units + signals.
101
+ *
102
+ * Centralises the verdict/summary computation so all three tools agree on
103
+ * "`passed` ⇔ no critical/high" and the score definition. Pure: no IO, no
104
+ * clock, no id generation — `runId`/`createdAt` arrive on the input.
105
+ *
106
+ * - `summary.total/passed/failed` come from `units` (units are what "ran").
107
+ * - `summary.errors/warnings` come from `signals` (critical|high → error,
108
+ * else warning).
109
+ * - `score = passRate(summary)`.
110
+ * - `verdict.passed` (ADR-0035) ⇔ the run did not fault, no unit errored, AND
111
+ * the error/warning counts pass the tool's findings `policy`. This is the
112
+ * single verdict that drives both the exit code and the headline.
113
+ * - Every signal is fingerprint-stamped (ADR-0036) with
114
+ * `input.fingerprintStrategy` (host default when omitted; idempotent for
115
+ * pre-stamped signals), so the built envelope is gate-ready by construction.
116
+ */
117
+ export declare function buildSignalEnvelope(input: BuildEnvelopeInput): SignalEnvelope;
118
+ //# sourceMappingURL=signal-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-envelope.d.ts","sourceRoot":"","sources":["../src/signal-envelope.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEjG;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,mFAAmF;IACnF,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,cAAc,CA+C7E"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * SignalEnvelope — the universal tool-run output currency (ADR-0011).
3
+ *
4
+ * Every tool run yields one envelope: the flat `Signal[]` a run produced
5
+ * (the same currency the cloud egresses via {@link SignalBatch}, ADR-0008)
6
+ * plus run identity (`tool`, `recipe`, `runId`, `createdAt`), a `verdict`
7
+ * header, and a `units[]` sidecar (so "ran, errored, 0 signals" is expressible
8
+ * — a flat signal list cannot carry per-unit ran/errored/timing facts). On the
9
+ * CLI wire this envelope rides under `.envelope` of a `CommandOutcome`
10
+ * (ADR-0024), so a `--json` consumer reads `jq '.envelope.verdict.passed'` /
11
+ * `.envelope.verdict.score`.
12
+ *
13
+ * This is intentionally close to {@link SignalBatch} (the cloud egress shape):
14
+ * the cloud sink ships the signals as-is, adding `repo` identity and dropping
15
+ * `verdict`/`units`. It lives in `contracts` — the tool↔runner contract layer —
16
+ * because it is the `CommandResult` payload every tool returns and the
17
+ * composition root consumes.
18
+ *
19
+ * `schemaVersion` is the output-contract version, independent of any package
20
+ * version. It is `2`, succeeding the implicit `CliOutput` "1.0" husk this
21
+ * envelope replaces.
22
+ */
23
+ import { defaultFingerprintStrategy, policyPasses, SeverityPolicy, stampFingerprints, } from '@opensip-cli/core';
24
+ import { passRate } from './score.js';
25
+ /**
26
+ * Assemble a {@link SignalEnvelope} from a run's units + signals.
27
+ *
28
+ * Centralises the verdict/summary computation so all three tools agree on
29
+ * "`passed` ⇔ no critical/high" and the score definition. Pure: no IO, no
30
+ * clock, no id generation — `runId`/`createdAt` arrive on the input.
31
+ *
32
+ * - `summary.total/passed/failed` come from `units` (units are what "ran").
33
+ * - `summary.errors/warnings` come from `signals` (critical|high → error,
34
+ * else warning).
35
+ * - `score = passRate(summary)`.
36
+ * - `verdict.passed` (ADR-0035) ⇔ the run did not fault, no unit errored, AND
37
+ * the error/warning counts pass the tool's findings `policy`. This is the
38
+ * single verdict that drives both the exit code and the headline.
39
+ * - Every signal is fingerprint-stamped (ADR-0036) with
40
+ * `input.fingerprintStrategy` (host default when omitted; idempotent for
41
+ * pre-stamped signals), so the built envelope is gate-ready by construction.
42
+ */
43
+ export function buildSignalEnvelope(input) {
44
+ const total = input.units.length;
45
+ const passed = input.units.filter((u) => u.passed).length;
46
+ const failed = total - passed;
47
+ // ADR-0036: fingerprints are an envelope-construction concern. Stamping here
48
+ // (tool strategy, host default when none) guarantees every built envelope is
49
+ // gate-ready, instead of trusting each tool to remember a post-hoc stamp that
50
+ // would otherwise only fail at the first `--gate-save`.
51
+ const signals = stampFingerprints(input.signals, input.fingerprintStrategy ?? defaultFingerprintStrategy);
52
+ let errors = 0;
53
+ let warnings = 0;
54
+ for (const signal of signals) {
55
+ // The gate's error/warning split is the central policy predicate (§5.9), one
56
+ // source of truth shared with the verdict / terminal table / SARIF level.
57
+ if (SeverityPolicy.isError(signal.severity))
58
+ errors += 1;
59
+ else
60
+ warnings += 1;
61
+ }
62
+ const summary = { total, passed, failed, errors, warnings };
63
+ // A unit that errored (a check that threw / timed out) is a fault, not a
64
+ // finding — it FAILs the run regardless of the findings policy. Pre-unit
65
+ // faults (e.g. fit plugin-load) arrive on `runFaulted` since no unit exists.
66
+ const unitFaulted = input.units.some((u) => u.error !== undefined);
67
+ const verdict = {
68
+ score: passRate(summary),
69
+ passed: !input.runFaulted && !unitFaulted && policyPasses({ errors, warnings }, input.policy),
70
+ summary,
71
+ };
72
+ return {
73
+ schemaVersion: 2,
74
+ tool: input.tool,
75
+ recipe: input.recipe,
76
+ runId: input.runId,
77
+ createdAt: input.createdAt,
78
+ verdict,
79
+ units: input.units,
80
+ signals,
81
+ resolutionMode: input.resolutionMode,
82
+ };
83
+ }
84
+ //# sourceMappingURL=signal-envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-envelope.js","sourceRoot":"","sources":["../src/signal-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EACL,0BAA0B,EAC1B,YAAY,EACZ,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAyGtC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;IAC1D,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IAE9B,6EAA6E;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,wDAAwD;IACxD,MAAM,OAAO,GAAG,iBAAiB,CAC/B,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,mBAAmB,IAAI,0BAA0B,CACxD,CAAC;IAEF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,6EAA6E;QAC7E,0EAA0E;QAC1E,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,CAAC,CAAC;;YACpD,QAAQ,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAE5D,yEAAyE;IACzE,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAEnE,MAAM,OAAO,GAAe;QAC1B,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC;QACxB,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,WAAW,IAAI,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC;QAC7F,OAAO;KACR,CAAC;IAEF,OAAO;QACL,aAAa,EAAE,CAAC;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO;QACP,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO;QACP,cAAc,EAAE,KAAK,CAAC,cAAc;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=signal-envelope.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal-envelope.test.d.ts","sourceRoot":"","sources":["../src/signal-envelope.test.ts"],"names":[],"mappings":""}