@neurcode-ai/contracts 0.1.3 → 0.2.1

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.
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REPO_INTELLIGENCE_OPERATIONS_SCHEMA_VERSION = exports.REPO_INTELLIGENCE_EVIDENCE_SCHEMA_VERSION = exports.SEMANTIC_ADVISORY_SCHEMA_VERSION = exports.POLICY_EVALUATION_SCHEMA_VERSION = exports.COMPILED_STRUCTURAL_POLICY_SCHEMA_VERSION = exports.PROPOSED_CHANGE_ENVELOPE_SCHEMA_VERSION = exports.REPOSITORY_GRAPH_SCHEMA_VERSION = void 0;
4
+ exports.assertSourceFreeRepoIntelligence = assertSourceFreeRepoIntelligence;
5
+ exports.isRepoIntelligenceEvidence = isRepoIntelligenceEvidence;
6
+ exports.sourceFreePrivacyContract = sourceFreePrivacyContract;
7
+ exports.REPOSITORY_GRAPH_SCHEMA_VERSION = 'neurcode.repository-graph.v2';
8
+ exports.PROPOSED_CHANGE_ENVELOPE_SCHEMA_VERSION = 'neurcode.proposed-change.v2';
9
+ exports.COMPILED_STRUCTURAL_POLICY_SCHEMA_VERSION = 'neurcode.compiled-structural-policy.v2';
10
+ exports.POLICY_EVALUATION_SCHEMA_VERSION = 'neurcode.policy-evaluation.v2';
11
+ exports.SEMANTIC_ADVISORY_SCHEMA_VERSION = 'neurcode.semantic-advisory.v2';
12
+ exports.REPO_INTELLIGENCE_EVIDENCE_SCHEMA_VERSION = 'neurcode.repo-intelligence-evidence.v2';
13
+ exports.REPO_INTELLIGENCE_OPERATIONS_SCHEMA_VERSION = 'neurcode.repo-intelligence-operations.v1';
14
+ const SOURCE_LIKE_KEYS = new Set([
15
+ 'content',
16
+ 'source',
17
+ 'sourcetext',
18
+ 'sourcecode',
19
+ 'diff',
20
+ 'patch',
21
+ 'before',
22
+ 'after',
23
+ 'rawprompt',
24
+ 'rawchat',
25
+ 'terminaloutput',
26
+ 'secret',
27
+ ]);
28
+ function normalizedKey(key) {
29
+ return key.toLowerCase().replace(/[^a-z0-9]/g, '');
30
+ }
31
+ function assertSourceFreeRepoIntelligence(value, path = 'payload') {
32
+ if (Array.isArray(value)) {
33
+ value.forEach((item, index) => assertSourceFreeRepoIntelligence(item, `${path}[${index}]`));
34
+ return;
35
+ }
36
+ if (!value || typeof value !== 'object')
37
+ return;
38
+ for (const [key, child] of Object.entries(value)) {
39
+ if (SOURCE_LIKE_KEYS.has(normalizedKey(key))) {
40
+ throw new Error(`source-like repository intelligence field is not allowed: ${path}.${key}`);
41
+ }
42
+ assertSourceFreeRepoIntelligence(child, `${path}.${key}`);
43
+ }
44
+ }
45
+ function isRepoIntelligenceEvidence(value) {
46
+ if (!value || typeof value !== 'object' || Array.isArray(value))
47
+ return false;
48
+ const record = value;
49
+ if (record.schemaVersion !== exports.REPO_INTELLIGENCE_EVIDENCE_SCHEMA_VERSION)
50
+ return false;
51
+ if (typeof record.evidenceId !== 'string' || typeof record.generatedAt !== 'string')
52
+ return false;
53
+ if (!['deterministic', 'backend_signed', 'advisory', 'not_evaluated'].includes(String(record.classification)))
54
+ return false;
55
+ if (!['allow', 'warn', 'block', 'not_evaluated'].includes(String(record.verdict)))
56
+ return false;
57
+ const enforcement = record.enforcement;
58
+ if (!enforcement || ![
59
+ 'hard_prewrite', 'cooperative_prewrite', 'supervised_write',
60
+ 'post_write', 'ci_only', 'not_supported',
61
+ ].includes(String(enforcement.capability)))
62
+ return false;
63
+ const graph = record.graph;
64
+ const policy = record.policy;
65
+ const privacy = record.privacy;
66
+ if (!graph || !policy || !privacy)
67
+ return false;
68
+ if (graph.canonicalModel !== undefined && graph.canonicalModel !== 'repository_graph_v2')
69
+ return false;
70
+ if (graph.storageSchemaVersion !== undefined && graph.storageSchemaVersion !== null
71
+ && (typeof graph.storageSchemaVersion !== 'number'
72
+ || !Number.isInteger(graph.storageSchemaVersion) || graph.storageSchemaVersion < 0))
73
+ return false;
74
+ for (const key of ['lastSuccessfulIndexAt', 'lastAttemptedIndexAt']) {
75
+ if (graph[key] !== undefined && graph[key] !== null && typeof graph[key] !== 'string')
76
+ return false;
77
+ }
78
+ for (const key of ['deterministicEvidenceEligible', 'deterministicEnforcementEligible']) {
79
+ if (graph[key] !== undefined && typeof graph[key] !== 'boolean')
80
+ return false;
81
+ }
82
+ if (graph.enforcementIneligibilityReasons !== undefined && (!Array.isArray(graph.enforcementIneligibilityReasons)
83
+ || graph.enforcementIneligibilityReasons.length > 64
84
+ || !graph.enforcementIneligibilityReasons.every((item) => typeof item === 'string' && item.length > 0 && item.length <= 128)))
85
+ return false;
86
+ if (graph.recoveryCommand !== undefined
87
+ && !['neurcode brain repo-index', 'neurcode brain repo-recover'].includes(String(graph.recoveryCommand)))
88
+ return false;
89
+ if (graph.coverage !== undefined && graph.coverage !== null) {
90
+ if (typeof graph.coverage !== 'object' || Array.isArray(graph.coverage))
91
+ return false;
92
+ const coverage = graph.coverage;
93
+ for (const key of ['filesSeen', 'filesIndexed', 'filesAnalyzed', 'filesSkipped', 'filesUnsupported', 'filesDegraded', 'filesFailed']) {
94
+ if (typeof coverage[key] !== 'number' || !Number.isInteger(coverage[key]) || Number(coverage[key]) < 0)
95
+ return false;
96
+ }
97
+ }
98
+ if (graph.runtimeCompatibility !== undefined) {
99
+ if (!graph.runtimeCompatibility || typeof graph.runtimeCompatibility !== 'object' || Array.isArray(graph.runtimeCompatibility))
100
+ return false;
101
+ const compatibility = graph.runtimeCompatibility;
102
+ if (compatibility.component !== 'cli')
103
+ return false;
104
+ for (const key of ['contractId', 'runtimeContractVersion', 'manifestVersion']) {
105
+ if (typeof compatibility[key] !== 'string' || compatibility[key].length === 0)
106
+ return false;
107
+ }
108
+ }
109
+ if (!Array.isArray(policy.evaluatedRuleIds) || !Array.isArray(policy.notEvaluatedRuleIds) || !Array.isArray(policy.findings))
110
+ return false;
111
+ if (!Array.isArray(record.advisory))
112
+ return false;
113
+ if (privacy.sourceUploaded !== false || privacy.sourceStored !== false ||
114
+ privacy.diffUploaded !== false || privacy.promptUploaded !== false ||
115
+ privacy.chatUploaded !== false || privacy.rawContentRetained !== false)
116
+ return false;
117
+ try {
118
+ assertSourceFreeRepoIntelligence(value);
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ return true;
124
+ }
125
+ function sourceFreePrivacyContract() {
126
+ return {
127
+ sourceUploaded: false,
128
+ sourceStored: false,
129
+ diffUploaded: false,
130
+ promptUploaded: false,
131
+ chatUploaded: false,
132
+ rawContentRetained: false,
133
+ };
134
+ }
135
+ //# sourceMappingURL=repo-intelligence-v2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-intelligence-v2.js","sourceRoot":"","sources":["../src/repo-intelligence-v2.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@neurcode-ai/contracts",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Shared JSON contracts for Neurcode CLI, API, action, IDE, and MCP surfaces",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "scripts": {
8
11
  "build": "tsc",
9
12
  "dev": "tsc --watch",
@@ -12,6 +15,9 @@
12
15
  },
13
16
  "license": "MIT",
14
17
  "dependencies": {},
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
15
21
  "devDependencies": {
16
22
  "@types/node": "^20.10.0",
17
23
  "tsx": "^4.20.6",
@@ -1,93 +0,0 @@
1
- /**
2
- * Runtime Admission framing + privacy contract tests (Phase A).
3
- *
4
- * Covers:
5
- * - length-prefixed framing is byte-deterministic
6
- * - delimiter/path ambiguity cannot collide
7
- * - source-free privacy guard rejects source-like keys
8
- * - object-format helpers (sha1 vs sha256 widths)
9
- */
10
-
11
- import test from 'node:test';
12
- import assert from 'node:assert/strict';
13
-
14
- import {
15
- ADMISSION_SOURCE_LIKE_KEYS,
16
- AdmissionSourceLeakError,
17
- assertSourceFreeAdmissionValue,
18
- frameFields,
19
- frameRecordSet,
20
- objectIdHexLength,
21
- objectTypeForMode,
22
- zeroObjectId,
23
- } from './index';
24
-
25
- function toHex(bytes: Uint8Array): string {
26
- return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
27
- }
28
-
29
- test('frameFields is deterministic for identical input', () => {
30
- const a = frameFields(['src/auth/login.ts', 'modified', 'blob']);
31
- const b = frameFields(['src/auth/login.ts', 'modified', 'blob']);
32
- assert.equal(toHex(a), toHex(b));
33
- });
34
-
35
- test('length-prefixed fields cannot collide across boundaries', () => {
36
- // ["a","b"] must never produce the same bytes as ["ab"] or ["a\tb"].
37
- const split = toHex(frameFields(['a', 'b']));
38
- const joined = toHex(frameFields(['ab']));
39
- const tabbed = toHex(frameFields(['a\tb']));
40
- const nulled = toHex(frameFields(['a\0b']));
41
- assert.notEqual(split, joined);
42
- assert.notEqual(split, tabbed);
43
- assert.notEqual(split, nulled);
44
- assert.notEqual(joined, tabbed);
45
- });
46
-
47
- test('record boundaries cannot be forged by field content', () => {
48
- // Two records [["a"],["b"]] vs one record [["a","b"]] must differ.
49
- const twoRecords = toHex(frameRecordSet(['h'], [['a'], ['b']]));
50
- const oneRecord = toHex(frameRecordSet(['h'], [['a', 'b']]));
51
- assert.notEqual(twoRecords, oneRecord);
52
- });
53
-
54
- test('header participates in the framed bytes (domain separation)', () => {
55
- const delta = toHex(frameRecordSet(['delta'], [['x']]));
56
- const coverage = toHex(frameRecordSet(['coverage'], [['x']]));
57
- assert.notEqual(delta, coverage);
58
- });
59
-
60
- test('object id helpers respect format width', () => {
61
- assert.equal(objectIdHexLength('sha1'), 40);
62
- assert.equal(objectIdHexLength('sha256'), 64);
63
- assert.equal(zeroObjectId('sha1').length, 40);
64
- assert.equal(zeroObjectId('sha256').length, 64);
65
- assert.equal(objectTypeForMode('160000'), 'submodule');
66
- assert.equal(objectTypeForMode('120000'), 'symlink');
67
- assert.equal(objectTypeForMode('100755'), 'blob');
68
- assert.equal(objectTypeForMode('000000'), 'absent');
69
- assert.throws(() => objectTypeForMode('040000'));
70
- });
71
-
72
- test('source-free guard permits provenance keys', () => {
73
- const safe = {
74
- path: 'src/auth/login.ts',
75
- objectId: 'a'.repeat(40),
76
- mode: '100644',
77
- classification: 'governed_prewrite',
78
- sessions: ['abc123'],
79
- coverageSetHash: 'f'.repeat(64),
80
- capturedAt: '2026-06-02T00:00:00.000Z',
81
- };
82
- assert.doesNotThrow(() => assertSourceFreeAdmissionValue(safe));
83
- });
84
-
85
- test('source-free guard rejects planted source content keys', () => {
86
- for (const key of ['content', 'diff', 'patch', 'sourceText', 'sourceCode', 'rawPrompt', 'commandBody', 'secret', 'token']) {
87
- assert.ok(ADMISSION_SOURCE_LIKE_KEYS.has(key), `expected ${key} in denylist`);
88
- assert.throws(
89
- () => assertSourceFreeAdmissionValue({ manifest: { coverage: [{ [key]: 'leak' }] } }),
90
- AdmissionSourceLeakError,
91
- );
92
- }
93
- });
@@ -1,78 +0,0 @@
1
- /**
2
- * Runtime Admission — canonical byte-safe framing (Phase A).
3
- *
4
- * Deterministic, length-prefixed framing for the delta and coverage hashes.
5
- * Web-safe: uses Uint8Array + TextEncoder only (this package is imported by
6
- * the dashboard and other non-Node surfaces). No Node Buffer, no crypto here —
7
- * hashing of the framed bytes happens in the governance-runtime core.
8
- *
9
- * Why length-prefixed framing (and not a delimiter join):
10
- * A delimiter such as "\0" or "\t" can appear inside a path, so a delimiter
11
- * join lets two distinct field sets collapse to the same byte stream
12
- * (e.g. ["a\tb"] vs ["a","b"]). Every field and every record here is prefixed
13
- * with its exact byte length, so field and record boundaries are
14
- * unambiguous and no path content can forge a boundary.
15
- */
16
-
17
- /** Schema/domain version for the framing contract. Bump only on a breaking framing change. */
18
- export const ADMISSION_FRAMING_VERSION = 'neurcode.admission-framing.v1' as const;
19
-
20
- /** Domain separators so delta and coverage hashes can never alias each other. */
21
- export const ADMISSION_DELTA_HASH_DOMAIN = 'neurcode.admission.delta.v1' as const;
22
- export const ADMISSION_COVERAGE_SET_HASH_DOMAIN = 'neurcode.admission.coverage-set.v1' as const;
23
-
24
- const textEncoder = new TextEncoder();
25
-
26
- function u32be(value: number): Uint8Array {
27
- if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
28
- throw new Error(`admission framing: length out of range: ${value}`);
29
- }
30
- const out = new Uint8Array(4);
31
- const view = new DataView(out.buffer);
32
- view.setUint32(0, value, false);
33
- return out;
34
- }
35
-
36
- function concatBytes(parts: Uint8Array[]): Uint8Array {
37
- let total = 0;
38
- for (const part of parts) total += part.length;
39
- const out = new Uint8Array(total);
40
- let offset = 0;
41
- for (const part of parts) {
42
- out.set(part, offset);
43
- offset += part.length;
44
- }
45
- return out;
46
- }
47
-
48
- /** Frame a single field as: u32be(byteLength) ‖ utf8(value). */
49
- export function frameField(value: string): Uint8Array {
50
- const bytes = textEncoder.encode(value);
51
- return concatBytes([u32be(bytes.length), bytes]);
52
- }
53
-
54
- /** Frame an ordered list of fields. Order is preserved; the caller sorts records. */
55
- export function frameFields(fields: readonly string[]): Uint8Array {
56
- return concatBytes(fields.map((field) => frameField(field)));
57
- }
58
-
59
- /**
60
- * Frame a header plus an ordered set of records into one canonical byte stream.
61
- *
62
- * Layout:
63
- * frameFields(header)
64
- * for each record: u32be(recordByteLength) ‖ frameFields(record)
65
- *
66
- * Records must already be canonically sorted by the caller (the hash functions
67
- * sort before calling this, which is what makes shuffled input produce an
68
- * identical hash). Double length-prefixing (record length + per-field length)
69
- * makes both record and field boundaries unambiguous.
70
- */
71
- export function frameRecordSet(header: readonly string[], records: readonly (readonly string[])[]): Uint8Array {
72
- const parts: Uint8Array[] = [frameFields(header)];
73
- for (const record of records) {
74
- const recordBytes = frameFields(record);
75
- parts.push(u32be(recordBytes.length), recordBytes);
76
- }
77
- return concatBytes(parts);
78
- }
@@ -1,58 +0,0 @@
1
- export {
2
- ADMISSION_COVERAGE_MANIFEST_SCHEMA_VERSION,
3
- ADMISSION_CONSISTENCY_DECISION_SCHEMA_VERSION,
4
- SELF_ATTESTED_ADMISSION_RECORD_SCHEMA_VERSION,
5
- SELF_ATTESTED_ADMISSION_DISCLAIMER,
6
- GIT_MODE_ABSENT,
7
- GIT_MODE_BLOB,
8
- GIT_MODE_EXEC,
9
- GIT_MODE_SUBMODULE,
10
- GIT_MODE_SYMLINK,
11
- coverageEntryCanonicalFields,
12
- coverageEntryIdentityKey,
13
- deltaEntryCanonicalFields,
14
- isAdmissibleClassification,
15
- isGovernedClassification,
16
- isKnownGitMode,
17
- isStrictlyAdmissible,
18
- isValidObjectId,
19
- isZeroObjectId,
20
- objectIdHexLength,
21
- objectTypeForMode,
22
- zeroObjectId,
23
- type AdmissionCaptureDescriptor,
24
- type AdmissionCaptureMode,
25
- type AdmissionChangeType,
26
- type AdmissionEligibilityMode,
27
- type AdmissionEligibilityOptions,
28
- type AdmissionConsistencyDecision,
29
- type AdmissionConsistencyVerdict,
30
- type AdmissionCoverageClassification,
31
- type AdmissionCoverageEntry,
32
- type AdmissionCoverageManifest,
33
- type AdmissionDeltaEntry,
34
- type AdmissionObjectType,
35
- type AdmissionRepoIdentifiers,
36
- type AdmissionSessionRef,
37
- type GitObjectFormat,
38
- type RuntimeAdmissionAgentHost,
39
- type RuntimeAdmissionContext,
40
- type RuntimeAdmissionReceiptSummary,
41
- type RuntimeAdmissionTrustLevel,
42
- type SelfAttestedAdmissionRecord,
43
- } from './schema';
44
-
45
- export {
46
- ADMISSION_COVERAGE_SET_HASH_DOMAIN,
47
- ADMISSION_DELTA_HASH_DOMAIN,
48
- ADMISSION_FRAMING_VERSION,
49
- frameField,
50
- frameFields,
51
- frameRecordSet,
52
- } from './framing';
53
-
54
- export {
55
- ADMISSION_SOURCE_LIKE_KEYS,
56
- AdmissionSourceLeakError,
57
- assertSourceFreeAdmissionValue,
58
- } from './privacy';
@@ -1,93 +0,0 @@
1
- /**
2
- * Runtime Admission — source-free privacy guard (Phase A).
3
- *
4
- * The admission artifact may contain only: paths, object ids, modes, hashes,
5
- * classifications, timestamps, session ids, and version strings. It must never
6
- * contain file content, diff hunks, patch text, excerpts, or secrets.
7
- *
8
- * This guard is a denylist on key names (mirrors the runtime-sync upload guard)
9
- * plus a value-shape check. It is intentionally conservative: any source-like
10
- * key anywhere in the structure throws.
11
- */
12
-
13
- /** Keys that would indicate source content / diffs / secrets leaked into the artifact. */
14
- export const ADMISSION_SOURCE_LIKE_KEYS: ReadonlySet<string> = new Set([
15
- 'content',
16
- 'fileContent',
17
- 'file_content',
18
- 'sourceText',
19
- 'source_text',
20
- 'sourceCode',
21
- 'source_code',
22
- 'source',
23
- 'body',
24
- 'text',
25
- 'diff',
26
- 'diffText',
27
- 'diff_text',
28
- 'diffHunk',
29
- 'diffHunks',
30
- 'patch',
31
- 'patchText',
32
- 'patchBody',
33
- 'patch_body',
34
- 'hunk',
35
- 'hunks',
36
- 'excerpt',
37
- 'snippet',
38
- 'before',
39
- 'after',
40
- 'blobContent',
41
- 'contents',
42
- 'prompt',
43
- 'rawPrompt',
44
- 'raw_prompt',
45
- 'promptWithSource',
46
- 'prompt_with_source',
47
- 'commandBody',
48
- 'command_body',
49
- 'shellCommand',
50
- 'shell_command',
51
- 'secret',
52
- 'secrets',
53
- 'token',
54
- 'password',
55
- ]);
56
-
57
- export class AdmissionSourceLeakError extends Error {
58
- constructor(public readonly keyPath: string) {
59
- super(`admission artifact contains source-like key: ${keyPath}`);
60
- this.name = 'AdmissionSourceLeakError';
61
- }
62
- }
63
-
64
- function isSourceLikeAdmissionKey(key: string): boolean {
65
- if (ADMISSION_SOURCE_LIKE_KEYS.has(key)) return true;
66
- const normalized = key.toLowerCase().replace(/[^a-z0-9_]/g, '');
67
- const compact = normalized.replace(/_/g, '');
68
- for (const blocked of ADMISSION_SOURCE_LIKE_KEYS) {
69
- const blockedNormalized = blocked.toLowerCase().replace(/[^a-z0-9_]/g, '');
70
- if (normalized === blockedNormalized || compact === blockedNormalized.replace(/_/g, '')) {
71
- return true;
72
- }
73
- }
74
- return false;
75
- }
76
-
77
- /**
78
- * Walk a value and throw AdmissionSourceLeakError if any object key is a
79
- * source-like key. Arrays and nested objects are walked recursively.
80
- */
81
- export function assertSourceFreeAdmissionValue(value: unknown, path = 'admission'): void {
82
- if (Array.isArray(value)) {
83
- value.forEach((item, index) => assertSourceFreeAdmissionValue(item, `${path}[${index}]`));
84
- return;
85
- }
86
- if (!value || typeof value !== 'object') return;
87
- for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
88
- if (isSourceLikeAdmissionKey(key)) {
89
- throw new AdmissionSourceLeakError(`${path}.${key}`);
90
- }
91
- assertSourceFreeAdmissionValue(child, `${path}.${key}`);
92
- }
93
- }