Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions tests/wp0/v21-learning-policy-p1-decision-record.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');

const { createLearningPolicyDecisionContract } = require('../../backend/services/learningPolicyDecisionContract');

test('Learned Policy DecisionRecord binds canonical identity, persona, features, action and generation provenance', () => {
const authorityCalls = [];
const contract = createLearningPolicyDecisionContract({
personContextAuthority: {
resolve(input) {
authorityCalls.push(input);
return {
authority: 'PersonContextAuthority',
found: true,
personId: 'person-1',
contactIds: ['contact-1'],
conversationIds: ['conversation-1']
};
}
}
});

const record = contract.createDecisionRecord({
contactId: 'contact-1',
conversationId: 'conversation-1',
personaProfileId: 'persona-owner-v7',
featureBundle: {
relationshipStage: 'warming',
interactionBand: 'balanced',
targetLanguage: 'en'
},
candidateStrategyBranch: 'screen_and_advance',
policyVersion: 'vw-p1-v1',
policyArtifactId: 'artifact-sha256:abc123',
generation: {
modelBrainExecutionId: 'exec-1',
candidatePlanId: 'plan-1'
}
});

assert.equal(authorityCalls.length, 1);
assert.equal(record.personId, 'person-1');
assert.deepEqual(record.contactIds, ['contact-1']);
assert.deepEqual(record.conversationIds, ['conversation-1']);
assert.equal(record.personaProfileId, 'persona-owner-v7');
assert.equal(record.candidateStrategyBranch, 'screen_and_advance');
assert.equal(record.actionProbability, 1);
assert.equal(record.exploration, false);
assert.equal(record.policyVersion, 'vw-p1-v1');
assert.equal(record.policyArtifactId, 'artifact-sha256:abc123');
assert.match(record.decisionId, /^decision:/);
assert.equal(Object.isFrozen(record), true);
});

test('DecisionRecord fails closed on identity/persona ambiguity and rejects raw chat bodies in features', () => {
const ambiguous = createLearningPolicyDecisionContract({
personContextAuthority: {
resolve() {
return { authority: 'PersonContextAuthority', found: true, personId: 'person-1', contactIds: ['contact-1'], conversationIds: ['conversation-2'] };
}
}
});

assert.throws(() => ambiguous.createDecisionRecord({
contactId: 'contact-1',
conversationId: 'conversation-1',
personaProfileId: 'persona-owner-v7',
featureBundle: { relationshipStage: 'warming' },
candidateStrategyBranch: 'natural_hook',
policyVersion: 'vw-p1-v1',
policyArtifactId: 'artifact-sha256:abc123',
generation: { modelBrainExecutionId: 'exec-1' }
}), error => error?.reasonCode === 'LEARNING_POLICY_IDENTITY_BINDING_MISMATCH');

const valid = createLearningPolicyDecisionContract({
personContextAuthority: {
resolve() {
return { authority: 'PersonContextAuthority', found: true, personId: 'person-1', contactIds: ['contact-1'], conversationIds: ['conversation-1'] };
}
}
});
assert.throws(() => valid.createDecisionRecord({
contactId: 'contact-1',
conversationId: 'conversation-1',
personaProfileId: 'persona-owner-v7',
featureBundle: { rawChatBody: 'private text' },
candidateStrategyBranch: 'natural_hook',
policyVersion: 'vw-p1-v1',
policyArtifactId: 'artifact-sha256:abc123',
generation: { modelBrainExecutionId: 'exec-1' }
}), error => error?.reasonCode === 'LEARNING_POLICY_FEATURE_BUNDLE_PRIVATE_BODY_FORBIDDEN');
});
62 changes: 62 additions & 0 deletions tests/wp0/v21-learning-policy-p1-outcome-binding.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');

const { createLearningOutcomeAttributionService } = require('../../backend/services/learningOutcomeAttributionService');

test('raw OutcomeVector is immutable, decision-bound and permanently non-trainable', () => {
const service = createLearningOutcomeAttributionService();
const vector = service.createOutcomeVector({
decisionId: 'decision:abc',
outcomes: [
{ outcomeId: 'outcome-1', type: 'reply_received', value: 1, evidenceRef: 'message-99' },
{ outcomeId: 'outcome-2', type: 'conversation_continued', value: 1, evidenceRef: 'conversation-1' }
],
observedAt: '2026-08-14T11:00:00.000Z'
});

assert.equal(vector.signalType, 'policy_outcome_observed');
assert.equal(vector.learningEligible, false);
assert.equal(vector.decisionId, 'decision:abc');
assert.equal(Object.isFrozen(vector), true);
assert.equal(Object.isFrozen(vector.outcomes), true);
});

test('trainable binding requires the immutable eligible candidate_sent anchor and Learning-approved Langfuse Score', () => {
const service = createLearningOutcomeAttributionService();
const vector = service.createOutcomeVector({
decisionId: 'decision:abc',
outcomes: [{ outcomeId: 'outcome-1', type: 'reply_received', value: 1, evidenceRef: 'message-99' }],
observedAt: '2026-08-14T11:00:00.000Z'
});
const source = {
signal_id: 'signal-1',
signal_type: 'candidate_sent',
learning_eligible: true,
signal: { decisionRecord: { decisionId: 'decision:abc' } }
};
const score = {
authority: 'Langfuse',
approvedByLearning: true,
scoreId: 'score-1',
eligibleSourceSignalId: 'signal-1',
decisionId: 'decision:abc',
outcomeIds: ['outcome-1'],
outcomeEvidenceSetRef: 'evidence-set-1',
rewardPolicyVersion: 'reward-v1',
value: 1
};

const bound = service.bindTrainableOutcome({ eligibleSourceSignal: source, outcomeVector: vector, score });
assert.equal(bound.eligibleSourceSignalId, 'signal-1');
assert.equal(bound.decisionId, 'decision:abc');
assert.equal(bound.reward.authority, 'Langfuse');
assert.equal(vector.learningEligible, false, 'binding may never upgrade the raw outcome row');

assert.throws(() => service.bindTrainableOutcome({
eligibleSourceSignal: { ...source, learning_eligible: false },
outcomeVector: vector,
score
}), error => error?.reasonCode === 'LEARNING_POLICY_ELIGIBLE_SOURCE_SIGNAL_REQUIRED');
});
24 changes: 24 additions & 0 deletions tests/wp0/v21-learning-policy-p1-production-consumption.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const ROOT = path.resolve(__dirname, '..', '..');
const source = () => fs.readFileSync(path.join(ROOT, 'backend/services/contextAwareReplyBrain.js'), 'utf8');

test('production reply brain consumes Learned Policy before existing frontier generation', () => {
const text = source();
assert.match(text, /require\(['"]\.\/learningPolicyRuntimeAdapter['"]\)/);
assert.match(text, /selectLearnedPolicyAction\s*\(/);
assert.match(text, /candidateStrategyBranch/);
assert.match(text, /aiGateway\.execute\s*\(/, 'existing Model Brain gateway must remain the final generator');
});

test('Learned Policy production consumption cannot take provider/model credential or final-text authority', () => {
const text = source();
assert.doesNotMatch(text, /learnedPolicy[^\n]{0,120}(apiKey|credential|providerCredential)/i);
assert.doesNotMatch(text, /learnedPolicy[^\n]{0,120}modelId\s*:/i);
assert.doesNotMatch(text, /learnedPolicy[^\n]{0,120}(finalReply|finalText)\s*:/i);
});
72 changes: 72 additions & 0 deletions tests/wp0/v21-learning-policy-p1-projection.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');

const { createLearningDeepTrainingContract } = require('../../backend/services/learningDeepTrainingContract');

test('projectPolicy reads only immutable eligible source signals and joins raw outcomes by decisionId', async () => {
const repository = {
async listLearningSignals(query) {
assert.equal(query.learningEligible, true);
return [{
signal_id: 'signal-1',
signal_type: 'candidate_sent',
learning_eligible: true,
scope_type: 'relationship',
scope_id: 'person-1',
signal: {
decisionRecord: {
decisionId: 'decision:abc',
candidateStrategyBranch: 'natural_hook',
featureBundle: { relationshipStage: 'warming' },
policyVersion: 'vw-p1-v1',
policyArtifactId: 'artifact-sha256:abc123'
}
}
}];
},
async listPolicyOutcomeSignals({ decisionIds }) {
assert.deepEqual(decisionIds, ['decision:abc']);
return [{
signal_id: 'raw-outcome-1',
signal_type: 'policy_outcome_observed',
learning_eligible: false,
signal: {
decisionId: 'decision:abc',
outcomes: [{ outcomeId: 'outcome-1', type: 'reply_received', value: 1, evidenceRef: 'message-99' }]
}
}];
}
};
const contract = createLearningDeepTrainingContract({ repository, dataPolicy: { minimize: async value => ({ allowed: true, text: value.text || '' }) } });
const projection = await contract.projectPolicy({
scopeType: 'relationship',
scopeId: 'person-1',
approvedScoresBySignalId: {
'signal-1': {
authority: 'Langfuse', approvedByLearning: true, scoreId: 'score-1', name: 'policy_reward', value: 1, traceId: 'trace-1',
eligibleSourceSignalId: 'signal-1', decisionId: 'decision:abc', outcomeIds: ['outcome-1'], outcomeEvidenceSetRef: 'evidence-set-1', rewardPolicyVersion: 'reward-v1'
}
}
});

assert.equal(projection.authority, 'Learning');
assert.equal(projection.readOnly, true);
assert.equal(projection.trajectory.length, 1);
assert.equal(projection.trajectory[0].decisionId, 'decision:abc');
assert.equal(projection.trajectory[0].outcomes[0].outcomeId, 'outcome-1');
});

test('projectPolicy never upgrades raw outcome eligibility', async () => {
const raw = { signal_id: 'raw-outcome-1', signal_type: 'policy_outcome_observed', learning_eligible: false, signal: { decisionId: 'decision:abc', outcomes: [] } };
const repository = {
async listLearningSignals() {
return [{ signal_id: 'signal-1', signal_type: 'candidate_sent', learning_eligible: true, scope_type: 'relationship', scope_id: 'person-1', signal: { decisionRecord: { decisionId: 'decision:abc', candidateStrategyBranch: 'natural_hook', featureBundle: {}, policyVersion: 'vw-p1-v1', policyArtifactId: 'artifact-sha256:abc123' } } }];
},
async listPolicyOutcomeSignals() { return [raw]; }
};
const contract = createLearningDeepTrainingContract({ repository, dataPolicy: { minimize: async () => ({ allowed: true, text: '' }) } });
await contract.projectPolicy({ scopeType: 'relationship', scopeId: 'person-1', approvedScoresBySignalId: { 'signal-1': { authority: 'Langfuse', approvedByLearning: true, scoreId: 'score-1', name: 'policy_reward', value: 1, traceId: 'trace-1', eligibleSourceSignalId: 'signal-1', decisionId: 'decision:abc', outcomeIds: [], outcomeEvidenceSetRef: 'evidence-set-1', rewardPolicyVersion: 'reward-v1' } } });
assert.equal(raw.learning_eligible, false);
});
25 changes: 25 additions & 0 deletions tests/wp0/v21-learning-policy-p1-supply-chain.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const ROOT = path.resolve(__dirname, '..', '..');
const read = p => fs.readFileSync(path.join(ROOT, p), 'utf8');

test('Vowpal Wabbit supply chain is pinned to 9.11.2 with exact upstream provenance and BSD-3-Clause notice', () => {
const pyproject = read('runtime/learning-growth/python/pyproject.toml');
const lock = read('runtime/learning-growth/python/uv.lock');
const upstream = JSON.parse(read('config/upstreams/v21-learning-growth-brain-p0.json'));
const notices = read('THIRD_PARTY_NOTICES.md');
const license = read('third_party/licenses/vowpal-wabbit-BSD-3-Clause.txt');

assert.match(pyproject, /vowpalwabbit\s*==\s*9\.11\.2/i);
assert.match(lock, /name\s*=\s*["']vowpalwabbit["']/i);
assert.match(lock, /version\s*=\s*["']9\.11\.2["']/i);
assert.match(JSON.stringify(upstream), /122bae254a5b8bc2b774d13b33d53e6dbc2cfba7/);
assert.match(notices, /Vowpal Wabbit/i);
assert.match(notices, /BSD-3-Clause/i);
assert.match(license, /Redistribution and use in source and binary forms/i);
});
40 changes: 40 additions & 0 deletions tests/wp0/v21-learning-policy-p1-vw-runtime.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');

const ROOT = path.resolve(__dirname, '..', '..');
const read = p => fs.readFileSync(path.join(ROOT, p), 'utf8');

const { createLearningPolicyRuntimeAdapter } = require('../../backend/services/learningPolicyRuntimeAdapter');

test('Learning runtime adapter delegates the action head to sealed Vowpal Wabbit and keeps P1 deterministic', async () => {
const calls = [];
const adapter = createLearningPolicyRuntimeAdapter({
invokeVowpalWabbit: async input => {
calls.push(input);
return { action: 'playful_attraction', policyVersion: 'vw-p1-v1', policyArtifactId: 'artifact-sha256:abc123' };
}
});
const decision = await adapter.selectLearnedPolicyAction({
featureBundle: { relationshipStage: 'warming', interactionBand: 'balanced' },
allowedActions: ['natural_hook', 'playful_attraction', 'direct_advance', 'screen_and_advance', 'leave_aftertaste']
});

assert.equal(calls.length, 1);
assert.equal(decision.candidateStrategyBranch, 'playful_attraction');
assert.equal(decision.actionProbability, 1);
assert.equal(decision.exploration, false);
assert.equal(decision.providerRoutingAuthority, undefined);
assert.equal(decision.finalReply, undefined);
});

test('sealed Learning Python entrypoint exposes the VW policy action mode without provider credentials or local text generation', () => {
const source = read('runtime/learning-growth/python/learning_entrypoint.py');
assert.match(source, /vowpalwabbit/i);
assert.match(source, /learned[_-]policy/i);
assert.doesNotMatch(source, /(OPENAI_API_KEY|ANTHROPIC_API_KEY|provider[_-]?credential)/i);
assert.doesNotMatch(source, /(generate[_-]?reply|final[_-]?reply)/i);
});
Loading