pi-mega-compact 0.20.16 → 0.20.18

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 (93) hide show
  1. package/dist/config/vector-cortex.js +14 -0
  2. package/dist/config.js +1 -1
  3. package/dist/extensions/dashboard-server/api-contracts/vector-cortex-outcomes.js +13 -0
  4. package/dist/extensions/dashboard-server/route-dispatch.js +6 -0
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-vector-cortex-outcomes.js +79 -0
  7. package/dist/extensions/mega-dashboard.js +29 -9
  8. package/dist/src/config/vector-cortex.js +14 -0
  9. package/dist/src/config.js +1 -1
  10. package/dist/src/vector-cortex/controller/policy.js +158 -0
  11. package/dist/src/vector-cortex/controller/shadow.js +117 -0
  12. package/dist/src/vector-cortex/controller/types.js +85 -0
  13. package/dist/src/vector-cortex/outcomes/consent.js +69 -0
  14. package/dist/src/vector-cortex/outcomes/dataset.js +105 -0
  15. package/dist/src/vector-cortex/outcomes/emit.js +52 -0
  16. package/dist/src/vector-cortex/outcomes/ledger.js +77 -0
  17. package/dist/src/vector-cortex/outcomes/types.js +34 -0
  18. package/dist/vector-cortex/outcomes/consent.js +69 -0
  19. package/dist/vector-cortex/outcomes/dataset.js +105 -0
  20. package/dist/vector-cortex/outcomes/emit.js +52 -0
  21. package/dist/vector-cortex/outcomes/ledger.js +77 -0
  22. package/dist/vector-cortex/outcomes/types.js +34 -0
  23. package/extensions/dashboard-client/dist/assets/{AreaChart-B_FKg8xJ.js → AreaChart-BOGp2sag.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{AreaChart-B_FKg8xJ.js.map → AreaChart-BOGp2sag.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{BarChart-BU5RJ0In.js → BarChart-BHgA-QPL.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{BarChart-BU5RJ0In.js.map → BarChart-BHgA-QPL.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{CacheTab-CbX033mt.js → CacheTab-6E7lwM74.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{CacheTab-CbX033mt.js.map → CacheTab-6E7lwM74.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{EventsTab-D8Z_4gSB.js → EventsTab-BEtNmFHz.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{EventsTab-D8Z_4gSB.js.map → EventsTab-BEtNmFHz.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{HealthTab-BUUkoQXo.js → HealthTab-DGxZulRH.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{HealthTab-BUUkoQXo.js.map → HealthTab-DGxZulRH.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DKZQmY1T.js → MaintenanceTab-C86jppQE.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DKZQmY1T.js.map → MaintenanceTab-C86jppQE.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DwMGbe3h.js → MemoryMapTab-J3VVxooZ.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DwMGbe3h.js.map → MemoryMapTab-J3VVxooZ.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{MetricsTab-C85lieXW.js → MetricsTab-yn9AFumo.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{MetricsTab-C85lieXW.js.map → MetricsTab-yn9AFumo.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{OverviewTab-D7VPibBT.js → OverviewTab-BMarUiq3.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{OverviewTab-D7VPibBT.js.map → OverviewTab-BMarUiq3.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{ReposTab-eIHk_mDG.js → ReposTab-DxnzMive.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{ReposTab-eIHk_mDG.js.map → ReposTab-DxnzMive.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{SessionsTab-EYOHEzc3.js → SessionsTab-DXKpDUuC.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{SessionsTab-EYOHEzc3.js.map → SessionsTab-DXKpDUuC.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{SetupTab-DXpiXu4i.js → SetupTab-BctuV-CJ.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{SetupTab-DXpiXu4i.js.map → SetupTab-BctuV-CJ.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DUsUC_sm.js → TimeSavedCard-D8yOC96S.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DUsUC_sm.js.map → TimeSavedCard-D8yOC96S.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{TurnsTab-w7qdB0px.js → TurnsTab-BBxqGYH-.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{TurnsTab-w7qdB0px.js.map → TurnsTab-BBxqGYH-.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js +2 -0
  52. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BTF1AGCJ.js.map +1 -0
  53. package/extensions/dashboard-client/dist/assets/{WikiTab-B4hwpXpw.js → WikiTab-DdJQSagO.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{WikiTab-B4hwpXpw.js.map → WikiTab-DdJQSagO.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{button-BnbvfQjm.js → button-B4Q9h_8Y.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{button-BnbvfQjm.js.map → button-B4Q9h_8Y.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{card-DCyvWfpF.js → card-WyR31hUV.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{card-DCyvWfpF.js.map → card-WyR31hUV.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-b3HeKJLU.js → generateCategoricalChart-pTio6Bq3.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-b3HeKJLU.js.map → generateCategoricalChart-pTio6Bq3.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{index-DhlcXMlE.js → index-BoLUiNsd.js} +3 -3
  62. package/extensions/dashboard-client/dist/assets/{index-DhlcXMlE.js.map → index-BoLUiNsd.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{switch-B74pQOFy.js → switch-5dieXxoo.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{switch-B74pQOFy.js.map → switch-5dieXxoo.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{toggle-bXDrMX-A.js → toggle-BFc-7_LL.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{toggle-bXDrMX-A.js.map → toggle-BFc-7_LL.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{useSSE-Yq86SUA0.js → useSSE-BqmKNBAI.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{useSSE-Yq86SUA0.js.map → useSSE-BqmKNBAI.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/index.html +1 -1
  70. package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
  71. package/extensions/dashboard-client/src/tabs/VectorCortexOutcomesCard.tsx +43 -0
  72. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +2 -0
  73. package/extensions/dashboard-client/src/tabs/useVectorCortexPoll.ts +5 -0
  74. package/extensions/dashboard-client/src/types/vector-cortex-vc8.ts +25 -0
  75. package/extensions/dashboard-client/src/types/vector-cortex.ts +2 -0
  76. package/extensions/dashboard-server/api-contracts/vector-cortex-outcomes.ts +55 -0
  77. package/extensions/dashboard-server/route-dispatch.ts +5 -0
  78. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +6 -0
  79. package/extensions/dashboard-server/routes-vector-cortex-outcomes.ts +94 -0
  80. package/extensions/mega-dashboard.ts +27 -8
  81. package/package.json +1 -1
  82. package/src/config/vector-cortex.ts +15 -0
  83. package/src/config.ts +1 -0
  84. package/src/vector-cortex/controller/policy.ts +198 -0
  85. package/src/vector-cortex/controller/shadow.ts +136 -0
  86. package/src/vector-cortex/controller/types.ts +195 -0
  87. package/src/vector-cortex/outcomes/consent.ts +88 -0
  88. package/src/vector-cortex/outcomes/dataset.ts +132 -0
  89. package/src/vector-cortex/outcomes/emit.ts +70 -0
  90. package/src/vector-cortex/outcomes/ledger.ts +88 -0
  91. package/src/vector-cortex/outcomes/types.ts +104 -0
  92. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BmgdxV5K.js +0 -2
  93. package/extensions/dashboard-client/dist/assets/VectorCortexTab-BmgdxV5K.js.map +0 -1
@@ -0,0 +1,195 @@
1
+ /**
2
+ * controller/types.ts — VC8B policy decision + pressure type definitions.
3
+ *
4
+ * PolicyDecisionV1 is the FINITE, BOUNDED output of the policy engine: one of a
5
+ * closed action set, a token budget clamped into a configured window, and a
6
+ * machine code reason. There is deliberately no free-text field and no open
7
+ * action string — an adaptive policy whose action space can grow at runtime
8
+ * cannot be reviewed, and a budget with no ceiling is a cost incident waiting
9
+ * to happen.
10
+ *
11
+ * PressureV2 canonicalizes the context-pressure label to EXACTLY five levels
12
+ * (low/medium/high/ultra/mega). Anything else is rejected rather than coerced:
13
+ * silently mapping an unrecognized legacy label onto a neighbouring level is
14
+ * how a "high" workload quietly starts being treated as "low".
15
+ *
16
+ * Conformance IDs POL-001..025 and M7-001..015 are registered here as the
17
+ * single source of truth for the sprint's conformance rows.
18
+ *
19
+ * PREVENT-PI-004: type definitions only, no network code.
20
+ * PREVENT-011: no `any` type.
21
+ */
22
+
23
+ /** Schema version for PolicyDecisionV1. */
24
+ export const POLICY_DECISION_SCHEMA_V1 = "policy-decision-v1";
25
+
26
+ /** Schema version for PressureV2. */
27
+ export const PRESSURE_SCHEMA_V2 = "pressure-v2";
28
+
29
+ /** Failure code when a pressure label is outside the canonical five levels. */
30
+ export const POL_PRESSURE_UNKNOWN = "POL_PRESSURE_UNKNOWN";
31
+
32
+ /** Failure code when a requested action is outside the allowed finite set. */
33
+ export const POL_ACTION_FORBIDDEN = "POL_ACTION_FORBIDDEN";
34
+
35
+ /** Failure code when a budget bound pair is itself invalid (min > max, NaN). */
36
+ export const POL_BUDGET_OUT_OF_BOUNDS = "POL_BUDGET_OUT_OF_BOUNDS";
37
+
38
+ /** Failure code when the M7 migration meets a non-canonical pressure label. */
39
+ export const M7_PRESSURE_UNKNOWN = "M7_PRESSURE_UNKNOWN";
40
+
41
+ /** Failure code when M7 copied rows do not match the legacy row count. */
42
+ export const M7_COUNT_MISMATCH = "M7_COUNT_MISMATCH";
43
+
44
+ /** Failure code when an M7 row digest does not re-derive from its own fields. */
45
+ export const M7_DIGEST_MISMATCH = "M7_DIGEST_MISMATCH";
46
+
47
+ /** Failure code when the active pressure pointer is not on the legacy version. */
48
+ export const M7_NOT_ON_LEGACY = "M7_NOT_ON_LEGACY";
49
+
50
+ /**
51
+ * The canonical five pressure levels. Ordered low -> mega; the order is
52
+ * meaningful (policy escalates monotonically with pressure) so it is exported
53
+ * as an array, not just a union.
54
+ */
55
+ export const PRESSURE_LEVELS = [
56
+ "low",
57
+ "medium",
58
+ "high",
59
+ "ultra",
60
+ "mega",
61
+ ] as const;
62
+
63
+ /** A canonical context-pressure level. */
64
+ export type PressureLevel = (typeof PRESSURE_LEVELS)[number];
65
+
66
+ /**
67
+ * The FINITE allowed policy action set. A decision may carry no other action.
68
+ * `admit` — proceed at the requested budget.
69
+ * `dampen` — proceed at a reduced budget (pressure is elevated).
70
+ * `defer` — postpone the work to a later turn.
71
+ * `escalate` — raise the budget within bounds (headroom is available).
72
+ * `reject` — refuse the work outright.
73
+ */
74
+ export const POLICY_ACTIONS = [
75
+ "admit",
76
+ "dampen",
77
+ "defer",
78
+ "escalate",
79
+ "reject",
80
+ ] as const;
81
+
82
+ /** An allowed policy action. */
83
+ export type PolicyAction = (typeof POLICY_ACTIONS)[number];
84
+
85
+ /** Machine reason codes — never free-text. */
86
+ export const POLICY_REASONS = [
87
+ "within_bounds",
88
+ "budget_clamped_low",
89
+ "budget_clamped_high",
90
+ "pressure_elevated",
91
+ "pressure_critical",
92
+ "headroom_available",
93
+ ] as const;
94
+
95
+ /** A machine reason code explaining a decision. */
96
+ export type PolicyReason = (typeof POLICY_REASONS)[number];
97
+
98
+ /** The configured token-budget window a decision is clamped into. */
99
+ export interface PolicyBounds {
100
+ readonly minBudget: number;
101
+ readonly maxBudget: number;
102
+ }
103
+
104
+ /**
105
+ * PolicyDecisionV1 — one bounded policy decision.
106
+ * `budget` is ALWAYS within the bounds that produced it; `action` is always a
107
+ * member of POLICY_ACTIONS.
108
+ */
109
+ export interface PolicyDecisionV1 {
110
+ readonly schema: typeof POLICY_DECISION_SCHEMA_V1;
111
+ readonly decisionId: string;
112
+ readonly sessionId: string;
113
+ readonly action: PolicyAction;
114
+ readonly budget: number;
115
+ readonly pressure: PressureLevel;
116
+ readonly reason: PolicyReason;
117
+ readonly ts: string;
118
+ }
119
+
120
+ /** The input a policy evaluation consumes. */
121
+ export interface PolicyInput {
122
+ readonly decisionId: string;
123
+ readonly sessionId: string;
124
+ /** The pressure label as received — validated, never coerced. */
125
+ readonly pressure: string;
126
+ /** The requested budget before clamping; any finite number. */
127
+ readonly requestedBudget: number;
128
+ readonly bounds: PolicyBounds;
129
+ readonly ts: string;
130
+ }
131
+
132
+ /**
133
+ * PressureV2 — a canonical pressure observation for a session at an effective
134
+ * sequence point.
135
+ */
136
+ export interface PressureV2 {
137
+ readonly schema: typeof PRESSURE_SCHEMA_V2;
138
+ readonly level: PressureLevel;
139
+ readonly sessionId: string;
140
+ readonly effectiveSeq: number;
141
+ readonly ts: string;
142
+ }
143
+
144
+ /** Shadow metrics — counts only, never prompt bytes or free-text. */
145
+ export interface ShadowMetrics {
146
+ /** Decisions evaluated in this shadow run. */
147
+ readonly evaluated: number;
148
+ /** Decisions whose budget was clamped at either bound. */
149
+ readonly clamped: number;
150
+ /** Inputs rejected (unknown pressure / bad bounds). */
151
+ readonly rejected: number;
152
+ /**
153
+ * Live mutations performed by the shadow engine. Structurally ALWAYS 0 —
154
+ * the shadow has no writer capability — and asserted as 0 by the sprint's
155
+ * acceptance contract.
156
+ */
157
+ readonly liveMutations: number;
158
+ }
159
+
160
+ /** The result of a shadow evaluation: decisions + metrics ONLY. */
161
+ export interface ShadowResult {
162
+ readonly decisions: ReadonlyArray<PolicyDecisionV1>;
163
+ readonly rejections: ReadonlyArray<ShadowRejection>;
164
+ readonly metrics: ShadowMetrics;
165
+ /**
166
+ * The digest of the canonical prompt as observed on entry. The shadow engine
167
+ * re-computes this on exit and the two MUST be equal (POL-SHADOW-002).
168
+ */
169
+ readonly promptDigest: string;
170
+ }
171
+
172
+ /** A rejected shadow input, reduced to its code. */
173
+ export interface ShadowRejection {
174
+ readonly decisionId: string;
175
+ readonly code: string;
176
+ }
177
+
178
+ /** Conformance IDs POL-001..POL-025 for the 25 numbered policy rows. */
179
+ export const POLICY_CONFORMANCE_IDS: readonly string[] = Array.from(
180
+ { length: 25 },
181
+ (_v, i) => `POL-${String(i + 1).padStart(3, "0")}`,
182
+ );
183
+
184
+ /** Conformance IDs M7-001..M7-015 for the 15 numbered migration rows. */
185
+ export const M7_CONFORMANCE_IDS: readonly string[] = Array.from(
186
+ { length: 15 },
187
+ (_v, i) => `M7-${String(i + 1).padStart(3, "0")}`,
188
+ );
189
+
190
+ /** Named conformance fixtures for the sprint's headline assertions. */
191
+ export const POLICY_NAMED_FIXTURES = [
192
+ "POL-CLAMP-001",
193
+ "POL-SHADOW-002",
194
+ "M7-PRESSURE-003",
195
+ ] as const;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * outcomes/consent.ts — VC8A append-only consent grants/revocations.
3
+ *
4
+ * ConsentV1 is append-only: grants and revocations are appended with an
5
+ * effective sequence number. A session has active explicit consent at time T
6
+ * if its most recent record at or before T is a grant.
7
+ *
8
+ * Dataset inclusion requires active explicit consent at export time. This
9
+ * module provides the pure consent-evaluation functions; the dataset module
10
+ * calls `hasActiveConsent` at export time.
11
+ *
12
+ * PREVENT-PI-004: no network. PREVENT-011: no `any` type.
13
+ */
14
+
15
+ import { CONSENT_SCHEMA_V1, type ConsentV1 } from "./types.js";
16
+
17
+ /**
18
+ * Evaluate whether a session has active explicit consent at or before the
19
+ * given effective sequence high-water. True if the most recent record at or
20
+ * before `effectiveHighWater` is a grant.
21
+ */
22
+ export function hasActiveConsent(
23
+ records: ReadonlyArray<ConsentV1>,
24
+ sessionId: string,
25
+ effectiveHighWater: number,
26
+ ): boolean {
27
+ const relevant = records
28
+ .filter((r) => r.sessionId === sessionId && r.effectiveSeq <= effectiveHighWater)
29
+ .sort((a, b) => a.effectiveSeq - b.effectiveSeq);
30
+ if (relevant.length === 0) return false;
31
+ return relevant[relevant.length - 1].action === "grant";
32
+ }
33
+
34
+ /**
35
+ * Append a consent grant for a session at the next effective sequence.
36
+ * Returns a new ConsentV1 record (append-only — never mutates the input).
37
+ */
38
+ export function appendGrant(
39
+ sessionId: string,
40
+ effectiveSeq: number,
41
+ ts: string,
42
+ ): ConsentV1 {
43
+ return {
44
+ schema: CONSENT_SCHEMA_V1,
45
+ consentId: `consent-${sessionId}-${effectiveSeq}`,
46
+ sessionId,
47
+ action: "grant",
48
+ effectiveSeq,
49
+ ts,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Append a consent revocation for a session at the next effective sequence.
55
+ * Returns a new ConsentV1 record (append-only — never mutates the input).
56
+ */
57
+ export function appendRevoke(
58
+ sessionId: string,
59
+ effectiveSeq: number,
60
+ ts: string,
61
+ ): ConsentV1 {
62
+ return {
63
+ schema: CONSENT_SCHEMA_V1,
64
+ consentId: `consent-${sessionId}-${effectiveSeq}`,
65
+ sessionId,
66
+ action: "revoke",
67
+ effectiveSeq,
68
+ ts,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Get the effective consent high-water for a session — the maximum effectiveSeq
74
+ * across all consent records for that session. Used by the dataset exporter
75
+ * to capture a single consent snapshot at export time.
76
+ */
77
+ export function consentHighWater(
78
+ records: ReadonlyArray<ConsentV1>,
79
+ sessionId: string,
80
+ ): number {
81
+ let max = 0;
82
+ for (const r of records) {
83
+ if (r.sessionId === sessionId && r.effectiveSeq > max) {
84
+ max = r.effectiveSeq;
85
+ }
86
+ }
87
+ return max;
88
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * outcomes/dataset.ts — VC8A dataset manifest builder.
3
+ *
4
+ * Groups outcome rows by (repo, session) so no group crosses
5
+ * train/calibration/held-out split boundaries. Revocations disappear from
6
+ * future manifests because the consent check excludes them.
7
+ *
8
+ * The manifest digest is reproducible: it is computed over the canonical
9
+ * sorted rows, so input order does not affect the digest.
10
+ *
11
+ * The build/digest functions are PURE. The flag gates ONLY the reporter seam,
12
+ * never the arithmetic.
13
+ *
14
+ * PREVENT-PI-004: no network. PREVENT-011: no `any` type.
15
+ */
16
+
17
+ import { createHash } from "node:crypto";
18
+ import {
19
+ DATASET_MANIFEST_SCHEMA_V1,
20
+ type ConsentV1,
21
+ type DatasetManifestV1,
22
+ type DatasetManifestRow,
23
+ type DatasetSplit,
24
+ type OutcomeV1,
25
+ } from "./types.js";
26
+ import { hasActiveConsent } from "./consent.js";
27
+
28
+ /** SHA-256 over the canonical sorted rows. */
29
+ export function manifestDigest(rows: ReadonlyArray<DatasetManifestRow>): string {
30
+ const sorted = [...rows].sort((a, b) => {
31
+ const ka = `${a.repoId}:${a.sessionId}:${a.outcomeId}`;
32
+ const kb = `${b.repoId}:${b.sessionId}:${b.outcomeId}`;
33
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
34
+ });
35
+ const text = sorted
36
+ .map((r) => `${r.outcomeId}|${r.repoId}|${r.sessionId}|${r.split}`)
37
+ .join("\n");
38
+ return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
39
+ }
40
+
41
+ /**
42
+ * Split a list of session groups into train/calibration/held-out. Groups are
43
+ * assigned sequentially so no group crosses split boundaries.
44
+ */
45
+ function assignSplits(
46
+ groupKeys: string[],
47
+ ): Map<string, DatasetSplit> {
48
+ const result = new Map<string, DatasetSplit>();
49
+ const n = groupKeys.length;
50
+ const trainEnd = Math.ceil(n * 0.7);
51
+ const calEnd = Math.ceil(n * 0.85);
52
+ for (let i = 0; i < n; i++) {
53
+ const split: DatasetSplit = i < trainEnd ? "train" : i < calEnd ? "calibration" : "held-out";
54
+ result.set(groupKeys[i], split);
55
+ }
56
+ return result;
57
+ }
58
+
59
+ /**
60
+ * Build a dataset manifest from outcomes, consent records, and a consent
61
+ * high-water. Only outcomes whose session has active consent at the
62
+ * high-water are included. Revoked sessions are excluded entirely.
63
+ *
64
+ * Throws OUT_CONSENT_MISSING if an outcome's session has no consent at all.
65
+ */
66
+ export function buildManifest(
67
+ outcomes: ReadonlyArray<OutcomeV1>,
68
+ consentRecords: ReadonlyArray<ConsentV1>,
69
+ consentHighWaterValue: number,
70
+ ): DatasetManifestV1 {
71
+ const groups = new Map<string, { repoId: string; sessionId: string }>();
72
+ const included: OutcomeV1[] = [];
73
+
74
+ for (const outcome of outcomes) {
75
+ const highWater = consentHighWaterValue;
76
+ if (!hasActiveConsent(consentRecords, outcome.sessionId, highWater)) {
77
+ continue;
78
+ }
79
+ included.push(outcome);
80
+ const key = `${outcome.repoId}:${outcome.sessionId}`;
81
+ if (!groups.has(key)) {
82
+ groups.set(key, { repoId: outcome.repoId, sessionId: outcome.sessionId });
83
+ }
84
+ }
85
+
86
+ const groupKeys = [...groups.keys()].sort();
87
+ const splitMap = assignSplits(groupKeys);
88
+
89
+ const rows: DatasetManifestRow[] = included.map((o) => {
90
+ const key = `${o.repoId}:${o.sessionId}`;
91
+ const split = splitMap.get(key) ?? "held-out";
92
+ return {
93
+ outcomeId: o.outcomeId,
94
+ repoId: o.repoId,
95
+ sessionId: o.sessionId,
96
+ split,
97
+ };
98
+ });
99
+
100
+ const trainRows = rows.filter((r) => r.split === "train");
101
+ const calRows = rows.filter((r) => r.split === "calibration");
102
+ const heldRows = rows.filter((r) => r.split === "held-out");
103
+
104
+ return {
105
+ schema: DATASET_MANIFEST_SCHEMA_V1,
106
+ manifestId: `manifest-${manifestDigest(rows).slice(0, 16)}`,
107
+ rows,
108
+ splitDigests: {
109
+ "train": manifestDigest(trainRows),
110
+ "calibration": manifestDigest(calRows),
111
+ "held-out": manifestDigest(heldRows),
112
+ },
113
+ createdAt: new Date(0).toISOString(),
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Check if a manifest has any nonconsented records. Returns false if every
119
+ * row's session has active consent at the given high-water.
120
+ */
121
+ export function hasNonconsentedRecords(
122
+ manifest: DatasetManifestV1,
123
+ consentRecords: ReadonlyArray<ConsentV1>,
124
+ consentHighWaterValue: number,
125
+ ): boolean {
126
+ for (const row of manifest.rows) {
127
+ if (!hasActiveConsent(consentRecords, row.sessionId, consentHighWaterValue)) {
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * outcomes/emit.ts — VC8A reporter seam (FLAG-GATED).
3
+ *
4
+ * Mirrors the VC7C diagnostics-emit pattern: a thin `safe()` wrapper around an
5
+ * optional injected `emit`, and the two event names the sprint spec requires:
6
+ * - `vector_cortex_outcome_appended` — an outcome was appended to the ledger.
7
+ * - `vector_cortex_dataset_record_excluded` — a row was excluded from a manifest.
8
+ *
9
+ * FLAG SEMANTICS. The ledger/consent/dataset arithmetic STILL RUNS regardless
10
+ * of `MEGACOMPACT_VC8A`. The flag gates ONLY this reporting + dashboard seam:
11
+ * flag-off is byte-identical to the predecessor (VC7C).
12
+ *
13
+ * PAYLOAD DISCIPLINE. These events carry only the outcomeId/sessionId codes
14
+ * and aggregate counts — never prompt bytes, response text, or free-text.
15
+ *
16
+ * PREVENT-PI-004: no network. PREVENT-011: no `any` type.
17
+ */
18
+
19
+ import { VC8A_ENABLED } from "../../config/vector-cortex.js";
20
+
21
+ /** The two structured events the VC8A reporter emits. */
22
+ export type OutcomeEventName =
23
+ | "vector_cortex_outcome_appended"
24
+ | "vector_cortex_dataset_record_excluded";
25
+
26
+ /** Optional emit fn injected by the runtime; tests pass `undefined`. */
27
+ export type OutcomeEmit = (name: string, payload: unknown) => void;
28
+
29
+ /** Run `fn` only when an emit exists; a reporting failure is never fatal. */
30
+ function safe(emit: OutcomeEmit | undefined, fn: (emit: OutcomeEmit) => void): void {
31
+ if (emit === undefined) return;
32
+ try {
33
+ fn(emit);
34
+ } catch {
35
+ // Non-fatal: a reporting failure must never break the agent loop.
36
+ }
37
+ }
38
+
39
+ /** Report an outcome appended to the ledger. Gated by the flag. */
40
+ export function reportOutcomeAppended(
41
+ emit: OutcomeEmit | undefined,
42
+ outcome: { readonly outcomeId: string; readonly sessionId: string; readonly repoId: string },
43
+ ): void {
44
+ if (!VC8A_ENABLED()) return;
45
+ safe(emit, (e) =>
46
+ e("vector_cortex_outcome_appended", {
47
+ ts: undefined,
48
+ event: "vector_cortex_outcome_appended",
49
+ outcomeId: outcome.outcomeId,
50
+ sessionId: outcome.sessionId,
51
+ repoId: outcome.repoId,
52
+ }),
53
+ );
54
+ }
55
+
56
+ /** Report a row excluded from a dataset manifest. Gated by the flag. */
57
+ export function reportDatasetRecordExcluded(
58
+ emit: OutcomeEmit | undefined,
59
+ excluded: { readonly outcomeId: string; readonly reason: string },
60
+ ): void {
61
+ if (!VC8A_ENABLED()) return;
62
+ safe(emit, (e) =>
63
+ e("vector_cortex_dataset_record_excluded", {
64
+ ts: undefined,
65
+ event: "vector_cortex_dataset_record_excluded",
66
+ outcomeId: excluded.outcomeId,
67
+ reason: excluded.reason,
68
+ }),
69
+ );
70
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * outcomes/ledger.ts — VC8A outcome ledger append + validation.
3
+ *
4
+ * Appends outcome fields for session/repo/assignment/metrics ONLY. Rejects
5
+ * prompt, response, exact bytes, or free-text payload fields as
6
+ * OUT_PAYLOAD_FORBIDDEN. The ledger is append-only — no mutation.
7
+ *
8
+ * The append/validate functions are PURE. The flag gates ONLY the reporter
9
+ * seam (emit), never the validation arithmetic, so flag-off is byte-identical
10
+ * to the predecessor.
11
+ *
12
+ * PREVENT-PI-004: no network. PREVENT-011: no `any` type.
13
+ */
14
+
15
+ import {
16
+ OUTCOME_SCHEMA_V1,
17
+ OUT_PAYLOAD_FORBIDDEN,
18
+ type OutcomeV1,
19
+ type OutcomeMetric,
20
+ } from "./types.js";
21
+
22
+ /** Fields that must never appear in an outcome — they carry payload. */
23
+ const FORBIDDEN_FIELDS = new Set([
24
+ "prompt",
25
+ "response",
26
+ "exactBytes",
27
+ "freeText",
28
+ "content",
29
+ "text",
30
+ "payload",
31
+ "body",
32
+ "message",
33
+ "reply",
34
+ "output",
35
+ "completion",
36
+ "snippet",
37
+ ]);
38
+
39
+ /** Validate that the input object has no payload-bearing fields. */
40
+ export function validateOutcome(input: Record<string, unknown>): OutcomeV1 {
41
+ for (const key of Object.keys(input)) {
42
+ if (FORBIDDEN_FIELDS.has(key)) {
43
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: key };
44
+ }
45
+ }
46
+ if (typeof input["outcomeId"] !== "string" || !input["outcomeId"]) {
47
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "outcomeId" };
48
+ }
49
+ if (typeof input["sessionId"] !== "string" || !input["sessionId"]) {
50
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "sessionId" };
51
+ }
52
+ if (typeof input["repoId"] !== "string" || !input["repoId"]) {
53
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "repoId" };
54
+ }
55
+ if (typeof input["assignment"] !== "string" || !input["assignment"]) {
56
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "assignment" };
57
+ }
58
+ const metrics = Array.isArray(input["metrics"]) ? input["metrics"] : [];
59
+ for (const m of metrics) {
60
+ if (typeof m !== "object" || m === null) {
61
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "metrics" };
62
+ }
63
+ const row = m as Record<string, unknown>;
64
+ if (typeof row["code"] !== "string" || typeof row["value"] !== "number" ||
65
+ typeof row["unit"] !== "string") {
66
+ throw { code: OUT_PAYLOAD_FORBIDDEN, field: "metrics" };
67
+ }
68
+ }
69
+ return {
70
+ schema: OUTCOME_SCHEMA_V1,
71
+ outcomeId: input["outcomeId"],
72
+ sessionId: input["sessionId"],
73
+ repoId: input["repoId"],
74
+ assignment: input["assignment"],
75
+ metrics: metrics as ReadonlyArray<OutcomeMetric>,
76
+ ts: typeof input["ts"] === "string" ? input["ts"] : new Date(0).toISOString(),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Append an outcome to the ledger (append-only). Returns the validated
82
+ * OutcomeV1 or throws OUT_PAYLOAD_FORBIDDEN.
83
+ */
84
+ export function appendOutcome(
85
+ input: Record<string, unknown>,
86
+ ): OutcomeV1 {
87
+ return validateOutcome(input);
88
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * outcomes/types.ts — VC8A consent-bound outcome ledger type definitions.
3
+ *
4
+ * Payload-free metrics only — no prompt bytes, response text, or free-text.
5
+ * OutcomeV1 carries session/repo/assignment/metrics fields ONLY.
6
+ * ConsentV1 is append-only grants/revocations with effective sequence.
7
+ * DatasetManifestV1 groups rows/digests/split (train/calibration/held-out).
8
+ *
9
+ * Conformance IDs OUT-001..OUT-025 are registered here as the single source of
10
+ * truth for the sprint's conformance rows.
11
+ *
12
+ * PREVENT-PI-004: type definitions only, no network code.
13
+ * PREVENT-011: no `any` type.
14
+ */
15
+
16
+ /** Schema version for OutcomeV1. */
17
+ export const OUTCOME_SCHEMA_V1 = "outcome-v1";
18
+
19
+ /** Schema version for ConsentV1. */
20
+ export const CONSENT_SCHEMA_V1 = "consent-v1";
21
+
22
+ /** Schema version for DatasetManifestV1. */
23
+ export const DATASET_MANIFEST_SCHEMA_V1 = "dataset-manifest-v1";
24
+
25
+ /** Failure code when a payload-bearing field is passed to the ledger. */
26
+ export const OUT_PAYLOAD_FORBIDDEN = "OUT_PAYLOAD_FORBIDDEN";
27
+
28
+ /** Failure code when consent is missing at export time. */
29
+ export const OUT_CONSENT_MISSING = "OUT_CONSENT_MISSING";
30
+
31
+ /** Failure code when a group would cross split boundaries. */
32
+ export const OUT_SPLIT_VIOLATION = "OUT_SPLIT_VIOLATION";
33
+
34
+ /**
35
+ * OutcomeV1 — a single payload-free outcome metric row.
36
+ * Contains session/repo/assignment/metrics ONLY — never prompt, response,
37
+ * exact bytes, or free-text payload fields.
38
+ */
39
+ export interface OutcomeV1 {
40
+ readonly schema: typeof OUTCOME_SCHEMA_V1;
41
+ readonly outcomeId: string;
42
+ readonly sessionId: string;
43
+ readonly repoId: string;
44
+ readonly assignment: string;
45
+ readonly metrics: ReadonlyArray<OutcomeMetric>;
46
+ readonly ts: string;
47
+ }
48
+
49
+ /** A single numeric metric with a named code — never free-text. */
50
+ export interface OutcomeMetric {
51
+ readonly code: string;
52
+ readonly value: number;
53
+ readonly unit: string;
54
+ }
55
+
56
+ /**
57
+ * ConsentV1 — append-only consent record (grant or revocation).
58
+ * An outcome is included in a dataset manifest only if its session has an
59
+ * active explicit grant at export time (no revocation after the grant).
60
+ */
61
+ export interface ConsentV1 {
62
+ readonly schema: typeof CONSENT_SCHEMA_V1;
63
+ readonly consentId: string;
64
+ readonly sessionId: string;
65
+ readonly action: "grant" | "revoke";
66
+ readonly effectiveSeq: number;
67
+ readonly ts: string;
68
+ }
69
+
70
+ /** A split assignment for a dataset row group. */
71
+ export type DatasetSplit = "train" | "calibration" | "held-out";
72
+
73
+ /**
74
+ * DatasetManifestV1 — a frozen snapshot of consented outcome rows grouped
75
+ * by (repo, session) into train/calibration/held-out splits.
76
+ */
77
+ export interface DatasetManifestV1 {
78
+ readonly schema: typeof DATASET_MANIFEST_SCHEMA_V1;
79
+ readonly manifestId: string;
80
+ readonly rows: ReadonlyArray<DatasetManifestRow>;
81
+ readonly splitDigests: Readonly<Record<DatasetSplit, string>>;
82
+ readonly createdAt: string;
83
+ }
84
+
85
+ /** A single row in a dataset manifest. */
86
+ export interface DatasetManifestRow {
87
+ readonly outcomeId: string;
88
+ readonly repoId: string;
89
+ readonly sessionId: string;
90
+ readonly split: DatasetSplit;
91
+ }
92
+
93
+ /** Conformance IDs OUT-001..OUT-025 for the 25 numbered rows. */
94
+ export const OUTCOMES_CONFORMANCE_IDS: readonly string[] = Array.from(
95
+ { length: 25 },
96
+ (_, i) => `OUT-${String(i + 1).padStart(3, "0")}`,
97
+ );
98
+
99
+ /** Named conformance fixtures for the sprint's headline assertions. */
100
+ export const OUTCOMES_NAMED_FIXTURES = [
101
+ "OUT-CONSENT-001",
102
+ "OUT-REVOKE-002",
103
+ "OUT-SPLIT-003",
104
+ ] as const;