@simplexable/kc2-client 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@simplexable/kc2-client",
3
+ "version": "0.3.0",
4
+ "description": "Host-neutral KC2 contracts for platform integrations.",
5
+ "main": "./src/index.js",
6
+ "type": "commonjs",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./fingerprints/stableHash": "./src/fingerprints/stableHash.js",
10
+ "./platform/kc2PlatformIdentity": "./src/platform/kc2PlatformIdentity.js",
11
+ "./platform/kc2PlatformAdapterErrors": "./src/platform/kc2PlatformAdapterErrors.js",
12
+ "./platform/kc2PlatformAdapterContract": "./src/platform/kc2PlatformAdapterContract.js",
13
+ "./platform/kc2ObserveContract": "./src/platform/kc2ObserveContract.js",
14
+ "./platform/kc2SignalContract": "./src/platform/kc2SignalContract.js",
15
+ "./platform/kc2MaterializeContract": "./src/platform/kc2MaterializeContract.js",
16
+ "./ia/articleTopologyContracts": "./src/ia/articleTopologyContracts.js",
17
+ "./ia/authorabilityContracts": "./src/ia/authorabilityContracts.js",
18
+ "./generation/generationDecisionContract": "./src/generation/generationDecisionContract.js",
19
+ "./relationships/articleRelationshipContracts": "./src/relationships/articleRelationshipContracts.js",
20
+ "./reconciliation/kc2ReconciliationStageContracts": "./src/reconciliation/kc2ReconciliationStageContracts.js",
21
+ "./reconciliation/kc2ReconciliationContract": "./src/reconciliation/kc2ReconciliationContract.js",
22
+ "./reconciliation/stageArtifactEnvelope": "./src/reconciliation/stageArtifactEnvelope.js",
23
+ "./authority/authorityContracts": "./src/authority/authorityContracts.js",
24
+ "./authority/humanGuidanceContracts": "./src/authority/humanGuidanceContracts.js",
25
+ "./authority/authorityDispositionContracts": "./src/authority/authorityDispositionContracts.js",
26
+ "./persistence/kc2WireOperationContracts": "./src/persistence/kc2WireOperationContracts.js",
27
+ "./persistence/kc2GenerationSnapshotPersistenceContract": "./src/persistence/kc2GenerationSnapshotPersistenceContract.js",
28
+ "./persistence/kc2HumanAuthorityReadContract": "./src/persistence/kc2HumanAuthorityReadContract.js",
29
+ "./persistence/kc2HumanAuthorityWriteContract": "./src/persistence/kc2HumanAuthorityWriteContract.js",
30
+ "./errors/Kc2ClientError": "./src/errors/Kc2ClientError.js",
31
+ "./transport/requestJson": "./src/transport/requestJson.js",
32
+ "./clients/helpClient": "./src/clients/helpClient.js"
33
+ },
34
+ "files": [
35
+ "src"
36
+ ],
37
+ "publishConfig": {
38
+ "registry": "https://registry.npmjs.org/",
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "test": "node --test test/*.test.js"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "license": "UNLICENSED"
48
+ }
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+ const AUTHORITY_CLASSIFICATION = Object.freeze({ AUTHORITATIVE_DOMAIN_KNOWLEDGE: 'authoritative-domain-knowledge', EXPERIENTIAL_OPERATOR_KNOWLEDGE: 'experiential-operator-knowledge', SOFTWARE_BEHAVIOR_CORRECTION: 'software-behavior-correction' });
3
+ const AUTHORITY_DISPOSITION = Object.freeze(['incorporated', 'preserved-unchanged', 'not-applicable', 'superseded-by-newer-human-authority', 'conflict-with-software-evidence', 'conflict-with-another-human-authority', 'requires-human-review']);
4
+ const AUTHORITY_OVERALL_OUTCOME = Object.freeze(['complete', 'provisional', 'blocked']);
5
+ const authorityReferenceSchema = Object.freeze({ type: 'object', additionalProperties: false, required: ['kind', 'ref'], properties: { kind: { type: 'string', minLength: 1 }, ref: { type: 'string', minLength: 1 }, classification: { type: 'string', enum: Object.values(AUTHORITY_CLASSIFICATION) } } });
6
+ function validateAuthorityReference(reference = {}) { const violations = []; const kind = String(reference.kind || '').trim(); const ref = String(reference.ref || '').trim(); const classification = String(reference.classification || '').trim() || null; if (!kind) violations.push('authority:missing-kind'); if (!ref) violations.push('authority:missing-ref'); if (classification && !Object.values(AUTHORITY_CLASSIFICATION).includes(classification)) violations.push('authority:invalid-classification'); return { classification, kind: kind || null, ref: ref || null, valid: violations.length === 0, violations }; }
7
+ module.exports = { AUTHORITY_CLASSIFICATION, AUTHORITY_DISPOSITION, AUTHORITY_OVERALL_OUTCOME, authorityReferenceSchema, validateAuthorityReference };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ // Public disposition vocabulary and field descriptors. Conflict analysis and
4
+ // authority application remain KC2 server implementation.
5
+ const AUTHORITY_DISPOSITION_CONTRACT_VERSION = 'kc2-human-authority-disposition.v1';
6
+ const AUTHORITY_CONFLICT_KIND = Object.freeze({
7
+ DUPLICATE_DOMAIN_ASSERTION: 'duplicate-domain-assertion-text',
8
+ PROTECTED_CONTENT: 'protected-content-conflict',
9
+ REQUIRED_COVERAGE_EXCLUSION: 'required-coverage-exclusion-target-conflict',
10
+ SOFTWARE_EVIDENCE_VS_PROTECTED_OVERRIDE: 'software-evidence-vs-protected-override',
11
+ });
12
+ const AUTHORITY_PROTECTED_CONFLICT_KINDS = Object.freeze([
13
+ AUTHORITY_CONFLICT_KIND.SOFTWARE_EVIDENCE_VS_PROTECTED_OVERRIDE,
14
+ AUTHORITY_CONFLICT_KIND.PROTECTED_CONTENT,
15
+ ]);
16
+ const AUTHORITY_DISPOSITION_FORBIDDEN_MUTATION_FIELDS = Object.freeze([
17
+ 'mutated',
18
+ 'retired',
19
+ 'released',
20
+ 'superseded',
21
+ 'updatedRecord',
22
+ ]);
23
+ const AUTHORITY_DISPOSITION_SECTIONS = Object.freeze([
24
+ Object.freeze({ dispositionField: 'disposition', idField: 'assertionId', invalidDispositions: Object.freeze(['preserved-unchanged']), key: 'domainAssertions', sourceField: 'domainAssertions' }),
25
+ Object.freeze({ dispositionField: 'disposition', idField: 'guidanceId', invalidDispositions: Object.freeze(['preserved-unchanged']), key: 'authoringGuidance', sourceField: 'authoringGuidance' }),
26
+ Object.freeze({ dispositionField: 'disposition', idField: 'addendumId', invalidDispositions: Object.freeze(['incorporated']), key: 'addenda', sourceField: 'addenda' }),
27
+ Object.freeze({ dispositionField: 'disposition', idField: 'protectionId', invalidDispositions: Object.freeze(['incorporated']), key: 'protectedOverrides', sourceField: 'protectedOverrides' }),
28
+ ]);
29
+
30
+ module.exports = {
31
+ AUTHORITY_CONFLICT_KIND,
32
+ AUTHORITY_DISPOSITION_CONTRACT_VERSION,
33
+ AUTHORITY_DISPOSITION_FORBIDDEN_MUTATION_FIELDS,
34
+ AUTHORITY_DISPOSITION_SECTIONS,
35
+ AUTHORITY_PROTECTED_CONFLICT_KINDS,
36
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+ const HUMAN_GUIDANCE_STATUSES = Object.freeze(['active', 'superseded', 'retired']);
3
+ const HUMAN_GUIDANCE_TYPES = Object.freeze(['required-coverage', 'emphasis', 'terminology', 'exclusion', 'sequencing', 'caution']);
4
+ const HUMAN_GUIDANCE_MUTUALLY_EXCLUSIVE_TYPE_PAIRS = Object.freeze([Object.freeze(['required-coverage', 'exclusion'])]);
5
+ function textOrEmpty(value) { return String(value || '').trim(); }
6
+ function validateHumanGuidanceType(value) { const guidanceType = textOrEmpty(value); const valid = HUMAN_GUIDANCE_TYPES.includes(guidanceType); return { guidanceType: guidanceType || null, valid, violations: valid ? [] : [`human-guidance:invalid-type:${guidanceType || 'missing'}`] }; }
7
+ function validateHumanGuidanceStatus(value) { const status = textOrEmpty(value); const valid = HUMAN_GUIDANCE_STATUSES.includes(status); return { status: status || null, valid, violations: valid ? [] : [`human-guidance:invalid-status:${status || 'missing'}`] }; }
8
+ function areHumanGuidanceTypesMutuallyExclusive(left, right) { const a = textOrEmpty(left); const b = textOrEmpty(right); return HUMAN_GUIDANCE_MUTUALLY_EXCLUSIVE_TYPE_PAIRS.some(([first, second]) => (a === first && b === second) || (a === second && b === first)); }
9
+ module.exports = { HUMAN_GUIDANCE_MUTUALLY_EXCLUSIVE_TYPE_PAIRS, HUMAN_GUIDANCE_STATUSES, HUMAN_GUIDANCE_TYPES, areHumanGuidanceTypesMutuallyExclusive, validateHumanGuidanceStatus, validateHumanGuidanceType };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const Kc2ClientError = require('../errors/Kc2ClientError');
4
+ const { requestJson } = require('../transport/requestJson');
5
+
6
+ async function resolveKc2Help({
7
+ baseUrl,
8
+ input = {},
9
+ masterKey,
10
+ platformId,
11
+ request = requestJson,
12
+ timeoutMs,
13
+ } = {}) {
14
+ const normalizedBaseUrl = String(baseUrl || '').trim().replace(/\/+$/, '');
15
+ if (!normalizedBaseUrl) {
16
+ throw new Kc2ClientError('KC2 help location is missing', {
17
+ code: 'kc2-client-invalid-url',
18
+ statusCode: 503,
19
+ });
20
+ }
21
+ if (platformId == null || String(platformId).trim() === '') {
22
+ throw new Kc2ClientError('KC2 help platform identity is missing', {
23
+ code: 'kc2-client-invalid-request',
24
+ statusCode: 400,
25
+ });
26
+ }
27
+ let response;
28
+ try {
29
+ response = await request({
30
+ body: { input, masterKey },
31
+ method: 'POST',
32
+ timeoutMs,
33
+ url: `${normalizedBaseUrl}/api/platforms/${encodeURIComponent(platformId)}/help/resolve`,
34
+ });
35
+ } catch (cause) {
36
+ if (cause instanceof Kc2ClientError) throw cause;
37
+ throw new Kc2ClientError('KC2 help request failed', { cause });
38
+ }
39
+ if (!response || response.statusCode >= 400 || response.body?.success === false) {
40
+ throw new Kc2ClientError(response?.body?.failure || 'KC2 help request failed', {
41
+ code: response?.body?.code || 'kc2-client-response-failed',
42
+ statusCode: response?.statusCode || 503,
43
+ });
44
+ }
45
+ return response.body?.result === undefined ? null : response.body.result;
46
+ }
47
+
48
+ module.exports = {
49
+ resolveKc2Help,
50
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ class Kc2ClientError extends Error {
4
+ constructor(message, { code = 'kc2-client-request-failed', statusCode = 503, cause } = {}) {
5
+ super(message, cause === undefined ? undefined : { cause });
6
+ this.name = 'Kc2ClientError';
7
+ this.code = code;
8
+ this.statusCode = statusCode;
9
+ if (cause !== undefined) this.cause = cause;
10
+ }
11
+ }
12
+
13
+ module.exports = Kc2ClientError;
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ function stableStringify(value) {
6
+ if (value === null || value === undefined) return 'null';
7
+ if (typeof value !== 'object') return JSON.stringify(value);
8
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
9
+ return `{${Object.keys(value).sort().map((key) => (
10
+ `${JSON.stringify(key)}:${stableStringify(value[key])}`
11
+ )).join(',')}}`;
12
+ }
13
+
14
+ function hashContent(value) {
15
+ return crypto.createHash('sha256')
16
+ .update(typeof value === 'string' ? value : stableStringify(value))
17
+ .digest('hex');
18
+ }
19
+
20
+ module.exports = { hashContent, stableStringify };
@@ -0,0 +1,208 @@
1
+ 'use strict';
2
+
3
+ const CONTRACT_VERSION = 'generation-decision.v1';
4
+ const OPERATION = 'generation-decision';
5
+ const MAX_STRING_LENGTH = 512;
6
+ const MAX_REASONS = 32;
7
+
8
+ const ATTEMPT_STATUSES = Object.freeze([
9
+ 'pending', 'running', 'resumable', 'completed', 'failed', 'stale', 'abandoned',
10
+ ]);
11
+ const OPERATOR_INTENTS = Object.freeze([
12
+ 'reuse', 'generate', 'inspect',
13
+ ]);
14
+ const DECISIONS = Object.freeze(['reuse', 'generate', 'blocked', 'not-ready']);
15
+ const READINESS_STATES = Object.freeze([
16
+ 'ready', 'needs-authority', 'needs-inputs', 'stale', 'invalid',
17
+ ]);
18
+ const REUSE_DISPOSITIONS = Object.freeze([
19
+ 'allowed', 'not-allowed', 'not-applicable',
20
+ ]);
21
+ const GENERATION_ENTRIES = Object.freeze([
22
+ 'reuse-existing', 'start-generation', 'resolve-blockers', 'none',
23
+ ]);
24
+ const ERROR_CATEGORIES = Object.freeze([
25
+ 'invalid-payload', 'unauthorized', 'unavailable', 'unsupported-operation',
26
+ 'semantic-failure',
27
+ ]);
28
+ const ERROR_CODES = Object.freeze([
29
+ 'invalid-payload', 'unauthorized', 'provider-unavailable',
30
+ 'unsupported-operation', 'semantic-failure',
31
+ ]);
32
+ const AUTHORITY_REASON_CODES = Object.freeze([
33
+ 'authority-required', 'authority-unresolved', 'authority-conflict',
34
+ ]);
35
+ const RESULT_REASON_CODES = Object.freeze([
36
+ 'article-invalid', 'article-missing', 'attempt-missing', 'attempt-stale',
37
+ 'artifact-current', 'artifact-stale', 'authority-required',
38
+ 'authority-unresolved', 'authority-conflict', 'inputs-current',
39
+ 'inputs-missing', 'inputs-stale', 'not-authorable', 'ready',
40
+ ]);
41
+
42
+ function contractError(message) {
43
+ const error = new TypeError(message);
44
+ error.code = 'invalid-payload';
45
+ return error;
46
+ }
47
+
48
+ function assertPlainObject(value, label) {
49
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
50
+ throw contractError(`${label} must be an object`);
51
+ }
52
+ }
53
+
54
+ function assertNoUnknownKeys(value, allowed, label) {
55
+ for (const key of Object.keys(value)) {
56
+ if (!allowed.includes(key)) throw contractError(`${label}.${key} is not supported`);
57
+ }
58
+ }
59
+
60
+ function assertString(value, label, { nullable = false } = {}) {
61
+ if (nullable && value === null) return;
62
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_STRING_LENGTH) {
63
+ throw contractError(`${label} must be a bounded non-empty string${nullable ? ' or null' : ''}`);
64
+ }
65
+ }
66
+
67
+ function assertNullableString(value, label) {
68
+ assertString(value, label, { nullable: true });
69
+ }
70
+
71
+ function assertNullableBoolean(value, label) {
72
+ if (value !== null && typeof value !== 'boolean') throw contractError(`${label} must be boolean or null`);
73
+ }
74
+
75
+ function assertEnum(value, values, label, { nullable = false } = {}) {
76
+ if (nullable && value === null) return;
77
+ if (typeof value !== 'string' || !values.includes(value)) {
78
+ throw contractError(`${label} is not a supported value`);
79
+ }
80
+ }
81
+
82
+ function assertNullableObject(value, label) {
83
+ if (value !== null) assertPlainObject(value, label);
84
+ }
85
+
86
+ function validateGenerationDecisionRequest(input) {
87
+ assertPlainObject(input, 'request');
88
+ assertNoUnknownKeys(input, [
89
+ 'contractVersion', 'platformId', 'article', 'attempt', 'currentness',
90
+ 'authority', 'operatorIntent', 'requestId',
91
+ ], 'request');
92
+ if (input.contractVersion !== CONTRACT_VERSION) throw contractError('request.contractVersion is unsupported');
93
+ assertString(input.platformId, 'request.platformId');
94
+ assertPlainObject(input.article, 'request.article');
95
+ assertNoUnknownKeys(input.article, ['canonicalKey', 'title'], 'request.article');
96
+ assertString(input.article.canonicalKey, 'request.article.canonicalKey');
97
+ if (Object.hasOwn(input.article, 'title')) assertNullableString(input.article.title, 'request.article.title');
98
+
99
+ if (Object.hasOwn(input, 'attempt')) assertNullableObject(input.attempt, 'request.attempt');
100
+ if (input.attempt && input.attempt !== null) {
101
+ assertNoUnknownKeys(input.attempt, ['attemptKey', 'inputFingerprint', 'status'], 'request.attempt');
102
+ if (Object.hasOwn(input.attempt, 'attemptKey')) assertNullableString(input.attempt.attemptKey, 'request.attempt.attemptKey');
103
+ if (Object.hasOwn(input.attempt, 'inputFingerprint')) assertNullableString(input.attempt.inputFingerprint, 'request.attempt.inputFingerprint');
104
+ if (Object.hasOwn(input.attempt, 'status')) assertEnum(input.attempt.status, ATTEMPT_STATUSES, 'request.attempt.status', { nullable: true });
105
+ }
106
+ if (Object.hasOwn(input, 'currentness')) assertNullableObject(input.currentness, 'request.currentness');
107
+ if (input.currentness && input.currentness !== null) {
108
+ assertNoUnknownKeys(input.currentness, ['inputCurrent', 'artifactCurrent'], 'request.currentness');
109
+ for (const key of ['inputCurrent', 'artifactCurrent']) {
110
+ if (Object.hasOwn(input.currentness, key)) assertNullableBoolean(input.currentness[key], `request.currentness.${key}`);
111
+ }
112
+ }
113
+ if (Object.hasOwn(input, 'authority')) assertNullableObject(input.authority, 'request.authority');
114
+ if (input.authority && input.authority !== null) {
115
+ assertNoUnknownKeys(input.authority, ['required', 'resolved', 'blockingReasons'], 'request.authority');
116
+ if (Object.hasOwn(input.authority, 'required')) assertNullableBoolean(input.authority.required, 'request.authority.required');
117
+ if (Object.hasOwn(input.authority, 'resolved')) assertNullableBoolean(input.authority.resolved, 'request.authority.resolved');
118
+ if (Object.hasOwn(input.authority, 'blockingReasons') && (!Array.isArray(input.authority.blockingReasons)
119
+ || input.authority.blockingReasons.length > MAX_REASONS)) {
120
+ throw contractError('request.authority.blockingReasons must be a bounded array');
121
+ }
122
+ (input.authority.blockingReasons || []).forEach((reason, index) => {
123
+ assertEnum(reason, AUTHORITY_REASON_CODES, `request.authority.blockingReasons[${index}]`);
124
+ });
125
+ }
126
+ if (Object.hasOwn(input, 'operatorIntent')) assertEnum(input.operatorIntent, OPERATOR_INTENTS, 'request.operatorIntent', { nullable: true });
127
+ if (Object.hasOwn(input, 'requestId')) assertNullableString(input.requestId, 'request.requestId');
128
+ return input;
129
+ }
130
+
131
+ function validateGenerationDecisionResponse(input) {
132
+ assertPlainObject(input, 'response');
133
+ assertNoUnknownKeys(input, [
134
+ 'contractVersion', 'success', 'operation', 'provider', 'result', 'error',
135
+ ], 'response');
136
+ if (input.contractVersion !== CONTRACT_VERSION) throw contractError('response.contractVersion is unsupported');
137
+ if (typeof input.success !== 'boolean') throw contractError('response.success must be boolean');
138
+ if (input.operation !== OPERATION) throw contractError('response.operation is unsupported');
139
+ assertPlainObject(input.provider, 'response.provider');
140
+ assertNoUnknownKeys(input.provider, ['providerId', 'providerVersion'], 'response.provider');
141
+ assertString(input.provider.providerId, 'response.provider.providerId');
142
+ assertString(input.provider.providerVersion, 'response.provider.providerVersion');
143
+ if (input.success) {
144
+ if (input.error !== null) throw contractError('response.error must be null on success');
145
+ assertPlainObject(input.result, 'response.result');
146
+ assertNoUnknownKeys(input.result, [
147
+ 'decision', 'readiness', 'reuse', 'generationEntry', 'reasons',
148
+ 'inputFingerprint', 'evaluatedAt',
149
+ ], 'response.result');
150
+ for (const key of ['decision', 'readiness', 'reuse', 'generationEntry', 'reasons']) {
151
+ if (!Object.hasOwn(input.result, key)) throw contractError(`response.result.${key} is required`);
152
+ }
153
+ assertEnum(input.result.decision, DECISIONS, 'response.result.decision');
154
+ assertEnum(input.result.readiness, READINESS_STATES, 'response.result.readiness');
155
+ assertEnum(input.result.reuse, REUSE_DISPOSITIONS, 'response.result.reuse');
156
+ assertEnum(input.result.generationEntry, GENERATION_ENTRIES, 'response.result.generationEntry');
157
+ if (!Array.isArray(input.result.reasons) || input.result.reasons.length > MAX_REASONS) {
158
+ throw contractError('response.result.reasons must be a bounded array');
159
+ }
160
+ input.result.reasons.forEach((reason, index) => {
161
+ assertEnum(reason, RESULT_REASON_CODES, `response.result.reasons[${index}]`);
162
+ });
163
+ assertNullableString(input.result.inputFingerprint, 'response.result.inputFingerprint');
164
+ assertNullableString(input.result.evaluatedAt, 'response.result.evaluatedAt');
165
+ } else {
166
+ if (input.result !== null) throw contractError('response.result must be null on failure');
167
+ assertPlainObject(input.error, 'response.error');
168
+ assertNoUnknownKeys(input.error, ['code', 'category', 'message'], 'response.error');
169
+ for (const key of ['code', 'category', 'message']) {
170
+ if (!Object.hasOwn(input.error, key)) throw contractError(`response.error.${key} is required`);
171
+ }
172
+ assertEnum(input.error.code, ERROR_CODES, 'response.error.code');
173
+ assertEnum(input.error.category, ERROR_CATEGORIES, 'response.error.category');
174
+ assertString(input.error.message, 'response.error.message');
175
+ }
176
+ return input;
177
+ }
178
+
179
+ function normalizeGenerationDecisionRequest(input) {
180
+ validateGenerationDecisionRequest(input);
181
+ return JSON.parse(JSON.stringify(input));
182
+ }
183
+
184
+ function normalizeGenerationDecisionResponse(input) {
185
+ validateGenerationDecisionResponse(input);
186
+ return JSON.parse(JSON.stringify(input));
187
+ }
188
+
189
+ module.exports = {
190
+ ATTEMPT_STATUSES,
191
+ AUTHORITY_REASON_CODES,
192
+ CONTRACT_VERSION,
193
+ DECISIONS,
194
+ ERROR_CATEGORIES,
195
+ ERROR_CODES,
196
+ GENERATION_ENTRIES,
197
+ OPERATION,
198
+ OPERATOR_INTENTS,
199
+ READINESS_STATES,
200
+ RESULT_REASON_CODES,
201
+ REUSE_DISPOSITIONS,
202
+ MAX_REASONS,
203
+ MAX_STRING_LENGTH,
204
+ normalizeGenerationDecisionRequest,
205
+ normalizeGenerationDecisionResponse,
206
+ validateGenerationDecisionRequest,
207
+ validateGenerationDecisionResponse,
208
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+ const KC2_ARTICLE_MAPPING_AUTHORITY = Object.freeze({ ACCEPTED_KC2_STATE: 'accepted-kc2-state', COMPONENT_HUB: 'shared-primary-component-hub', DEFAULT_ONE_TO_ONE: 'default-one-to-one', KC2_MANUAL_REGISTRATION: 'kc2-manual-registration', OPERATOR_CONFIRM_ARTICLE_GROUP: 'operator-confirm-article-group', OPERATOR_CONVERGE: 'operator-converge-into', OPERATOR_KEEP_INDEPENDENT: 'operator-keep-independent', OPERATOR_NOT_AN_ARTICLE: 'operator-not-an-article', UNRESOLVED: 'unresolved-mapping' });
3
+ const KC2_ARTICLE_MAPPING_CONFIDENCE = Object.freeze({ DETERMINISTIC: 'deterministic', OPERATOR: 'operator', RECOMMENDATION: 'recommendation', UNRESOLVED: 'unresolved' });
4
+ const KC2_ARTICLE_GROUP_ISSUES = Object.freeze({ COMPETING_MAPPINGS: 'competing-primary-mappings', IDENTITY_UNRESOLVED: 'identity-unresolved', MAPPING_UNRESOLVED: 'mapping-unresolved', NOT_AN_ARTICLE: 'not-an-article', PENDING_IA_DECISION: 'pending-ia-decision', PLACEMENT_UNRESOLVED: 'placement-unresolved', STRUCTURAL_BLOCKER: 'structural-blocker' });
5
+ function isOperatorIndependentMappingAuthority(value) { return [KC2_ARTICLE_MAPPING_AUTHORITY.OPERATOR_KEEP_INDEPENDENT, KC2_ARTICLE_MAPPING_AUTHORITY.OPERATOR_CONFIRM_ARTICLE_GROUP].includes(value); }
6
+ function isOperatorIndependentDecisionType(value) { return ['keep-independent', 'confirm-article-group'].includes(value); }
7
+ module.exports = { KC2_ARTICLE_GROUP_ISSUES, KC2_ARTICLE_MAPPING_AUTHORITY, KC2_ARTICLE_MAPPING_CONFIDENCE, isOperatorIndependentDecisionType, isOperatorIndependentMappingAuthority };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+ const KC2_ARTICLE_ELIGIBILITY_KINDS = Object.freeze({ AMBIGUOUS_REVIEW: 'ambiguous-review', ARTICLE_ELIGIBLE: 'article-eligible', EXISTING_CANONICAL_CONVERGENCE: 'existing-canonical-convergence', EXISTING_KC2_ARTICLE: 'existing-kc2-article', NON_ARTICLE_SOFTWARE_OBJECT: 'non-article-software-object', PRODUCT_SURFACE: 'product-surface', SUBORDINATE_TOPIC: 'subordinate-topic' });
3
+ const KC2_IA_DECISION_TYPES = Object.freeze({ BOUNDARY_VALID_PLACEMENT_REVIEW: 'boundary-valid-placement-review', CONFIRM_ARTICLE_GROUP: 'confirm-article-group', CONVERGE_INTO: 'converge-into', DEFER: 'defer', KEEP_INDEPENDENT: 'keep-independent', MERGE_CANDIDATE: 'merge-candidate', NOT_AN_ARTICLE: 'not-an-article' });
4
+ const KC2_PENDING_IA_DECISION_TYPES = Object.freeze(['boundary-valid-placement-review', 'defer', 'merge-candidate']);
5
+ const KC2_NATIVE_TARGET_REASONS = Object.freeze({ ACCEPTED_KC2_STATE: 'accepted-kc2-state', CURRENT_PRODUCT_SURFACE: 'current-product-surface', HUMAN_IA_DECISION: 'human-ia-decision', KC2_REGISTRATION: 'kc2-registration' });
6
+ const KC2_REPOSITORY_HELP_STATES = Object.freeze({ AUTOMATICALLY_MAINTAINABLE: 'automatically-maintainable', CURRENT: 'current', NEEDS_HUMAN_REVIEW: 'needs-human-review', NEEDS_NEW_KC2_HELP: 'needs-new-kc2-help', NEEDS_UPDATED_KC2_HELP: 'needs-updated-kc2-help' });
7
+ const KC2_REPOSITORY_HELP_REASON_CODES = Object.freeze({ AMBIGUOUS_KC2_PRESENCE: 'ambiguous-kc2-presence', CURRENT: 'kc2-current', DETERMINISTIC_MAINTENANCE_APPLIED: 'deterministic-maintenance-applied', DETERMINISTIC_STALE_KC2_CONTENT: 'deterministic-stale-kc2-content', KC2_ARTICLE_MISSING: 'kc2-article-missing', MISSING_CANONICAL_IDENTITY: 'missing-canonical-identity', PARTIAL_EVIDENCE_CHANGE: 'partial-evidence-change-needs-review', UNRESOLVED_CANONICAL_IDENTITY: 'unresolved-canonical-identity' });
8
+ const KC2_AUTHORABILITY_STATUSES = Object.freeze({ AUTHORABLE: 'authorable', CANONICAL_CONVERGENCE: 'canonical-convergence', CURRENT: 'current', NOT_AUTHORABLE: 'not-authorable', REVIEW_REQUIRED: 'review-required', UNKNOWN: 'unknown' });
9
+ const KC2_AUTHORABILITY_QUEUES = Object.freeze({ IA_REVIEW: 'ia-review', SAFE_TO_AUTHOR: 'safe-to-author', STRUCTURAL_REVIEW: 'structural-review' });
10
+ const KC2_ARTICLE_BOUNDARY_CODES = Object.freeze({ BOUNDARY_RESOLVED: 'boundary-resolved', COMPETING_MAPPINGS: 'competing-mappings', NEEDS_IA_DECISION: 'needs-ia-decision', NOT_AN_ARTICLE: 'not-an-article' });
11
+ function uniqueSortedKeys(values = []) { return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b)); }
12
+ function normalizeIaDecision(decision = null) { if (!decision || typeof decision !== 'object') return null; const decisionType = String(decision.decisionType || '').trim() || null; if (!decisionType) return null; return { decisionType, destinationCanonicalKey: String(decision.destinationCanonicalKey || decision.targetArticleKey || '').trim() || null }; }
13
+ function isPendingIaDecisionType(value = '') { return KC2_PENDING_IA_DECISION_TYPES.includes(String(value).trim()); }
14
+ module.exports = { KC2_ARTICLE_BOUNDARY_CODES, KC2_ARTICLE_ELIGIBILITY_KINDS, KC2_AUTHORABILITY_QUEUES, KC2_AUTHORABILITY_STATUSES, KC2_IA_DECISION_TYPES, KC2_NATIVE_TARGET_REASONS, KC2_PENDING_IA_DECISION_TYPES, KC2_REPOSITORY_HELP_REASON_CODES, KC2_REPOSITORY_HELP_STATES, isPendingIaDecisionType, normalizeIaDecision, uniqueSortedKeys };
package/src/index.js ADDED
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ fingerprints: { stableHash: require('./fingerprints/stableHash') },
5
+ platform: {
6
+ kc2PlatformIdentity: require('./platform/kc2PlatformIdentity'),
7
+ kc2PlatformAdapterErrors: require('./platform/kc2PlatformAdapterErrors'),
8
+ kc2PlatformAdapterContract: require('./platform/kc2PlatformAdapterContract'),
9
+ kc2ObserveContract: require('./platform/kc2ObserveContract'),
10
+ kc2SignalContract: require('./platform/kc2SignalContract'),
11
+ kc2MaterializeContract: require('./platform/kc2MaterializeContract'),
12
+ },
13
+ ia: {
14
+ articleTopologyContracts: require('./ia/articleTopologyContracts'),
15
+ authorabilityContracts: require('./ia/authorabilityContracts'),
16
+ },
17
+ generation: {
18
+ generationDecisionContract: require('./generation/generationDecisionContract'),
19
+ },
20
+ relationships: { articleRelationshipContracts: require('./relationships/articleRelationshipContracts') },
21
+ reconciliation: {
22
+ kc2ReconciliationContract: require('./reconciliation/kc2ReconciliationContract'),
23
+ kc2ReconciliationStageContracts: require('./reconciliation/kc2ReconciliationStageContracts'),
24
+ stageArtifactEnvelope: require('./reconciliation/stageArtifactEnvelope'),
25
+ },
26
+ authority: {
27
+ authorityContracts: require('./authority/authorityContracts'),
28
+ authorityDispositionContracts: require('./authority/authorityDispositionContracts'),
29
+ humanGuidanceContracts: require('./authority/humanGuidanceContracts'),
30
+ },
31
+ persistence: {
32
+ kc2WireOperationContracts: require('./persistence/kc2WireOperationContracts'),
33
+ kc2GenerationSnapshotPersistenceContract: require('./persistence/kc2GenerationSnapshotPersistenceContract'),
34
+ kc2HumanAuthorityReadContract: require('./persistence/kc2HumanAuthorityReadContract'),
35
+ kc2HumanAuthorityWriteContract: require('./persistence/kc2HumanAuthorityWriteContract'),
36
+ },
37
+ errors: { Kc2ClientError: require('./errors/Kc2ClientError') },
38
+ transport: { requestJson: require('./transport/requestJson') },
39
+ clients: { helpClient: require('./clients/helpClient') },
40
+ };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const KC2_GENERATION_SNAPSHOT_PERSISTENCE_CAPABILITY = Object.freeze({
4
+ AVAILABLE: 'available',
5
+ UNAVAILABLE: 'unavailable',
6
+ });
7
+
8
+ function firstDefined(...values) {
9
+ return values.find((value) => value !== undefined && value !== null);
10
+ }
11
+
12
+ function projectKc2GenerationSnapshotRecord(source = {}) {
13
+ if (!source || typeof source !== 'object') return null;
14
+ return {
15
+ acceptedVersionId: firstDefined(source.acceptedVersionId, source.knowledgeArticleVersionId, source.knowledge_article_version_id, source.versionId, null),
16
+ articleKey: firstDefined(source.articleKey, source.article_key, null),
17
+ platformId: firstDefined(source.platformId, source.platform_id, null),
18
+ contractVersion: firstDefined(source.contractVersion, source.contract_version, null),
19
+ createdAt: firstDefined(source.createdAt, source.created_at, null),
20
+ expertGuidanceFingerprint: firstDefined(source.expertGuidanceFingerprint, source.expert_guidance_fingerprint, null),
21
+ expertGuidanceSnapshot: firstDefined(source.expertGuidanceSnapshot, source.expert_guidance_snapshot, []),
22
+ generationMetadata: firstDefined(source.generationMetadata, source.generation_metadata, {}),
23
+ proTipsFingerprint: firstDefined(source.proTipsFingerprint, source.pro_tips_fingerprint, null),
24
+ proTipsSnapshot: firstDefined(source.proTipsSnapshot, source.pro_tips_snapshot, []),
25
+ protectedAuthorityFingerprint: firstDefined(source.protectedAuthorityFingerprint, source.protected_authority_fingerprint, null),
26
+ protectedAuthoritySnapshot: firstDefined(source.protectedAuthoritySnapshot, source.protected_authority_snapshot, []),
27
+ snapshotId: firstDefined(source.snapshotId, source.knowledgeArticleGenerationSnapshotId, source.knowledge_article_generation_snapshot_id, null),
28
+ softwareEvidenceFingerprint: firstDefined(source.softwareEvidenceFingerprint, source.software_evidence_fingerprint, null),
29
+ softwareEvidenceSnapshot: firstDefined(source.softwareEvidenceSnapshot, source.software_evidence_snapshot, {}),
30
+ };
31
+ }
32
+
33
+ function assertKc2GenerationSnapshotPersistence(implementation) {
34
+ if (!implementation || typeof implementation !== 'object') {
35
+ throw new TypeError('KC2 generation-snapshot persistence must be an object');
36
+ }
37
+ if (implementation.capability !== KC2_GENERATION_SNAPSHOT_PERSISTENCE_CAPABILITY.AVAILABLE) {
38
+ throw new Error('KC2 generation-snapshot persistence is unavailable');
39
+ }
40
+ return implementation;
41
+ }
42
+
43
+ module.exports = {
44
+ KC2_GENERATION_SNAPSHOT_PERSISTENCE_CAPABILITY,
45
+ assertKc2GenerationSnapshotPersistence,
46
+ projectKc2GenerationSnapshotRecord,
47
+ };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const KC2_HUMAN_AUTHORITY_READ_CAPABILITY = Object.freeze({
4
+ AVAILABLE: 'available',
5
+ UNAVAILABLE: 'unavailable',
6
+ });
7
+ const KC2_EXPERT_REVIEW_OBLIGATION_CATEGORY = 'expert-review-required';
8
+
9
+ function assertKc2HumanAuthorityRead(implementation) {
10
+ if (!implementation || typeof implementation !== 'object') {
11
+ throw new TypeError('KC2 human-authority read persistence must be an object');
12
+ }
13
+ if (implementation.capability !== KC2_HUMAN_AUTHORITY_READ_CAPABILITY.AVAILABLE) {
14
+ throw new Error('KC2 human-authority read persistence is unavailable');
15
+ }
16
+ return implementation;
17
+ }
18
+
19
+ module.exports = {
20
+ KC2_EXPERT_REVIEW_OBLIGATION_CATEGORY,
21
+ KC2_HUMAN_AUTHORITY_READ_CAPABILITY,
22
+ assertKc2HumanAuthorityRead,
23
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const KC2_HUMAN_AUTHORITY_WRITE_CAPABILITY = Object.freeze({
4
+ AVAILABLE: 'available',
5
+ UNAVAILABLE: 'unavailable',
6
+ });
7
+
8
+ function assertKc2HumanAuthorityWrite(implementation) {
9
+ if (!implementation || typeof implementation !== 'object') {
10
+ throw new TypeError('KC2 human-authority write persistence must be an object');
11
+ }
12
+ if (implementation.capability !== KC2_HUMAN_AUTHORITY_WRITE_CAPABILITY.AVAILABLE) {
13
+ throw new Error('KC2 human-authority write persistence is unavailable');
14
+ }
15
+ return implementation;
16
+ }
17
+
18
+ module.exports = {
19
+ KC2_HUMAN_AUTHORITY_WRITE_CAPABILITY,
20
+ assertKc2HumanAuthorityWrite,
21
+ };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ // Names and direction only: storage, transactions, and CRUD remain server-owned.
4
+ const KC2_ACCEPTANCE_HTTP_OPERATIONS = Object.freeze(['finalize', 'status']);
5
+ const KC2_ATTEMPT_HTTP_READ_OPERATIONS = Object.freeze(['listResumableAttempts', 'readAttempt', 'readLatestCompletedAttempt', 'readLatestResumableAttempt']);
6
+ const KC2_ATTEMPT_HTTP_WRITE_OPERATIONS = Object.freeze(['claimAttemptForStage2', 'createAttempt', 'markAttemptAbandoned', 'markAttemptCompleted', 'markAttemptCompletionPersistenceFailed', 'markAttemptFailed', 'markAttemptResumable', 'markAttemptStale', 'markAttemptSuperseded', 'persistStage1Artifact', 'persistStage2Candidate']);
7
+ const KC2_ATTEMPT_HTTP_OPERATIONS = Object.freeze([...KC2_ATTEMPT_HTTP_READ_OPERATIONS, ...KC2_ATTEMPT_HTTP_WRITE_OPERATIONS]);
8
+ const KC2_EXPERT_REVIEW_HTTP_READ_OPERATIONS = Object.freeze(['loadExpertReviewObligations', 'readExpertReviewObligationById']);
9
+ const KC2_EXPERT_REVIEW_HTTP_WRITE_OPERATIONS = Object.freeze(['resolveExpertReviewObligation']);
10
+ const KC2_SNAPSHOT_HTTP_READ_OPERATIONS = Object.freeze(['listCurrentContent', 'loadAcceptedGenerationSnapshot']);
11
+ const KC2_ATTEMPT_PERSISTENCE_CAPABILITY = Object.freeze({ AVAILABLE: 'available', UNAVAILABLE: 'unavailable' });
12
+ const KC2_GENERATION_SNAPSHOT_PERSISTENCE_CAPABILITY = Object.freeze({ AVAILABLE: 'available', UNAVAILABLE: 'unavailable' });
13
+ const KC2_HUMAN_AUTHORITY_READ_CAPABILITY = Object.freeze({ AVAILABLE: 'available', UNAVAILABLE: 'unavailable' });
14
+ const KC2_HUMAN_AUTHORITY_WRITE_CAPABILITY = Object.freeze({ AVAILABLE: 'available', UNAVAILABLE: 'unavailable' });
15
+ const KC2_HUMAN_AUTHORITY_CORE_OPERATIONS = Object.freeze([
16
+ 'listActiveHumanAddendumArticleKeys', 'listAuthoringGuidance', 'listDomainAssertions',
17
+ 'listHumanAddenda', 'listProtectedContent', 'readActiveProtectedContentByTarget',
18
+ 'readAuthoringGuidanceByCreateRequest', 'readAuthoringGuidanceById',
19
+ 'readDomainAssertionByCreateRequest', 'readDomainAssertionById',
20
+ 'readHumanAddendumByCreateRequest', 'readHumanAddendumById', 'readProtectedContentById',
21
+ 'activateDomainAssertion', 'insertAuthoringGuidance', 'insertDomainAssertion',
22
+ 'insertHumanAddendum', 'insertProtectedContent', 'markAuthoringGuidanceSuperseded',
23
+ 'markDomainAssertionSuperseded', 'markProtectedContentSuperseded',
24
+ 'reactivateAuthoringGuidance', 'reactivateDomainAssertion', 'releaseProtectedContent',
25
+ 'removeHumanAddendum', 'restoreHumanAddendum', 'retireAuthoringGuidance',
26
+ 'retireDomainAssertion', 'updateAuthoringGuidance', 'updateDomainAssertion',
27
+ 'updateHumanAddendum',
28
+ ]);
29
+ const KC2_HUMAN_AUTHORITY_CORE_WRITE_OPERATIONS = Object.freeze(KC2_HUMAN_AUTHORITY_CORE_OPERATIONS.slice(13));
30
+ const KC2_EXPERT_REVIEW_OBLIGATION_CATEGORY = 'expert-review-required';
31
+ const HOST_CONVERSION_REVIEW_MARKER_CATEGORY = 'authoritative-domain-knowledge-gap';
32
+ const KC2_WIRE_OPERATION_GROUPS = Object.freeze({
33
+ acceptance: KC2_ACCEPTANCE_HTTP_OPERATIONS,
34
+ attemptRead: KC2_ATTEMPT_HTTP_READ_OPERATIONS,
35
+ attemptWrite: KC2_ATTEMPT_HTTP_WRITE_OPERATIONS,
36
+ expertReviewRead: KC2_EXPERT_REVIEW_HTTP_READ_OPERATIONS,
37
+ expertReviewWrite: KC2_EXPERT_REVIEW_HTTP_WRITE_OPERATIONS,
38
+ snapshotRead: KC2_SNAPSHOT_HTTP_READ_OPERATIONS,
39
+ });
40
+ function isKc2WireOperation(name) { return Object.values(KC2_WIRE_OPERATION_GROUPS).some((operations) => operations.includes(String(name || '').trim())); }
41
+ const isKc2AttemptAcceptanceCoupledOperation = (name) => String(name || '').trim() === 'bindAcceptedCanonicalStage2';
42
+ const isKc2SnapshotHttpOperation = (name) => KC2_SNAPSHOT_HTTP_READ_OPERATIONS.includes(String(name || '').trim());
43
+ const isKc2AcceptanceHttpOperation = (name) => KC2_ACCEPTANCE_HTTP_OPERATIONS.includes(String(name || '').trim());
44
+ const isKc2ExpertReviewHttpOperation = (name) => [...KC2_EXPERT_REVIEW_HTTP_READ_OPERATIONS, ...KC2_EXPERT_REVIEW_HTTP_WRITE_OPERATIONS].includes(String(name || '').trim());
45
+ const isKc2ExpertReviewHttpWriteOperation = (name) => KC2_EXPERT_REVIEW_HTTP_WRITE_OPERATIONS.includes(String(name || '').trim());
46
+ const isKc2ExpertReviewAcceptanceCoupledOperation = (name) => String(name || '').trim() === 'openExpertReviewObligation';
47
+ const isKc2HumanAuthorityCoreOperation = (name) => KC2_HUMAN_AUTHORITY_CORE_OPERATIONS.includes(String(name || '').trim());
48
+ const isKc2HumanAuthorityCoreWriteOperation = (name) => KC2_HUMAN_AUTHORITY_CORE_WRITE_OPERATIONS.includes(String(name || '').trim());
49
+ const isKc2AttemptHttpOperation = (name) => [...KC2_ATTEMPT_HTTP_READ_OPERATIONS, ...KC2_ATTEMPT_HTTP_WRITE_OPERATIONS].includes(String(name || '').trim());
50
+ const isKc2AttemptHttpWriteOperation = (name) => KC2_ATTEMPT_HTTP_WRITE_OPERATIONS.includes(String(name || '').trim());
51
+ const isKc2SnapshotAcceptanceCoupledOperation = (name) => String(name || '').trim() === 'recordAcceptedGenerationSnapshot';
52
+ module.exports = {
53
+ HOST_CONVERSION_REVIEW_MARKER_CATEGORY,
54
+ KC2_ACCEPTANCE_HTTP_OPERATIONS,
55
+ KC2_ATTEMPT_HTTP_READ_OPERATIONS,
56
+ KC2_ATTEMPT_HTTP_OPERATIONS,
57
+ KC2_ATTEMPT_HTTP_WRITE_OPERATIONS,
58
+ KC2_ATTEMPT_PERSISTENCE_CAPABILITY,
59
+ KC2_EXPERT_REVIEW_HTTP_READ_OPERATIONS,
60
+ KC2_EXPERT_REVIEW_HTTP_WRITE_OPERATIONS,
61
+ KC2_EXPERT_REVIEW_OBLIGATION_CATEGORY,
62
+ KC2_GENERATION_SNAPSHOT_PERSISTENCE_CAPABILITY,
63
+ KC2_HUMAN_AUTHORITY_CORE_OPERATIONS,
64
+ KC2_HUMAN_AUTHORITY_CORE_WRITE_OPERATIONS,
65
+ KC2_HUMAN_AUTHORITY_READ_CAPABILITY,
66
+ KC2_HUMAN_AUTHORITY_WRITE_CAPABILITY,
67
+ KC2_SNAPSHOT_HTTP_READ_OPERATIONS,
68
+ KC2_WIRE_OPERATION_GROUPS,
69
+ isKc2AcceptanceHttpOperation,
70
+ isKc2AttemptAcceptanceCoupledOperation,
71
+ isKc2AttemptHttpOperation,
72
+ isKc2AttemptHttpWriteOperation,
73
+ isKc2ExpertReviewAcceptanceCoupledOperation,
74
+ isKc2ExpertReviewHttpOperation,
75
+ isKc2ExpertReviewHttpWriteOperation,
76
+ isKc2HumanAuthorityCoreOperation,
77
+ isKc2HumanAuthorityCoreWriteOperation,
78
+ isKc2SnapshotAcceptanceCoupledOperation,
79
+ isKc2SnapshotHttpOperation,
80
+ isKc2WireOperation,
81
+ };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ const { hashContent } = require('../fingerprints/stableHash');
3
+ const { normalizeKc2HostObjectRef, normalizeKc2OpaqueActor, normalizeKc2OpaquePublicationIdentity, normalizeKc2PlatformId } = require('./kc2PlatformIdentity');
4
+ const KC2_MATERIALIZE_CONTRACT_VERSION = 'kc2-materialize.v1';
5
+ const KC2_MATERIALIZE_OPERATIONS = Object.freeze(['publish-article', 'update-article', 'retire-article', 'compensate', 'verify']);
6
+ const KC2_MATERIALIZE_MUTATION_OPERATIONS = Object.freeze(['publish-article', 'update-article', 'retire-article']);
7
+ const KC2_MATERIALIZE_RESULT_FORBIDDEN_KEYS = Object.freeze(['accepted', 'canonicalAcceptance', 'currentness', 'expertReviewDisposition', 'generationRequired']);
8
+ const KC2_COMPENSATE_REASONS = Object.freeze(['kc2-finalize-failed-after-materialization']);
9
+ function rejectPolicyFields(source, label) {
10
+ for (const key of KC2_MATERIALIZE_RESULT_FORBIDDEN_KEYS) if (source && Object.hasOwn(source, key)) {
11
+ const error = new Error(`${label} must not include KC2 policy field ${key}`); error.code = 'kc2-platform-materialization-failed'; throw error;
12
+ }
13
+ }
14
+ function hashKc2MaterializeRequest(request = {}) {
15
+ return hashContent({ operation: request.operation || null, payload: request.payload || null, platformId: request.platformId || null, subject: request.subject || null });
16
+ }
17
+ function normalizeKc2MaterializeRequest(input = {}, options = {}) {
18
+ const subject = normalizeKc2HostObjectRef(input.subject || input);
19
+ const operation = String(input.operation || 'publish-article').trim();
20
+ if (!KC2_MATERIALIZE_OPERATIONS.includes(operation)) { const error = new Error(`materialize operation is not recognized: ${operation}`); error.code = 'kc2-platform-materialization-rejected'; throw error; }
21
+ const timeoutMs = input.timeoutMs == null ? 30000 : Number(input.timeoutMs);
22
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) { const error = new Error('timeoutMs must be a positive integer'); error.code = 'kc2-platform-timeout'; throw error; }
23
+ const operationId = input.operationId == null ? null : String(input.operationId).trim();
24
+ if (options.requireOperationId === true && !operationId) { const error = new Error('operationId is required'); error.code = 'kc2-platform-materialization-rejected'; throw error; }
25
+ const normalized = { actor: normalizeKc2OpaqueActor(input.actor), contractVersion: KC2_MATERIALIZE_CONTRACT_VERSION, operation, operationId, payload: input.payload && typeof input.payload === 'object' ? input.payload : null, platformId: subject.platformId, subject, timeoutMs };
26
+ return { ...normalized, requestHash: hashKc2MaterializeRequest(normalized) };
27
+ }
28
+ function normalizeKc2MaterializeResult(input = {}) {
29
+ rejectPolicyFields(input, 'materialize result');
30
+ return { destinationIdentity: input.destinationIdentity == null ? null : String(input.destinationIdentity), error: input.error && typeof input.error === 'object' ? input.error : null, materializationFingerprint: input.materializationFingerprint == null ? null : String(input.materializationFingerprint), operationId: input.operationId == null ? null : String(input.operationId), publicationIdentity: normalizeKc2OpaquePublicationIdentity(input.publicationIdentity), requestHash: input.requestHash == null ? null : String(input.requestHash), rollbackToken: input.rollbackToken == null ? null : String(input.rollbackToken), success: input.success === true };
31
+ }
32
+ function normalizeKc2CompensateRequest(input = {}) {
33
+ const operationId = String(input.operationId || '').trim();
34
+ if (!operationId) { const error = new Error('operationId is required'); error.code = 'kc2-platform-materialization-rejected'; throw error; }
35
+ const timeoutMs = input.timeoutMs == null ? 30000 : Number(input.timeoutMs);
36
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) { const error = new Error('timeoutMs must be a positive integer'); error.code = 'kc2-platform-timeout'; throw error; }
37
+ return { contractVersion: KC2_MATERIALIZE_CONTRACT_VERSION, operation: 'compensate', operationId, platformId: normalizeKc2PlatformId(input.platformId), publicationIdentity: normalizeKc2OpaquePublicationIdentity(input.publicationIdentity || input.materializationIdentity), reason: KC2_COMPENSATE_REASONS.includes(input.reason) ? input.reason : KC2_COMPENSATE_REASONS[0], rollbackToken: input.rollbackToken == null ? null : String(input.rollbackToken), timeoutMs };
38
+ }
39
+ function describeKc2MaterializeSemantics() { return { automaticPublicationRequiresSeparateAuthority: true, capabilityIsNotAuthority: true, compensationIsHostOwned: true, compensationReversesHostSideEffectsOnly: true, distributedTwoPhaseCommit: false, doesNotDecideAcceptance: true, doesNotDecideCurrentness: true, laterObserveMayConfirm: true, noPublicationAvoidsMaterialize: true, operationIdOwnedByKc2Saga: true, payloadIsOpaqueHostContext: true, publicationRequiredUsesSaga: true, replaySamePayloadReturnsPriorResult: true, replayDifferentPayloadConflicts: true, successfulMaterializeDoesNotRefreshAllObservations: true }; }
40
+ module.exports = { KC2_COMPENSATE_REASONS, KC2_MATERIALIZE_CONTRACT_VERSION, KC2_MATERIALIZE_MUTATION_OPERATIONS, KC2_MATERIALIZE_OPERATIONS, KC2_MATERIALIZE_RESULT_FORBIDDEN_KEYS, describeKc2MaterializeSemantics, hashKc2MaterializeRequest, normalizeKc2CompensateRequest, normalizeKc2MaterializeRequest, normalizeKc2MaterializeResult };
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+ const { normalizeKc2HostObjectRef, normalizeKc2OpaquePublicationIdentity, normalizeKc2PlatformIdentity } = require('./kc2PlatformIdentity');
3
+ const KC2_OBSERVE_FACT_KINDS = Object.freeze(['inventory', 'evidence', 'relationships', 'organization', 'materialization', 'revision']);
4
+ const KC2_OBSERVE_FORBIDDEN_KEYS = Object.freeze(['automaticWorkPermitted', 'bootstrap', 'canonicalStage2', 'currentness', 'docsShouldExist', 'expertReviewRequired', 'generationRequired', 'reconciliationRequired', 'stale', 'workRequired']);
5
+ function rejectPolicyFields(source, label) {
6
+ if (!source || typeof source !== 'object') return;
7
+ for (const key of KC2_OBSERVE_FORBIDDEN_KEYS) if (Object.hasOwn(source, key)) {
8
+ const error = new Error(`${label} must not include KC2 policy field ${key}`); error.code = 'kc2-platform-observation-failed'; throw error;
9
+ }
10
+ }
11
+ function normalizeKc2ObservationProvenance(input = {}) {
12
+ const { platformId } = normalizeKc2PlatformIdentity(input);
13
+ return { adapterVersion: input.adapterVersion == null ? null : String(input.adapterVersion), changedScope: Array.isArray(input.changedScope) ? input.changedScope.slice() : null, evidenceFingerprint: input.evidenceFingerprint == null ? null : String(input.evidenceFingerprint), observedAt: input.observedAt == null ? null : String(input.observedAt), platformId, sourceFingerprint: input.sourceFingerprint == null ? null : String(input.sourceFingerprint), sourceRevision: input.sourceRevision == null ? null : String(input.sourceRevision) };
14
+ }
15
+ function normalizeKc2Observation(input = {}) {
16
+ rejectPolicyFields(input, 'observation'); rejectPolicyFields(input.facts, 'observation.facts');
17
+ const provenance = normalizeKc2ObservationProvenance(input);
18
+ const factKind = String(input.factKind || '').trim();
19
+ if (!KC2_OBSERVE_FACT_KINDS.includes(factKind)) { const error = new Error(`observation factKind is not recognized: ${factKind}`); error.code = 'kc2-platform-observation-failed'; throw error; }
20
+ const subject = input.subject ? normalizeKc2HostObjectRef({ ...input.subject, platformId: input.subject.platformId || provenance.platformId }) : { hostObjectId: null, hostObjectKind: null, kc2ArticleKey: null, platformId: provenance.platformId };
21
+ const facts = input.facts && typeof input.facts === 'object' ? { ...input.facts } : {};
22
+ if (factKind === 'materialization') {
23
+ facts.exists = facts.exists === true;
24
+ facts.publicationIdentity = normalizeKc2OpaquePublicationIdentity(facts.publicationIdentity || facts.currentVersionId);
25
+ facts.materializationFingerprint = facts.materializationFingerprint == null ? null : String(facts.materializationFingerprint);
26
+ facts.destinationIdentity = facts.destinationIdentity == null ? null : String(facts.destinationIdentity);
27
+ }
28
+ return { ...provenance, factKind, facts, subject };
29
+ }
30
+ module.exports = { KC2_OBSERVE_FACT_KINDS, KC2_OBSERVE_FORBIDDEN_KEYS, normalizeKc2Observation, normalizeKc2ObservationProvenance };
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ const { createKc2PlatformAdapterError, KC2_PLATFORM_ADAPTER_ERROR_CODES } = require('./kc2PlatformAdapterErrors');
4
+ const { normalizeKc2PlatformIdentity } = require('./kc2PlatformIdentity');
5
+ const KC2_PLATFORM_ADAPTER_CONTRACT_VERSION = 'kc2-platform-adapter.v1';
6
+ const KC2_PLATFORM_CAPABILITIES = Object.freeze({
7
+ MAINTAIN_APPROVED_WORK: 'maintain.approvedWork', MAINTAIN_ORGANIZATION: 'maintain.organization',
8
+ MAINTAIN_RELATIONSHIPS: 'maintain.relationships', MAINTAIN_REVIEW_MARKER: 'maintain.reviewMarker',
9
+ MATERIALIZE_ARTICLE: 'materialize.article', MATERIALIZE_COMPENSATE: 'materialize.compensate',
10
+ OBSERVE_EVIDENCE: 'observe.evidence', OBSERVE_INVENTORY: 'observe.inventory',
11
+ OBSERVE_MATERIALIZATION: 'observe.materialization', OBSERVE_ORGANIZATION: 'observe.organization',
12
+ OBSERVE_RELATIONSHIPS: 'observe.relationships', OBSERVE_REVISION: 'observe.revision',
13
+ SIGNAL_CHANGE: 'signal.change',
14
+ });
15
+ const KC2_PLATFORM_OPTIONAL_CAPABILITIES = Object.freeze(Object.values(KC2_PLATFORM_CAPABILITIES));
16
+ const KC2_PLATFORM_FAMILY_METHODS = Object.freeze({
17
+ maintain: ['approvedWork', 'organization', 'relationships', 'reviewMarker'],
18
+ materialize: ['article', 'compensate'],
19
+ observe: ['evidence', 'inventory', 'materialization', 'organization', 'relationships', 'revision'],
20
+ signal: ['change'],
21
+ });
22
+ function listDeclaredCapabilities(capabilities = {}) {
23
+ return Object.values(KC2_PLATFORM_CAPABILITIES).filter((name) => capabilities[name] === true);
24
+ }
25
+ function capabilityMethod(capability) {
26
+ const [family, method] = String(capability).split('.');
27
+ return { family, method };
28
+ }
29
+ function normalizeKc2PlatformCapabilities(input = {}) {
30
+ const capabilities = Object.fromEntries(Object.values(KC2_PLATFORM_CAPABILITIES)
31
+ .map((name) => [name, input[name] === true]));
32
+ if (!['observe.inventory', 'observe.revision', 'observe.materialization']
33
+ .some((name) => capabilities[name])) {
34
+ throw createKc2PlatformAdapterError(KC2_PLATFORM_ADAPTER_ERROR_CODES.ADAPTER_INCOMPATIBLE,
35
+ 'a platform adapter must declare at least one observe capability');
36
+ }
37
+ return Object.freeze(capabilities);
38
+ }
39
+ function assertKc2AdapterCallBounds(input = {}) {
40
+ if (input.timeoutMs == null) return input;
41
+ if (!Number.isInteger(Number(input.timeoutMs)) || Number(input.timeoutMs) <= 0) {
42
+ throw createKc2PlatformAdapterError(KC2_PLATFORM_ADAPTER_ERROR_CODES.TIMEOUT,
43
+ 'adapter timeoutMs must be a positive integer');
44
+ }
45
+ return input;
46
+ }
47
+ function hasCapability(adapter, capability) { return adapter?.capabilities?.[capability] === true; }
48
+ function assertCapability(adapter, capability) {
49
+ if (!hasCapability(adapter, capability)) throw createKc2PlatformAdapterError(
50
+ KC2_PLATFORM_ADAPTER_ERROR_CODES.UNSUPPORTED_CAPABILITY, `unsupported platform capability: ${capability}`);
51
+ }
52
+ function assertKc2PlatformAdapter(adapter = {}) {
53
+ if (adapter.contractVersion !== KC2_PLATFORM_ADAPTER_CONTRACT_VERSION) throw createKc2PlatformAdapterError(
54
+ KC2_PLATFORM_ADAPTER_ERROR_CODES.ADAPTER_INCOMPATIBLE, 'incompatible platform adapter contract');
55
+ const capabilities = normalizeKc2PlatformCapabilities(adapter.capabilities);
56
+ for (const capability of listDeclaredCapabilities(capabilities)) {
57
+ const { family, method } = capabilityMethod(capability);
58
+ if (typeof adapter[family]?.[method] !== 'function') throw createKc2PlatformAdapterError(
59
+ KC2_PLATFORM_ADAPTER_ERROR_CODES.ADAPTER_INCOMPATIBLE, `declared capability ${capability} has no implementation`);
60
+ }
61
+ for (const [family, methods] of Object.entries(KC2_PLATFORM_FAMILY_METHODS)) for (const method of methods) {
62
+ if (typeof adapter[family]?.[method] === 'function' && !capabilities[`${family}.${method}`]) throw createKc2PlatformAdapterError(
63
+ KC2_PLATFORM_ADAPTER_ERROR_CODES.ADAPTER_INCOMPATIBLE, `implemented ${family}.${method} must be declared`);
64
+ }
65
+ return { capabilities, contractVersion: adapter.contractVersion, platformId: normalizeKc2PlatformIdentity(adapter).platformId };
66
+ }
67
+ function describeKc2PlatformAdapter(adapter = {}) {
68
+ const { capabilities, contractVersion, platformId } = assertKc2PlatformAdapter(adapter);
69
+ const declared = listDeclaredCapabilities(capabilities);
70
+ return {
71
+ capabilities: declared, contractVersion, platformId,
72
+ maintainSupported: declared.some((n) => n.startsWith('maintain.')),
73
+ materializeSupported: declared.some((n) => n.startsWith('materialize.')),
74
+ observeSupported: declared.some((n) => n.startsWith('observe.')),
75
+ signalSupported: declared.includes('signal.change'),
76
+ pollOnly: declared.some((n) => n.startsWith('observe.')) && !declared.includes('signal.change'),
77
+ readOnly: !declared.some((n) => n.startsWith('materialize.') || n.startsWith('maintain.')),
78
+ };
79
+ }
80
+ module.exports = {
81
+ KC2_PLATFORM_ADAPTER_CONTRACT_VERSION, KC2_PLATFORM_CAPABILITIES, KC2_PLATFORM_OPTIONAL_CAPABILITIES,
82
+ assertCapability, assertKc2AdapterCallBounds, assertKc2PlatformAdapter, describeKc2PlatformAdapter,
83
+ hasCapability, listDeclaredCapabilities, normalizeKc2PlatformCapabilities,
84
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const KC2_PLATFORM_ADAPTER_ERROR_CODES = Object.freeze({
4
+ UNAVAILABLE: 'kc2-platform-unavailable',
5
+ UNSUPPORTED_CAPABILITY: 'kc2-platform-capability-unsupported',
6
+ OBSERVATION_FAILED: 'kc2-platform-observation-failed',
7
+ MATERIALIZATION_REJECTED: 'kc2-platform-materialization-rejected',
8
+ MATERIALIZATION_FAILED: 'kc2-platform-materialization-failed',
9
+ COMPENSATION_FAILED: 'kc2-platform-compensation-failed',
10
+ OBSERVATION_STALE: 'kc2-platform-observation-stale',
11
+ IDENTITY_UNRESOLVED: 'kc2-platform-identity-unresolved',
12
+ ADAPTER_INCOMPATIBLE: 'kc2-platform-adapter-incompatible',
13
+ TIMEOUT: 'kc2-platform-timeout',
14
+ });
15
+ function createKc2PlatformAdapterError(code, message, cause) {
16
+ if (!Object.values(KC2_PLATFORM_ADAPTER_ERROR_CODES).includes(code)) {
17
+ throw new Error(`unknown platform adapter error code: ${code}`);
18
+ }
19
+ const error = new Error(message || code);
20
+ error.code = code;
21
+ if (cause !== undefined) error.cause = cause;
22
+ return error;
23
+ }
24
+ module.exports = { KC2_PLATFORM_ADAPTER_ERROR_CODES, createKc2PlatformAdapterError };
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const KC2_HOST_OBJECT_KINDS = Object.freeze([
4
+ 'article', 'surface', 'evidence', 'relationship', 'organization',
5
+ 'revision', 'publication',
6
+ ]);
7
+ const KC2_ACTOR_KINDS = Object.freeze(['human', 'kc2-system', 'host-service']);
8
+ const KC2_PLATFORM_ID_MIN = 1;
9
+ const KC2_PLATFORM_ID_MAX = 32767;
10
+
11
+ function identityError(message) {
12
+ const error = new Error(message);
13
+ error.code = 'kc2-platform-identity-unresolved';
14
+ return error;
15
+ }
16
+ function normalizeKc2PlatformId(value) {
17
+ if (typeof value === 'string' && /^-?\d+$/.test(value.trim())) value = Number(value);
18
+ if (!Number.isInteger(value) || value < KC2_PLATFORM_ID_MIN || value > KC2_PLATFORM_ID_MAX) {
19
+ throw identityError('platformId must be a registered SMALLINT');
20
+ }
21
+ return value;
22
+ }
23
+ function requireKc2PlatformId(input = {}) { return normalizeKc2PlatformId(input.platformId); }
24
+ function normalizeKc2PlatformIdentity(input = {}) {
25
+ return { platformId: requireKc2PlatformId(input) };
26
+ }
27
+ function bindKc2AdapterPlatformIdentity(adapter = {}, input = {}) {
28
+ const platformId = normalizeKc2PlatformId(adapter.platformId);
29
+ return { ...input, platformId, subject: input.subject ? { ...input.subject, platformId } : input.subject };
30
+ }
31
+ function normalizeKc2HostObjectRef(input = {}) {
32
+ const platformId = normalizeKc2PlatformId(input.platformId);
33
+ const hostObjectKind = input.hostObjectKind == null || input.hostObjectKind === ''
34
+ ? null : String(input.hostObjectKind).trim();
35
+ if (hostObjectKind && !KC2_HOST_OBJECT_KINDS.includes(hostObjectKind)) {
36
+ throw identityError(`hostObjectKind is not recognized: ${hostObjectKind}`);
37
+ }
38
+ return {
39
+ hostObjectId: input.hostObjectId == null || input.hostObjectId === '' ? null : String(input.hostObjectId),
40
+ hostObjectKind,
41
+ kc2ArticleKey: input.kc2ArticleKey == null || input.kc2ArticleKey === ''
42
+ ? null : String(input.kc2ArticleKey).trim(),
43
+ platformId,
44
+ };
45
+ }
46
+ function normalizeKc2OpaqueActor(input = null) {
47
+ if (input == null) return null;
48
+ const kind = String(input.kind || '').trim();
49
+ if (!KC2_ACTOR_KINDS.includes(kind)) throw identityError('actor kind is not recognized');
50
+ const id = String(input.id || '').trim();
51
+ if (!id) throw identityError('actor.id is required');
52
+ return { id, kind };
53
+ }
54
+ function normalizeKc2OpaquePublicationIdentity(value) {
55
+ if (value == null || value === '') return null;
56
+ if (typeof value === 'object') value = value.publicationIdentity == null ? value.id : value.publicationIdentity;
57
+ return value == null || value === '' ? null : String(value);
58
+ }
59
+ function describeKc2PlatformIdentityModel() {
60
+ return {
61
+ deploymentIdRejected: true, environmentPropagationRejected: true, groupIdDistinct: true,
62
+ mutableGlobalCurrentPlatformRejected: true, platformIdIsPrimaryKey: true,
63
+ platformIdType: 'smallint', repositoryBoundHostConstant: true,
64
+ tenantIdentityOwner: 'external-login-authorization-system',
65
+ };
66
+ }
67
+ module.exports = {
68
+ KC2_ACTOR_KINDS, KC2_HOST_OBJECT_KINDS, KC2_PLATFORM_ID_MAX, KC2_PLATFORM_ID_MIN,
69
+ bindKc2AdapterPlatformIdentity, describeKc2PlatformIdentityModel,
70
+ normalizeKc2HostObjectRef, normalizeKc2OpaqueActor, normalizeKc2OpaquePublicationIdentity,
71
+ normalizeKc2PlatformId, normalizeKc2PlatformIdentity, requireKc2PlatformId,
72
+ };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+ const { normalizeKc2PlatformIdentity } = require('./kc2PlatformIdentity');
3
+ const KC2_SIGNAL_CONTRACT_VERSION = 'kc2-signal.v1';
4
+ const KC2_SIGNAL_FORBIDDEN_KEYS = Object.freeze(['generate', 'generationRequired', 'publish', 'regenerate', 'runAudit', 'stale', 'workRequired']);
5
+ function rejectPolicyFields(input) {
6
+ for (const key of KC2_SIGNAL_FORBIDDEN_KEYS) if (input && Object.hasOwn(input, key)) {
7
+ const error = new Error(`signal must not include KC2 policy field ${key}`); error.code = 'kc2-platform-observation-failed'; throw error;
8
+ }
9
+ }
10
+ function normalizeKc2ChangeSignal(input = {}) {
11
+ rejectPolicyFields(input);
12
+ if (input.contractVersion != null && input.contractVersion !== KC2_SIGNAL_CONTRACT_VERSION) {
13
+ const error = new Error(`unsupported signal contract version ${input.contractVersion}`); error.code = 'kc2-platform-observation-failed'; throw error;
14
+ }
15
+ const { platformId } = normalizeKc2PlatformIdentity(input);
16
+ const signalId = String(input.signalId || input.correlationKey || '').trim();
17
+ if (!signalId) { const error = new Error('signalId or correlationKey is required'); error.code = 'kc2-platform-identity-unresolved'; throw error; }
18
+ return { changeCategories: Array.isArray(input.changeCategories) ? input.changeCategories.slice() : [], changedScope: Array.isArray(input.changedScope) ? input.changedScope.slice() : [], contractVersion: KC2_SIGNAL_CONTRACT_VERSION, correlationKey: input.correlationKey == null ? signalId : String(input.correlationKey), observedAt: input.observedAt == null ? null : String(input.observedAt), observedRevision: input.observedRevision == null ? null : String(input.observedRevision), observationFingerprint: input.observationFingerprint == null ? null : String(input.observationFingerprint), platformId, priorRevision: input.priorRevision == null ? null : String(input.priorRevision), signalId };
19
+ }
20
+ function describeKc2SignalSemantics() {
21
+ return { advisoryOnly: true, broadOrPartialScopeValid: true, doesNotCauseGeneration: true, doesNotCausePublication: true, doesNotMutateCanonicalState: true, doesNotReplaceObserve: true, duplicateSignalsSafe: true, missingSignalsValid: true, outOfOrderSignalsSafe: true, pollOnlyHostValid: true };
22
+ }
23
+ module.exports = { KC2_SIGNAL_CONTRACT_VERSION, KC2_SIGNAL_FORBIDDEN_KEYS, describeKc2SignalSemantics, normalizeKc2ChangeSignal };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ // Wire vocabulary only. Reconciliation planning, validation, and execution
4
+ // remain KC2 server implementation.
5
+ const KC2_RECONCILIATION_CONTRACT_VERSION = 'kc2-reconciliation.v1';
6
+ const EVIDENCE_REF_KIND = Object.freeze(['sep', 'implementation', 'neighbor', 'policy', 'identity-label']);
7
+ const REQUIRED_PROGRESSIVE_DEPTH_LAYERS = Object.freeze(['summary', 'details', 'deepDive']);
8
+
9
+ module.exports = {
10
+ EVIDENCE_REF_KIND,
11
+ KC2_RECONCILIATION_CONTRACT_VERSION,
12
+ REQUIRED_PROGRESSIVE_DEPTH_LAYERS,
13
+ };
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+ const KC2_RECONCILIATION_DECISION_CONTRACT_VERSION = 'kc2-reconciliation-decision.v1';
3
+ const KC2_RECONCILIATION_ARTICLE_CONTRACT_VERSION = 'kc2-reconciliation-article.v1';
4
+ const KC2_RECONCILIATION_DECISION_CONTRACT_VERSION_V2 = 'kc2-reconciliation-decision.v2';
5
+ const KC2_RECONCILIATION_ARTICLE_CONTRACT_VERSION_V2 = 'kc2-reconciliation-article.v2';
6
+ module.exports = {
7
+ KC2_RECONCILIATION_ARTICLE_CONTRACT_VERSION,
8
+ KC2_RECONCILIATION_ARTICLE_CONTRACT_VERSION_V2,
9
+ KC2_RECONCILIATION_DECISION_CONTRACT_VERSION,
10
+ KC2_RECONCILIATION_DECISION_CONTRACT_VERSION_V2,
11
+ };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ // Envelope vocabulary only. Artifact validation and lifecycle remain server-side.
4
+ const ARTIFACT_ROLE = Object.freeze({
5
+ CANDIDATE: 'candidate',
6
+ CANONICAL: 'canonical',
7
+ });
8
+
9
+ const ARTIFACT_CURRENCY = Object.freeze({
10
+ CURRENT: 'current',
11
+ SUPERSEDED: 'superseded',
12
+ });
13
+
14
+ module.exports = {
15
+ ARTIFACT_CURRENCY,
16
+ ARTIFACT_ROLE,
17
+ };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+ const KC2_ARTICLE_RELATIONSHIP_ROLES = Object.freeze(['related', 'next-step', 'prerequisite', 'alternative', 'manage', 'supporting']);
3
+ const KC2_ARTICLE_RELATIONSHIP_DEFAULT_ROLE = 'related';
4
+ const KC2_ARTICLE_RELATIONSHIP_STATUSES = Object.freeze({ ACCEPTED: 'accepted' });
5
+ const KC2_ARTICLE_RELATIONSHIP_SOURCE = 'operator-relationship-curation';
6
+ const KC2_ARTICLE_RELATIONSHIP_ID_PREFIX = 'kar-';
7
+ const KC2_ARTICLE_RELATIONSHIP_DISMISSAL_ID_PREFIX = 'kard-';
8
+ module.exports = { KC2_ARTICLE_RELATIONSHIP_DEFAULT_ROLE, KC2_ARTICLE_RELATIONSHIP_DISMISSAL_ID_PREFIX, KC2_ARTICLE_RELATIONSHIP_ID_PREFIX, KC2_ARTICLE_RELATIONSHIP_ROLES, KC2_ARTICLE_RELATIONSHIP_SOURCE, KC2_ARTICLE_RELATIONSHIP_STATUSES };
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const http = require('node:http');
4
+ const https = require('node:https');
5
+ const { URL } = require('node:url');
6
+ const Kc2ClientError = require('../errors/Kc2ClientError');
7
+
8
+ const DEFAULT_TIMEOUT_MS = 10000;
9
+
10
+ function requestJson({
11
+ body = null,
12
+ method = 'GET',
13
+ rejectUnauthorized = false,
14
+ timeoutMs = DEFAULT_TIMEOUT_MS,
15
+ url,
16
+ } = {}) {
17
+ return new Promise((resolve, reject) => {
18
+ let parsed;
19
+ try {
20
+ parsed = new URL(url);
21
+ } catch (cause) {
22
+ reject(new Kc2ClientError('KC2 client URL is invalid', {
23
+ code: 'kc2-client-invalid-url',
24
+ cause,
25
+ }));
26
+ return;
27
+ }
28
+ const transport = parsed.protocol === 'https:' ? https : http;
29
+ const payload = body == null ? '' : JSON.stringify(body);
30
+ const request = transport.request({
31
+ hostname: parsed.hostname,
32
+ method,
33
+ path: `${parsed.pathname}${parsed.search}`,
34
+ port: parsed.port,
35
+ rejectUnauthorized,
36
+ headers: {
37
+ Accept: 'application/json',
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Length': Buffer.byteLength(payload),
40
+ },
41
+ }, (response) => {
42
+ const chunks = [];
43
+ response.on('data', (chunk) => chunks.push(chunk));
44
+ response.on('end', () => {
45
+ const text = Buffer.concat(chunks).toString('utf8');
46
+ try {
47
+ resolve({
48
+ body: text ? JSON.parse(text) : null,
49
+ statusCode: response.statusCode,
50
+ });
51
+ } catch (cause) {
52
+ reject(new Kc2ClientError('KC2 client received non-JSON response', {
53
+ code: 'kc2-client-invalid-response',
54
+ statusCode: response.statusCode || 503,
55
+ cause,
56
+ }));
57
+ }
58
+ });
59
+ });
60
+ request.setTimeout(timeoutMs, () => {
61
+ request.destroy(new Kc2ClientError('KC2 client request timed out', {
62
+ code: 'kc2-client-timeout',
63
+ }));
64
+ });
65
+ request.on('error', (cause) => {
66
+ if (cause instanceof Kc2ClientError) {
67
+ reject(cause);
68
+ return;
69
+ }
70
+ reject(new Kc2ClientError('KC2 client request failed', { cause }));
71
+ });
72
+ request.end(payload);
73
+ });
74
+ }
75
+
76
+ module.exports = {
77
+ DEFAULT_TIMEOUT_MS,
78
+ requestJson,
79
+ };