@kontextmind/kxm 0.6.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.kxm/README.md +14 -0
  3. package/.kxm/assets/README.md +5 -0
  4. package/.kxm/assets/retrospectives/README.md +5 -0
  5. package/.kxm/config/README.md +5 -0
  6. package/.kxm/config/agents.json +43 -0
  7. package/.kxm/config/env.example +56 -0
  8. package/.kxm/config/update.example.yaml +9 -0
  9. package/.kxm/config/workflows/fix.json +160 -0
  10. package/.kxm/config/workflows/jira-development.json +116 -0
  11. package/.kxm/config/workflows/provenance-quorum.json +150 -0
  12. package/.kxm/config/workflows/v04-dogfood.json +72 -0
  13. package/CHANGELOG.md +465 -0
  14. package/LICENSE +21 -0
  15. package/README.md +306 -0
  16. package/SECURITY.md +72 -0
  17. package/docs/README.md +48 -0
  18. package/docs/agent-communication-envelopes-and-gates.md +553 -0
  19. package/docs/architecture.md +242 -0
  20. package/docs/assignment-runner.md +241 -0
  21. package/docs/configuration.md +361 -0
  22. package/docs/continuous-improvement.md +114 -0
  23. package/docs/getting-started.md +253 -0
  24. package/docs/kxm-handbook.md +1090 -0
  25. package/docs/operations.md +205 -0
  26. package/docs/provenance-gates.md +291 -0
  27. package/docs/skills.md +45 -0
  28. package/docs/templates/README.md +95 -0
  29. package/docs/templates/adr.md +88 -0
  30. package/docs/templates/architecture.md +120 -0
  31. package/docs/templates/bug-fix.md +109 -0
  32. package/docs/templates/feature.md +108 -0
  33. package/docs/templates/handoff.md +72 -0
  34. package/docs/templates/postmortem.md +77 -0
  35. package/docs/templates/research.md +100 -0
  36. package/docs/templates/review.md +85 -0
  37. package/docs/templates/runbook.md +73 -0
  38. package/docs/templates/test-plan.md +87 -0
  39. package/docs/templates/test-report.md +72 -0
  40. package/docs/test-matrix.md +121 -0
  41. package/docs/troubleshooting.md +249 -0
  42. package/docs/vnext/README.md +62 -0
  43. package/docs/vnext/architecture.md +185 -0
  44. package/docs/vnext/effects-and-recovery.md +172 -0
  45. package/docs/vnext/lifecycles.md +235 -0
  46. package/docs/vnext/migration.md +220 -0
  47. package/docs/vnext/routing.md +184 -0
  48. package/docs/vnext/synchronization.md +172 -0
  49. package/docs/vnext/terminology.md +240 -0
  50. package/docs/vnext/validation.md +335 -0
  51. package/docs/webhook-workflows.md +240 -0
  52. package/docs/workflow-guide.md +1150 -0
  53. package/examples/README.md +102 -0
  54. package/examples/provenance-workflow.json +40 -0
  55. package/examples/requester.ts +30 -0
  56. package/examples/reviewer-agent.ts +29 -0
  57. package/examples/roundtrip.ts +46 -0
  58. package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
  59. package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
  60. package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
  61. package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
  62. package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
  63. package/examples/vnext/.kxm/agents/planner.yaml +13 -0
  64. package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
  65. package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
  66. package/examples/vnext/.kxm/gates.yaml +8 -0
  67. package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
  68. package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
  69. package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
  70. package/examples/vnext/.kxm/models/implementation.yaml +14 -0
  71. package/examples/vnext/.kxm/models/primary.yaml +17 -0
  72. package/examples/vnext/.kxm/prices.yaml +111 -0
  73. package/examples/vnext/.kxm/project/env.yaml +7 -0
  74. package/examples/vnext/.kxm/project.yaml +32 -0
  75. package/examples/vnext/.kxm/repo/repo.yaml +8 -0
  76. package/examples/vnext/.kxm/workflows/default.yaml +92 -0
  77. package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
  78. package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
  79. package/examples/vnext/README.md +53 -0
  80. package/examples/vnext/records/assignment-result-recorded.json +63 -0
  81. package/examples/vnext/records/assignment-result.json +46 -0
  82. package/examples/vnext/records/context-candidate.json +42 -0
  83. package/examples/vnext/records/delivery-manifest.json +66 -0
  84. package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
  85. package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
  86. package/examples/vnext/records/run-created.json +54 -0
  87. package/examples/vnext/records/sync-event.json +65 -0
  88. package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
  89. package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
  90. package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
  91. package/examples/workflow-signal.ts +63 -0
  92. package/package.json +129 -0
  93. package/plugins/kxm/.claude-plugin/plugin.json +73 -0
  94. package/plugins/kxm/.mcp.json +19 -0
  95. package/plugins/kxm/README.md +93 -0
  96. package/plugins/kxm/dist/cli.js +42853 -0
  97. package/plugins/kxm/dist/client.js +416 -0
  98. package/plugins/kxm/dist/core.js +1823 -0
  99. package/plugins/kxm/dist/extension.js +3797 -0
  100. package/plugins/kxm/dist/mcp-server.js +17104 -0
  101. package/plugins/kxm/dist/runtime.js +23361 -0
  102. package/plugins/kxm/dist/server.js +13640 -0
  103. package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
  104. package/plugins/kxm/package.json +12 -0
  105. package/plugins/kxm/skills/kxm/SKILL.md +97 -0
  106. package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
  107. package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
  108. package/plugins/kxm/src/arbiter.ts +355 -0
  109. package/plugins/kxm/src/artifacts-exist.ts +62 -0
  110. package/plugins/kxm/src/autocomplete.ts +236 -0
  111. package/plugins/kxm/src/cli.ts +3707 -0
  112. package/plugins/kxm/src/client.ts +614 -0
  113. package/plugins/kxm/src/commands.ts +1063 -0
  114. package/plugins/kxm/src/config.ts +290 -0
  115. package/plugins/kxm/src/context/providers.ts +101 -0
  116. package/plugins/kxm/src/context-packet.ts +332 -0
  117. package/plugins/kxm/src/context.ts +499 -0
  118. package/plugins/kxm/src/core.ts +6 -0
  119. package/plugins/kxm/src/database.ts +563 -0
  120. package/plugins/kxm/src/diagnostics.ts +184 -0
  121. package/plugins/kxm/src/envelope.ts +118 -0
  122. package/plugins/kxm/src/extension.ts +895 -0
  123. package/plugins/kxm/src/external-effects.ts +299 -0
  124. package/plugins/kxm/src/github-watch.ts +255 -0
  125. package/plugins/kxm/src/hub-binding.ts +160 -0
  126. package/plugins/kxm/src/hub.ts +2502 -0
  127. package/plugins/kxm/src/improve.ts +383 -0
  128. package/plugins/kxm/src/inbox.ts +10 -0
  129. package/plugins/kxm/src/kxm-install-kind.ts +113 -0
  130. package/plugins/kxm/src/kxm-update-config.ts +39 -0
  131. package/plugins/kxm/src/kxm-update.ts +238 -0
  132. package/plugins/kxm/src/local-snapshot.ts +406 -0
  133. package/plugins/kxm/src/logger.ts +198 -0
  134. package/plugins/kxm/src/mcp-server.ts +143 -0
  135. package/plugins/kxm/src/memory.ts +385 -0
  136. package/plugins/kxm/src/nous-pi.ts +287 -0
  137. package/plugins/kxm/src/nous-provider.ts +729 -0
  138. package/plugins/kxm/src/price-calc.ts +87 -0
  139. package/plugins/kxm/src/prices.ts +121 -0
  140. package/plugins/kxm/src/protocol.ts +172 -0
  141. package/plugins/kxm/src/recovery.ts +211 -0
  142. package/plugins/kxm/src/redact.ts +26 -0
  143. package/plugins/kxm/src/retrospective.ts +400 -0
  144. package/plugins/kxm/src/routing.ts +830 -0
  145. package/plugins/kxm/src/runtime.ts +9 -0
  146. package/plugins/kxm/src/server.ts +117 -0
  147. package/plugins/kxm/src/session-work.ts +571 -0
  148. package/plugins/kxm/src/session.ts +184 -0
  149. package/plugins/kxm/src/skills.ts +535 -0
  150. package/plugins/kxm/src/state.ts +326 -0
  151. package/plugins/kxm/src/store.ts +637 -0
  152. package/plugins/kxm/src/studio-layout.ts +268 -0
  153. package/plugins/kxm/src/suggest.ts +162 -0
  154. package/plugins/kxm/src/task-manager.ts +244 -0
  155. package/plugins/kxm/src/telemetry.ts +116 -0
  156. package/plugins/kxm/src/tui.ts +1046 -0
  157. package/plugins/kxm/src/vnext-bindings.ts +403 -0
  158. package/plugins/kxm/src/vnext-config.ts +1646 -0
  159. package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
  160. package/plugins/kxm/src/vnext-engine-command.ts +533 -0
  161. package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
  162. package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
  163. package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
  164. package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
  165. package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
  166. package/plugins/kxm/src/vnext-engine.ts +2458 -0
  167. package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
  168. package/plugins/kxm/src/vnext-harness.ts +1142 -0
  169. package/plugins/kxm/src/vnext-init.ts +430 -0
  170. package/plugins/kxm/src/vnext-migrate.ts +1848 -0
  171. package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
  172. package/plugins/kxm/src/vnext-permission.ts +936 -0
  173. package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
  174. package/plugins/kxm/src/vnext-repair.ts +1094 -0
  175. package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
  176. package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
  177. package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
  178. package/plugins/kxm/src/vnext-runtime.ts +663 -0
  179. package/plugins/kxm/src/vnext-template.ts +247 -0
  180. package/plugins/kxm/src/wiki.ts +313 -0
  181. package/plugins/kxm/src/workflow.ts +1548 -0
  182. package/schemas/vnext/README.md +46 -0
  183. package/schemas/vnext/agent.schema.json +40 -0
  184. package/schemas/vnext/assignment-result.schema.json +66 -0
  185. package/schemas/vnext/backup-manifest.schema.json +89 -0
  186. package/schemas/vnext/candidate.schema.json +109 -0
  187. package/schemas/vnext/common.schema.json +422 -0
  188. package/schemas/vnext/context-candidate.schema.json +76 -0
  189. package/schemas/vnext/context-packet.schema.json +192 -0
  190. package/schemas/vnext/delivery-manifest.schema.json +159 -0
  191. package/schemas/vnext/environment.schema.json +66 -0
  192. package/schemas/vnext/gate-registry.schema.json +109 -0
  193. package/schemas/vnext/handoff-manifest.schema.json +146 -0
  194. package/schemas/vnext/init-operation.schema.json +61 -0
  195. package/schemas/vnext/local-repository-bindings.schema.json +30 -0
  196. package/schemas/vnext/memory-record.schema.json +45 -0
  197. package/schemas/vnext/migration-decision.schema.json +26 -0
  198. package/schemas/vnext/migration-plan.schema.json +123 -0
  199. package/schemas/vnext/migration-receipt.schema.json +52 -0
  200. package/schemas/vnext/model.schema.json +42 -0
  201. package/schemas/vnext/permission-diff.schema.json +57 -0
  202. package/schemas/vnext/prices.schema.json +115 -0
  203. package/schemas/vnext/project.schema.json +85 -0
  204. package/schemas/vnext/repository.schema.json +24 -0
  205. package/schemas/vnext/run-event.schema.json +460 -0
  206. package/schemas/vnext/session-brief.schema.json +153 -0
  207. package/schemas/vnext/sync-event.schema.json +234 -0
  208. package/schemas/vnext/template-provenance.schema.json +38 -0
  209. package/schemas/vnext/workflow.schema.json +248 -0
  210. package/scripts/assignment-run.d.mts +354 -0
  211. package/scripts/assignment-run.mjs +4451 -0
  212. package/scripts/build-runtime.mjs +56 -0
  213. package/scripts/check-generated.mjs +77 -0
  214. package/scripts/check-versions.mjs +34 -0
  215. package/scripts/emit-codex-artifacts.d.mts +9 -0
  216. package/scripts/emit-codex-artifacts.mjs +91 -0
  217. package/scripts/harness-run.d.mts +83 -0
  218. package/scripts/harness-run.mjs +2095 -0
  219. package/scripts/kxm-hub.mjs +105 -0
  220. package/scripts/kxm-publish-npm.mjs +327 -0
  221. package/scripts/kxm-release-github.mjs +472 -0
  222. package/scripts/kxm-runtime-supervisor.mjs +7 -0
  223. package/scripts/kxm-worker.mjs +1127 -0
  224. package/scripts/kxm.mjs +27 -0
  225. package/scripts/roster-policy.d.mts +20 -0
  226. package/scripts/roster-policy.mjs +161 -0
  227. package/scripts/smoke-multi-pi.mjs +479 -0
@@ -0,0 +1,499 @@
1
+ import { ProtocolError, newId, requireString } from "./protocol.ts";
2
+ import { redactSecrets } from "./redact.ts";
3
+
4
+ /** Core KXM v0.5 context schemas. The context layer is additive to the v0.4
5
+ * mesh/workflow plane: nothing here changes hub or workflow behavior until a
6
+ * context surface explicitly consumes it. */
7
+
8
+ export const CONTEXT_ITEM_SCHEMA = "kxm.context-item.v1";
9
+ export const CONTEXT_REQUEST_SCHEMA = "kxm.context-request.v1";
10
+ export const CONTEXT_PACKET_SCHEMA = "kxm.context-packet.v1";
11
+
12
+ export const MAX_CONTEXT_SUMMARY_CHARS = 4_000;
13
+ export const MAX_CONTEXT_DETAILS_CHARS = 32_000;
14
+ export const MAX_CONTEXT_ID_REFS = 64;
15
+ export const MAX_CONTEXT_ITEMS = 256;
16
+ export const MIN_CONTEXT_BUDGET_TOKENS = 512;
17
+ export const MAX_CONTEXT_BUDGET_TOKENS = 200_000;
18
+ export const DEFAULT_CONTEXT_BUDGET_TOKENS = 32_000;
19
+ export const MAX_CONTEXT_ROLE_CHARS = 64;
20
+ export const MAX_CONTEXT_TASK_CHARS = 2_000;
21
+ /** Maximum derivation lineage depth. Chains longer than this fail closed:
22
+ * laundering provenance through unbounded re-summaries is a privilege
23
+ * escalation vector, not a legitimate workflow. */
24
+ export const MAX_CONTEXT_LINEAGE = 64;
25
+
26
+ /** Deterministic authority grant policy: the maximum authority each origin can
27
+ * ever bestow. Peer/tool/external/derived content is evidence at best — only
28
+ * humans and the deterministic workflow control plane can create `instruction`
29
+ * or `policy` authority, and tracked git history can carry instructions.
30
+ * This is the single source of truth for issue #36's "no escalation through
31
+ * reserialization" invariant. */
32
+ export const AUTHORITY_GRANT_FLOOR: Record<ContextSourceType, ContextAuthority> = {
33
+ human: "policy",
34
+ workflow: "policy",
35
+ git: "instruction",
36
+ peer: "evidence",
37
+ tool: "evidence",
38
+ external: "evidence",
39
+ derived: "evidence",
40
+ };
41
+
42
+ export function authorityGrantFloor(sourceType: ContextSourceType): ContextAuthority {
43
+ return AUTHORITY_GRANT_FLOOR[sourceType];
44
+ }
45
+
46
+ /** Control-plane field names that context items may never carry. Memory,
47
+ * wiki, and skill content may inform behavior but never expand tool
48
+ * permissions or approval scope; smuggling grants inside a context item is
49
+ * rejected at parse time. */
50
+ const RESERVED_CONTROL_PLANE_FIELDS = new Set([
51
+ "permissions",
52
+ "tools",
53
+ "allow",
54
+ "deny",
55
+ "grants",
56
+ "approval",
57
+ "policy",
58
+ "scopes",
59
+ "credentials",
60
+ "secrets",
61
+ "token",
62
+ "apiKey",
63
+ "password",
64
+ ]);
65
+
66
+ export type ContextItemKind = "evidence" | "state" | "episode" | "knowledge" | "skill";
67
+ export const CONTEXT_ITEM_KINDS: readonly ContextItemKind[] = ["evidence", "state", "episode", "knowledge", "skill"];
68
+
69
+ export type ContextSourceType =
70
+ | "human"
71
+ | "git"
72
+ | "workflow"
73
+ | "tool"
74
+ | "peer"
75
+ | "external"
76
+ | "derived";
77
+ export const CONTEXT_SOURCE_TYPES: readonly ContextSourceType[] = [
78
+ "human",
79
+ "git",
80
+ "workflow",
81
+ "tool",
82
+ "peer",
83
+ "external",
84
+ "derived",
85
+ ];
86
+
87
+ /** Authority classes are ordered: content can only ever carry the authority of
88
+ * its strongest *authorized* origin. Derived/summarized content must not
89
+ * increase authority (enforced by `derivedAuthority` below and hardened by
90
+ * issue #36 invariants). */
91
+ export type ContextAuthority = "policy" | "instruction" | "evidence" | "hypothesis";
92
+ export const CONTEXT_AUTHORITIES: readonly ContextAuthority[] = ["policy", "instruction", "evidence", "hypothesis"];
93
+
94
+ export type ContextConfidence = "verified" | "probable" | "uncertain";
95
+ export const CONTEXT_CONFIDENCES: readonly ContextConfidence[] = ["verified", "probable", "uncertain"];
96
+
97
+ export type ContextScope = "agent" | "project" | "run" | "operator";
98
+ export const CONTEXT_SCOPES: readonly ContextScope[] = ["agent", "project", "run", "operator"];
99
+
100
+ export type ContextItemStatus = "current" | "superseded" | "proposed" | "rejected";
101
+
102
+ /** Immutable content origin. Once written, provenance fields never change for
103
+ * the lifetime of an item ID; corrections mint a new item that supersedes it. */
104
+ export interface ContextProvenance {
105
+ sourceType: ContextSourceType;
106
+ sourceRef?: string;
107
+ derivedFrom?: string[];
108
+ }
109
+
110
+ export interface ContextItem {
111
+ id: string;
112
+ scope?: ContextScope;
113
+ kind: ContextItemKind;
114
+ project: string;
115
+ summary: string;
116
+ provenance: ContextProvenance;
117
+ authority: ContextAuthority;
118
+ confidence: ContextConfidence;
119
+ /** Key this item is authoritative for. Required for `state` items; the
120
+ * temporal state layer (state.ts) resolves one current value per key. */
121
+ stateKey?: string;
122
+ observedAt?: string;
123
+ validFrom?: string;
124
+ validUntil?: string;
125
+ status?: ContextItemStatus;
126
+ supersedes?: string[];
127
+ evidenceRefs?: string[];
128
+ }
129
+
130
+ export type ContextRole =
131
+ | "repro"
132
+ | "planner"
133
+ | "critic"
134
+ | "implementer"
135
+ | "verifier"
136
+ | (string & {});
137
+ export const CONTEXT_ROLES: readonly string[] = ["repro", "planner", "critic", "implementer", "verifier"];
138
+
139
+ export interface ContextRequest {
140
+ project: string;
141
+ role: ContextRole;
142
+ task: string;
143
+ workflowRunId?: string;
144
+ stageId?: string;
145
+ budgetTokens?: number;
146
+ includeKinds?: ContextItemKind[];
147
+ }
148
+
149
+ export interface ContextPacket {
150
+ workingState: Record<string, unknown>;
151
+ currentState: ContextItem[];
152
+ knowledge: ContextItem[];
153
+ episodes: ContextItem[];
154
+ skills: ContextItem[];
155
+ contradictions: ContextItem[];
156
+ unresolvedGaps: string[];
157
+ provenanceSummary: Record<string, number>;
158
+ estimatedTokens: number;
159
+ }
160
+
161
+ function oneOf<T extends string>(value: unknown, field: string, allowed: readonly T[]): T {
162
+ if (typeof value !== "string" || !allowed.includes(value as T)) {
163
+ throw new ProtocolError(400, `${field} must be one of ${allowed.join(", ")}`, "invalid_context_field");
164
+ }
165
+ return value as T;
166
+ }
167
+
168
+ function idRefs(value: unknown, field: string, required = false): string[] | undefined {
169
+ if (value === undefined || value === null) return required ? [] : undefined;
170
+ if (!Array.isArray(value)) {
171
+ throw new ProtocolError(400, `${field} must be an array of identifiers`, "invalid_context_field");
172
+ }
173
+ if (value.length > MAX_CONTEXT_ID_REFS) {
174
+ throw new ProtocolError(
175
+ 400,
176
+ `${field} exceeds ${MAX_CONTEXT_ID_REFS} references`,
177
+ "context_limits_exceeded",
178
+ );
179
+ }
180
+ const seen = new Set<string>();
181
+ const refs: string[] = [];
182
+ for (const candidate of value) {
183
+ if (typeof candidate !== "string" || !candidate.trim()) {
184
+ throw new ProtocolError(400, `${field} must contain non-empty identifiers`, "invalid_context_field");
185
+ }
186
+ const ref = candidate.trim();
187
+ if (seen.has(ref)) {
188
+ throw new ProtocolError(400, `${field} contains duplicate reference ${ref}`, "invalid_context_field");
189
+ }
190
+ seen.add(ref);
191
+ refs.push(ref);
192
+ }
193
+ return refs;
194
+ }
195
+
196
+ function optionalIsoTimestamp(value: unknown, field: string): string | undefined {
197
+ if (value === undefined || value === null) return undefined;
198
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
199
+ throw new ProtocolError(400, `${field} must be an ISO-8601 timestamp`, "invalid_context_field");
200
+ }
201
+ return value;
202
+ }
203
+
204
+ /** Parse and validate a context item from untrusted input. Fails closed on
205
+ * unknown kinds, oversized content, malformed provenance, incoherent lifecycle
206
+ * fields, authority above the origin's grant floor, and hostile payloads
207
+ * smuggling control-plane fields. */
208
+ export function parseContextItem(value: unknown): ContextItem {
209
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
210
+ throw new ProtocolError(400, "context item must be an object", "invalid_context_item");
211
+ }
212
+ const input = value as Record<string, unknown>;
213
+ for (const field of Object.keys(input)) {
214
+ if (RESERVED_CONTROL_PLANE_FIELDS.has(field)) {
215
+ throw new ProtocolError(
216
+ 403,
217
+ `context items may not carry control-plane field "${field}"`,
218
+ "context_authority_violation",
219
+ );
220
+ }
221
+ }
222
+ const item: ContextItem = {
223
+ id: requireString(input.id, "context item id", { max: 128 }),
224
+ kind: oneOf(input.kind, "context item kind", CONTEXT_ITEM_KINDS),
225
+ project: requireString(input.project, "context item project", { max: 200 }),
226
+ summary: redactSecrets(requireString(input.summary, "context item summary", { max: MAX_CONTEXT_SUMMARY_CHARS })),
227
+ provenance: parseContextProvenance(input.provenance),
228
+ authority: oneOf(input.authority, "context item authority", CONTEXT_AUTHORITIES),
229
+ confidence: oneOf(input.confidence, "context item confidence", CONTEXT_CONFIDENCES),
230
+ };
231
+ if (input.scope !== undefined && input.scope !== null) {
232
+ item.scope = oneOf(input.scope, "context item scope", CONTEXT_SCOPES);
233
+ }
234
+ const observedAt = optionalIsoTimestamp(input.observedAt, "context item observedAt");
235
+ const validFrom = optionalIsoTimestamp(input.validFrom, "context item validFrom");
236
+ const validUntil = optionalIsoTimestamp(input.validUntil, "context item validUntil");
237
+ if (observedAt !== undefined) item.observedAt = observedAt;
238
+ if (validFrom !== undefined) item.validFrom = validFrom;
239
+ if (validUntil !== undefined) item.validUntil = validUntil;
240
+ if (input.status !== undefined && input.status !== null) {
241
+ item.status = oneOf(input.status, "context item status", ["current", "superseded", "proposed", "rejected"]);
242
+ }
243
+ const supersedes = idRefs(input.supersedes, "context item supersedes");
244
+ if (supersedes !== undefined) {
245
+ if (supersedes.includes(item.id)) {
246
+ throw new ProtocolError(400, "context item cannot supersede itself", "invalid_context_item");
247
+ }
248
+ item.supersedes = supersedes;
249
+ }
250
+ const evidenceRefs = idRefs(input.evidenceRefs, "context item evidenceRefs");
251
+ if (evidenceRefs !== undefined) item.evidenceRefs = evidenceRefs;
252
+ const stateKey = input.stateKey === undefined || input.stateKey === null
253
+ ? undefined
254
+ : requireString(input.stateKey, "context item stateKey", { max: 200 });
255
+ if (stateKey !== undefined) item.stateKey = stateKey;
256
+ if (item.kind === "state" && item.stateKey === undefined) {
257
+ throw new ProtocolError(400, "state items require a stateKey", "invalid_context_item");
258
+ }
259
+ if (item.kind === "state" && item.status === undefined) {
260
+ throw new ProtocolError(400, "state items require an explicit lifecycle status", "invalid_context_item");
261
+ }
262
+ const grantFloor = authorityRank(authorityGrantFloor(item.provenance.sourceType));
263
+ if (authorityRank(item.authority) > grantFloor) {
264
+ throw new ProtocolError(
265
+ 403,
266
+ `content of origin ${item.provenance.sourceType} cannot claim ${item.authority} authority`,
267
+ "context_authority_violation",
268
+ );
269
+ }
270
+ return item;
271
+ }
272
+
273
+ export function parseContextProvenance(value: unknown): ContextProvenance {
274
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
275
+ throw new ProtocolError(400, "context provenance must be an object", "invalid_context_item");
276
+ }
277
+ const input = value as Record<string, unknown>;
278
+ const provenance: ContextProvenance = {
279
+ sourceType: oneOf(input.sourceType, "context provenance sourceType", CONTEXT_SOURCE_TYPES),
280
+ };
281
+ const sourceRef = input.sourceRef === undefined || input.sourceRef === null
282
+ ? undefined
283
+ : requireString(input.sourceRef, "context provenance sourceRef", { max: 512 });
284
+ if (sourceRef !== undefined) provenance.sourceRef = redactSecrets(sourceRef);
285
+ const derivedFrom = idRefs(input.derivedFrom, "context provenance derivedFrom");
286
+ if (derivedFrom !== undefined) provenance.derivedFrom = derivedFrom;
287
+ return provenance;
288
+ }
289
+
290
+ /** Parse and validate a context request. Every request is project-scoped; the
291
+ * arbiter (issue #34) never assembles packets across projects. */
292
+ export function parseContextRequest(value: unknown): ContextRequest {
293
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
294
+ throw new ProtocolError(400, "context request must be an object", "invalid_context_request");
295
+ }
296
+ const input = value as Record<string, unknown>;
297
+ const request: ContextRequest = {
298
+ project: requireString(input.project, "context request project", { max: 200 }),
299
+ role: requireString(input.role, "context request role", { max: MAX_CONTEXT_ROLE_CHARS }),
300
+ task: requireString(input.task, "context request task", { max: MAX_CONTEXT_TASK_CHARS }),
301
+ };
302
+ const workflowRunId = input.workflowRunId === undefined || input.workflowRunId === null
303
+ ? undefined
304
+ : requireString(input.workflowRunId, "context request workflowRunId", { max: 128 });
305
+ if (workflowRunId !== undefined) request.workflowRunId = workflowRunId;
306
+ const stageId = input.stageId === undefined || input.stageId === null
307
+ ? undefined
308
+ : requireString(input.stageId, "context request stageId", { max: 128 });
309
+ if (stageId !== undefined) request.stageId = stageId;
310
+ if (input.budgetTokens !== undefined && input.budgetTokens !== null) {
311
+ const budget = input.budgetTokens;
312
+ if (!Number.isInteger(budget) || (budget as number) < MIN_CONTEXT_BUDGET_TOKENS || (budget as number) > MAX_CONTEXT_BUDGET_TOKENS) {
313
+ throw new ProtocolError(
314
+ 400,
315
+ `context request budgetTokens must be an integer between ${MIN_CONTEXT_BUDGET_TOKENS} and ${MAX_CONTEXT_BUDGET_TOKENS}`,
316
+ "invalid_context_request",
317
+ );
318
+ }
319
+ request.budgetTokens = budget as number;
320
+ }
321
+ if (input.includeKinds !== undefined && input.includeKinds !== null) {
322
+ if (!Array.isArray(input.includeKinds) || input.includeKinds.length < 1 || input.includeKinds.length > CONTEXT_ITEM_KINDS.length) {
323
+ throw new ProtocolError(
324
+ 400,
325
+ "context request includeKinds must be a non-empty array of item kinds",
326
+ "invalid_context_request",
327
+ );
328
+ }
329
+ request.includeKinds = input.includeKinds.map((kind) => oneOf(kind, "context request includeKinds", CONTEXT_ITEM_KINDS));
330
+ }
331
+ return request;
332
+ }
333
+
334
+ /** Validate that a packet only contains items matching the request's project
335
+ * and requested kinds. Cross-project content fails closed. */
336
+ export function validateContextPacketContents(request: ContextRequest, packet: ContextPacket): void {
337
+ const items = [...packet.currentState, ...packet.knowledge, ...packet.episodes, ...packet.skills, ...packet.contradictions];
338
+ if (items.length > MAX_CONTEXT_ITEMS) {
339
+ throw new ProtocolError(400, `context packet exceeds ${MAX_CONTEXT_ITEMS} items`, "context_limits_exceeded");
340
+ }
341
+ const allowed = request.includeKinds ? new Set(request.includeKinds) : undefined;
342
+ for (const item of items) {
343
+ if (item.project !== request.project && item.project !== "_shared") {
344
+ throw new ProtocolError(
345
+ 400,
346
+ `context packet contains cross-project item ${item.id}`,
347
+ "context_isolation_violation",
348
+ );
349
+ }
350
+ if (allowed && !allowed.has(item.kind)) {
351
+ throw new ProtocolError(
352
+ 400,
353
+ `context packet contains item ${item.id} of unrequested kind ${item.kind}`,
354
+ "context_isolation_violation",
355
+ );
356
+ }
357
+ }
358
+ }
359
+
360
+ /** Deterministic, dependency-free token estimate. Roughly 4 characters per
361
+ * token; used for budget enforcement, never for billing. */
362
+ export function estimateContextTokens(items: ContextItem[]): number {
363
+ let characters = 0;
364
+ for (const item of items) {
365
+ characters += item.summary.length + item.id.length + item.kind.length;
366
+ if (item.provenance.sourceRef) characters += item.provenance.sourceRef.length;
367
+ }
368
+ return Math.ceil(characters / 4);
369
+ }
370
+
371
+ /** Authority rank used to enforce the "never increase through derivation"
372
+ * invariant. `policy` outranks everything; `hypothesis` outranks nothing. */
373
+ export function authorityRank(authority: ContextAuthority): number {
374
+ switch (authority) {
375
+ case "policy": return 3;
376
+ case "instruction": return 2;
377
+ case "evidence": return 1;
378
+ case "hypothesis": return 0;
379
+ }
380
+ }
381
+
382
+ /** Authority floor for derived content: a derived item may never carry more
383
+ * authority than the strongest item it derives from. */
384
+ export function derivedAuthority(claimed: ContextAuthority, lineage: ContextItem[]): ContextAuthority {
385
+ let floor = authorityRank("hypothesis");
386
+ for (const ancestor of lineage) {
387
+ floor = Math.max(floor, authorityRank(ancestor.authority));
388
+ }
389
+ const claimedRank = authorityRank(claimed);
390
+ if (claimedRank > floor) return "evidence";
391
+ return claimed;
392
+ }
393
+
394
+ /** Mint a new derived context item with provenance that cannot exceed its
395
+ * lineage authority, an authority that never exceeds the `evidence` grant
396
+ * floor of derived origins, and a lineage depth bounded by
397
+ * MAX_CONTEXT_LINEAGE. Iterated summarization is the classic privilege
398
+ * laundering vector: each hop must re-earn nothing and bound the chain. */
399
+ export function deriveContextItem(
400
+ base: Pick<ContextItem, "project" | "kind" | "summary">,
401
+ claimed: ContextAuthority,
402
+ lineage: ContextItem[],
403
+ sourceRef?: string,
404
+ ): ContextItem {
405
+ const derivedFrom = new Set<string>();
406
+ for (const ancestor of lineage) {
407
+ derivedFrom.add(ancestor.id);
408
+ for (const ancestorOfAncestor of ancestor.provenance.derivedFrom ?? []) {
409
+ derivedFrom.add(ancestorOfAncestor);
410
+ }
411
+ }
412
+ if (derivedFrom.size > MAX_CONTEXT_LINEAGE) {
413
+ throw new ProtocolError(
414
+ 400,
415
+ `derivation lineage exceeds ${MAX_CONTEXT_LINEAGE} ancestors`,
416
+ "context_limits_exceeded",
417
+ );
418
+ }
419
+ // Derived origins grant at most evidence authority, no matter what the
420
+ // lineage or the caller claims.
421
+ const effectiveClaim: ContextAuthority = authorityRank(claimed) > authorityRank("evidence")
422
+ ? "evidence"
423
+ : claimed;
424
+ return {
425
+ id: newId("ctx"),
426
+ kind: base.kind,
427
+ project: base.project,
428
+ summary: base.summary,
429
+ provenance: {
430
+ sourceType: "derived",
431
+ ...(sourceRef !== undefined ? { sourceRef } : {}),
432
+ derivedFrom: [...derivedFrom].sort(),
433
+ },
434
+ authority: derivedAuthority(effectiveClaim, lineage),
435
+ confidence: "probable",
436
+ };
437
+ }
438
+
439
+ /** Summarize many items into one derived knowledge item. The summary's
440
+ * authority is at most the strongest lineage authority and at most
441
+ * `evidence` for derived origins; provenance is preserved for every source;
442
+ * superseded/rejected lineage is excluded because dead records are not
443
+ * evidence of current truth. */
444
+ export function summarizeContextItems(
445
+ project: string,
446
+ summary: string,
447
+ lineage: ContextItem[],
448
+ sourceRef?: string,
449
+ ): ContextItem {
450
+ const live = lineage.filter((item) => item.status !== "superseded" && item.status !== "rejected");
451
+ return deriveContextItem(
452
+ { project, kind: "knowledge", summary },
453
+ "evidence",
454
+ live,
455
+ sourceRef,
456
+ );
457
+ }
458
+
459
+ /** Bounded, secret-free metadata describing an item for telemetry and audits.
460
+ * Never includes summaries or raw content bodies. */
461
+ export interface ContextItemAuditMetadata {
462
+ id: string;
463
+ kind: ContextItemKind;
464
+ authority: ContextAuthority;
465
+ confidence: ContextConfidence;
466
+ status?: ContextItemStatus;
467
+ sourceType: ContextSourceType;
468
+ sourceRef?: string;
469
+ derived: boolean;
470
+ lineageDepth: number;
471
+ }
472
+
473
+ export function contextItemAuditMetadata(item: ContextItem): ContextItemAuditMetadata {
474
+ const metadata: ContextItemAuditMetadata = {
475
+ id: item.id,
476
+ kind: item.kind,
477
+ authority: item.authority,
478
+ confidence: item.confidence,
479
+ sourceType: item.provenance.sourceType,
480
+ derived: item.provenance.sourceType === "derived" || (item.provenance.derivedFrom?.length ?? 0) > 0,
481
+ lineageDepth: item.provenance.derivedFrom?.length ?? 0,
482
+ };
483
+ if (item.status !== undefined) metadata.status = item.status;
484
+ if (item.provenance.sourceRef !== undefined) metadata.sourceRef = item.provenance.sourceRef;
485
+ return metadata;
486
+ }
487
+
488
+ /** Build the provenanceSummary bucket for a packet: counts by source type. */
489
+ export function provenanceSummaryOf(items: ContextItem[]): Record<string, number> {
490
+ const summary: Record<string, number> = {};
491
+ for (const item of items) {
492
+ summary[item.provenance.sourceType] = (summary[item.provenance.sourceType] ?? 0) + 1;
493
+ }
494
+ for (const key of Object.keys(summary).sort()) {
495
+ // no-op: ensures deterministic key ordering for stable serialization
496
+ if (summary[key] === 0) delete summary[key];
497
+ }
498
+ return summary;
499
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./protocol.ts";
2
+ export * from "./envelope.ts";
3
+ export * from "./routing.ts";
4
+ export * from "./redact.ts";
5
+ export * from "./commands.ts";
6
+ export * from "./logger.ts";